diff --git a/src/main/java/com/yunhai/userbase/aspect/OapiLogAspect.java b/src/main/java/com/yunhai/userbase/aspect/OapiLogAspect.java index 961e8c7..4618da8 100644 --- a/src/main/java/com/yunhai/userbase/aspect/OapiLogAspect.java +++ b/src/main/java/com/yunhai/userbase/aspect/OapiLogAspect.java @@ -30,6 +30,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -77,18 +78,17 @@ public class OapiLogAspect { SysOapiLog entity = buildEntity(point, request, httpMethod, oapiLog); boolean saveResponse = oapiLog != null ? oapiLog.saveResponse() : true; - int maxResponseSize = oapiLog != null ? oapiLog.maxResponseSize() : 10240; Object result = null; try { result = point.proceed(); entity.setStatusCode(200); - fillResponse(entity, result, saveResponse, maxResponseSize); + fillResponse(entity, result, saveResponse); } catch (Throwable e) { entity.setStatusCode(500); entity.setErrorMsg(truncate(e.getMessage(), 2000)); // 业务异常(DException/BizException)同样构造实际返回的响应体落库,便于详情下载 - fillErrorResponse(entity, e, saveResponse, maxResponseSize); + fillErrorResponse(entity, e, saveResponse); throw e; } finally { entity.setCostTime(System.currentTimeMillis() - start); @@ -216,20 +216,20 @@ public class OapiLogAspect { } /** - * 写入响应结果(脱敏 → 截断 → GZIP)。 + * 写入响应结果(脱敏 → GZIP,不截断)。 */ - private void fillResponse(SysOapiLog entity, Object result, boolean saveResponse, int maxResponseSize) { + private void fillResponse(SysOapiLog entity, Object result, boolean saveResponse) { if (!saveResponse || result == null) { return; } - writeResponseData(entity, result, maxResponseSize); + writeResponseData(entity, result); } /** * 异常路径:与 {@code GlobalExceptionHandler} 保持一致的响应体(DException/BizException → ApiResponse), * 使日志下载内容等于调用方实际收到的 body;其余异常不构造(响应不可预期)。 */ - private void fillErrorResponse(SysOapiLog entity, Throwable e, boolean saveResponse, int maxResponseSize) { + private void fillErrorResponse(SysOapiLog entity, Throwable e, boolean saveResponse) { if (!saveResponse) { return; } @@ -237,7 +237,7 @@ public class OapiLogAspect { if (response == null) { return; } - writeResponseData(entity, response, maxResponseSize); + writeResponseData(entity, response); } private ApiResponse toApiResponse(Throwable e) { @@ -252,7 +252,7 @@ public class OapiLogAspect { return null; } - private void writeResponseData(SysOapiLog entity, Object payload, int maxResponseSize) { + private void writeResponseData(SysOapiLog entity, Object payload) { if (payload == null) { return; } @@ -261,10 +261,8 @@ public class OapiLogAspect { if (resp == null) { return; } - if (resp.length() > maxResponseSize) { - resp = resp.substring(0, maxResponseSize) + "...[截断]"; - } entity.setResponseData(GzipUtil.compress(resp)); + entity.setResponseSize(resp.getBytes(StandardCharsets.UTF_8).length); } catch (Exception e) { log.warn("开放接口日志响应序列化失败, uri={}", entity.getRequestUri(), e); } diff --git a/src/main/java/com/yunhai/userbase/aspect/OperationLogAspect.java b/src/main/java/com/yunhai/userbase/aspect/OperationLogAspect.java index 99afc9b..2850743 100644 --- a/src/main/java/com/yunhai/userbase/aspect/OperationLogAspect.java +++ b/src/main/java/com/yunhai/userbase/aspect/OperationLogAspect.java @@ -23,6 +23,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -45,9 +46,6 @@ public class OperationLogAspect { /** 不记录日志查询/下载接口自身,避免递归与刷屏。 */ private static final String LOG_CONTROLLER = "com.yunhai.userbase.controller.api.SysOperationLogController"; - /** GET 自动记录时使用的默认响应保存上限(10KB)。 */ - private static final int DEFAULT_MAX_RESPONSE_SIZE = 10240; - @Autowired private ISysOperationLogService sysOperationLogService; @@ -76,13 +74,12 @@ public class OperationLogAspect { // GET 未标注注解时使用默认参数;标注了则优先用注解配置 boolean saveResponse = operationLog != null ? operationLog.saveResponse() : true; - int maxResponseSize = operationLog != null ? operationLog.maxResponseSize() : DEFAULT_MAX_RESPONSE_SIZE; Object result = null; try { result = point.proceed(); entity.setStatusCode(200); - fillResponse(entity, result, saveResponse, maxResponseSize); + fillResponse(entity, result, saveResponse); } catch (Throwable e) { entity.setStatusCode(500); entity.setErrorMsg(truncate(e.getMessage(), 2000)); @@ -174,18 +171,16 @@ public class OperationLogAspect { } /** - * 写入响应结果(GZIP 压缩 + 截断)。 + * 写入响应结果(全量 GZIP 压缩入库,不再截断)。 */ - private void fillResponse(SysOperationLog entity, Object result, boolean saveResponse, int maxResponseSize) { + private void fillResponse(SysOperationLog entity, Object result, boolean saveResponse) { if (!saveResponse || result == null) { return; } try { String resp = JSON.toJSONString(result); - if (resp.length() > maxResponseSize) { - resp = resp.substring(0, maxResponseSize) + "...[截断]"; - } entity.setResponseData(GzipUtil.compress(resp)); + entity.setResponseSize(resp.getBytes(StandardCharsets.UTF_8).length); } catch (Exception e) { log.warn("操作日志响应结果序列化失败, uri={}", entity.getRequestUri(), e); } diff --git a/src/main/java/com/yunhai/userbase/common/annotation/OapiLog.java b/src/main/java/com/yunhai/userbase/common/annotation/OapiLog.java index f34290c..4445f14 100644 --- a/src/main/java/com/yunhai/userbase/common/annotation/OapiLog.java +++ b/src/main/java/com/yunhai/userbase/common/annotation/OapiLog.java @@ -32,9 +32,4 @@ public @interface OapiLog { * 是否保存响应结果(GZIP 压缩后入库)。 */ boolean saveResponse() default true; - - /** - * 响应结果序列化后的最大字符数,超过则截断。默认 10KB。 - */ - int maxResponseSize() default 10240; } diff --git a/src/main/java/com/yunhai/userbase/common/annotation/OperationLog.java b/src/main/java/com/yunhai/userbase/common/annotation/OperationLog.java index db7b8d4..54e99c0 100644 --- a/src/main/java/com/yunhai/userbase/common/annotation/OperationLog.java +++ b/src/main/java/com/yunhai/userbase/common/annotation/OperationLog.java @@ -27,9 +27,4 @@ public @interface OperationLog { * 是否保存响应结果(GZIP 压缩后入库)。 */ boolean saveResponse() default true; - - /** - * 响应结果序列化后的最大字符数,超过则截断。默认 10KB。 - */ - int maxResponseSize() default 10240; } diff --git a/src/main/java/com/yunhai/userbase/entity/SysOapiLog.java b/src/main/java/com/yunhai/userbase/entity/SysOapiLog.java index 5ab18d6..d538a5d 100644 --- a/src/main/java/com/yunhai/userbase/entity/SysOapiLog.java +++ b/src/main/java/com/yunhai/userbase/entity/SysOapiLog.java @@ -114,6 +114,12 @@ public class SysOapiLog implements Serializable { @Column(name = "response_data", columnDefinition = "MEDIUMBLOB") private byte[] responseData; + /** + * 响应原文(GZIP 前)UTF-8 字节数,写入时算好,列表按此列切换在线预览/下载,避免加载 response_data 大字段。 + */ + @Column(name = "response_size") + private Integer responseSize; + /** * HTTP状态码 */ diff --git a/src/main/java/com/yunhai/userbase/entity/SysOperationLog.java b/src/main/java/com/yunhai/userbase/entity/SysOperationLog.java index ade5499..5fe8874 100644 --- a/src/main/java/com/yunhai/userbase/entity/SysOperationLog.java +++ b/src/main/java/com/yunhai/userbase/entity/SysOperationLog.java @@ -106,6 +106,12 @@ public class SysOperationLog implements Serializable { @Column(name = "response_data", columnDefinition = "MEDIUMBLOB") private byte[] responseData; + /** + * 响应原文(GZIP 前)UTF-8 字节数,写入时算好,列表按此列切换在线预览/下载,避免加载 response_data 大字段。 + */ + @Column(name = "response_size") + private Integer responseSize; + /** * HTTP状态码 */ diff --git a/src/main/java/com/yunhai/userbase/model/oapilog/SysOapiLogListVo.java b/src/main/java/com/yunhai/userbase/model/oapilog/SysOapiLogListVo.java index 37a5acd..3eb9bf2 100644 --- a/src/main/java/com/yunhai/userbase/model/oapilog/SysOapiLogListVo.java +++ b/src/main/java/com/yunhai/userbase/model/oapilog/SysOapiLogListVo.java @@ -38,8 +38,6 @@ public class SysOapiLogListVo { private String operationDesc; /** 请求参数JSON(脱敏后) */ private String requestParams; - /** 响应结果(GZIP 解压后原文,脱敏后;随列表带出,详情弹窗直接展示) */ - private String responseData; /** HTTP 状态码 */ private Integer statusCode; /** 耗时(ms) */ @@ -52,6 +50,8 @@ public class SysOapiLogListVo { @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JSONField(format = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime; + /** 响应原文字节数(用于前端按大小切换在线预览/下载,response_data 原文不进列表) */ + private Integer responseSize; /** * 全参构造,顺序需与 {@code SysOapiLog} 的列表投影字段一致。
@@ -61,7 +61,8 @@ public class SysOapiLogListVo { String requestIp, String requestUri, String requestMethod, String operationType, String classMethod, String operationDesc, String requestParams, Integer statusCode, Long costTime, - String userAgent, String errorMsg, LocalDateTime createTime) { + String userAgent, String errorMsg, LocalDateTime createTime, + Integer responseSize) { this.id = id; this.logLevel = logLevel; this.callerAppKey = callerAppKey; @@ -78,5 +79,6 @@ public class SysOapiLogListVo { this.userAgent = userAgent; this.errorMsg = errorMsg; this.createTime = createTime; + this.responseSize = responseSize; } } diff --git a/src/main/java/com/yunhai/userbase/model/oplog/SysOperationLogListVo.java b/src/main/java/com/yunhai/userbase/model/oplog/SysOperationLogListVo.java index c731bfc..9cd8199 100644 --- a/src/main/java/com/yunhai/userbase/model/oplog/SysOperationLogListVo.java +++ b/src/main/java/com/yunhai/userbase/model/oplog/SysOperationLogListVo.java @@ -48,6 +48,8 @@ public class SysOperationLogListVo { @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JSONField(format = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime; + /** 响应原文字节数(用于前端按大小切换在线预览/下载,response_data 原文不进列表) */ + private Integer responseSize; /** * 全参构造,顺序需与 {@code SysOperationLog} 的列表投影字段一致。
@@ -57,7 +59,7 @@ public class SysOperationLogListVo { String requestIp, String requestUri, String requestMethod, String operationType, String classMethod, String requestParams, Integer statusCode, Long costTime, String userAgent, - String errorMsg, LocalDateTime createTime) { + String errorMsg, LocalDateTime createTime, Integer responseSize) { this.id = id; this.logLevel = logLevel; this.userId = userId; @@ -73,5 +75,6 @@ public class SysOperationLogListVo { this.userAgent = userAgent; this.errorMsg = errorMsg; this.createTime = createTime; + this.responseSize = responseSize; } } diff --git a/src/main/java/com/yunhai/userbase/model/push/PushRecordEntity.java b/src/main/java/com/yunhai/userbase/model/push/PushRecordEntity.java index 9017d6a..d746c9a 100644 --- a/src/main/java/com/yunhai/userbase/model/push/PushRecordEntity.java +++ b/src/main/java/com/yunhai/userbase/model/push/PushRecordEntity.java @@ -42,6 +42,10 @@ public class PushRecordEntity { @Column(name = "payload", columnDefinition = "MEDIUMTEXT") private String payload; + /** 请求体 UTF-8 字节数(写入时算好,列表按此列切换在线预览/下载,避免查询 payload 大字段)。 */ + @Column(name = "payload_size") + private Integer payloadSize; + @Column(name = "sign_t") private String signT; diff --git a/src/main/java/com/yunhai/userbase/model/push/PushRecordListVo.java b/src/main/java/com/yunhai/userbase/model/push/PushRecordListVo.java index 41e13db..d8acf29 100644 --- a/src/main/java/com/yunhai/userbase/model/push/PushRecordListVo.java +++ b/src/main/java/com/yunhai/userbase/model/push/PushRecordListVo.java @@ -46,6 +46,8 @@ public class PushRecordListVo { /** 创建时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createdAt; + /** 请求体字节数(用于前端按大小切换在线预览/下载,payload 原文不进列表) */ + private Integer payloadSize; /** * 全参构造,顺序需与 {@code PushRecordEntity} 的列表投影字段一致。
@@ -54,7 +56,7 @@ public class PushRecordListVo { public PushRecordListVo(Long id, Integer subscriberAppKey, String appName, String interfacePath, String interfaceLabel, String requestUrl, String signT, Integer httpStatus, Integer respCode, String respMsg, Integer success, Integer attemptNo, - Integer costMs, String triggerType, LocalDateTime createdAt) { + Integer costMs, String triggerType, LocalDateTime createdAt, Integer payloadSize) { this.id = id; this.subscriberAppKey = subscriberAppKey; this.appName = appName; @@ -70,5 +72,6 @@ public class PushRecordListVo { this.costMs = costMs; this.triggerType = triggerType; this.createdAt = createdAt; + this.payloadSize = payloadSize; } } diff --git a/src/main/java/com/yunhai/userbase/service/impl/SysOapiLogServiceImpl.java b/src/main/java/com/yunhai/userbase/service/impl/SysOapiLogServiceImpl.java index a70ac55..86d269f 100644 --- a/src/main/java/com/yunhai/userbase/service/impl/SysOapiLogServiceImpl.java +++ b/src/main/java/com/yunhai/userbase/service/impl/SysOapiLogServiceImpl.java @@ -73,7 +73,7 @@ public class SysOapiLogServiceImpl implements ISysOapiLogService { root.get("requestIp"), root.get("requestUri"), root.get("requestMethod"), root.get("operationType"), root.get("classMethod"), root.get("operationDesc"), root.get("requestParams"), root.get("statusCode"), root.get("costTime"), - root.get("userAgent"), root.get("errorMsg"), root.get("createTime"))); + root.get("userAgent"), root.get("errorMsg"), root.get("createTime"), root.get("responseSize"))); List predicates = buildPredicates(cb, root, query); cq.where(predicates.toArray(new Predicate[0])); cq.orderBy(cb.desc(root.get("createTime"))); @@ -89,10 +89,6 @@ public class SysOapiLogServiceImpl implements ISysOapiLogService { typed.setFirstResult((pageIndex - 1) * pageSize); typed.setMaxResults(pageSize); List content = typed.getResultList(); - // 响应结果随列表带出(GZIP 解压后),供详情弹窗直接展示,无需再单独请求 - for (SysOapiLogListVo vo : content) { - vo.setResponseData(getResponseData(vo.getId())); - } int totalPages = (int) Math.ceil((double) total / pageSize); boolean hasNext = pageIndex < totalPages; diff --git a/src/main/java/com/yunhai/userbase/service/impl/SysOperationLogServiceImpl.java b/src/main/java/com/yunhai/userbase/service/impl/SysOperationLogServiceImpl.java index 72076b4..11868ce 100644 --- a/src/main/java/com/yunhai/userbase/service/impl/SysOperationLogServiceImpl.java +++ b/src/main/java/com/yunhai/userbase/service/impl/SysOperationLogServiceImpl.java @@ -74,7 +74,7 @@ public class SysOperationLogServiceImpl implements ISysOperationLogService { root.get("requestIp"), root.get("requestUri"), root.get("requestMethod"), root.get("operationType"), root.get("classMethod"), root.get("requestParams"), root.get("statusCode"), root.get("costTime"), root.get("userAgent"), - root.get("errorMsg"), root.get("createTime"))); + root.get("errorMsg"), root.get("createTime"), root.get("responseSize"))); List predicates = buildPredicates(cb, root, query); cq.where(predicates.toArray(new Predicate[0])); cq.orderBy(cb.desc(root.get("createTime"))); diff --git a/src/main/java/com/yunhai/userbase/service/push/PushConsumer.java b/src/main/java/com/yunhai/userbase/service/push/PushConsumer.java index bb84f7f..96a9d0c 100644 --- a/src/main/java/com/yunhai/userbase/service/push/PushConsumer.java +++ b/src/main/java/com/yunhai/userbase/service/push/PushConsumer.java @@ -25,6 +25,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Map; @@ -100,6 +101,8 @@ public class PushConsumer { rec.setInterfaceLabel(msg.getInterfaceLabel()); rec.setRequestUrl(url == null ? "" : url); rec.setPayload(msg.getBody()); + rec.setPayloadSize(msg.getBody() == null ? 0 + : msg.getBody().getBytes(StandardCharsets.UTF_8).length); rec.setSignT(msg.getT()); rec.setHttpStatus(httpStatus); rec.setRespCode(respCode); diff --git a/src/main/java/com/yunhai/userbase/service/push/PushRecordService.java b/src/main/java/com/yunhai/userbase/service/push/PushRecordService.java index 0c58931..7d7a197 100644 --- a/src/main/java/com/yunhai/userbase/service/push/PushRecordService.java +++ b/src/main/java/com/yunhai/userbase/service/push/PushRecordService.java @@ -66,7 +66,7 @@ public class PushRecordService { root.get("interfacePath"), root.get("interfaceLabel"), root.get("requestUrl"), root.get("signT"), root.get("httpStatus"), root.get("respCode"), root.get("respMsg"), root.get("success"), root.get("attemptNo"), root.get("costMs"), - root.get("triggerType"), root.get("createdAt"))); + root.get("triggerType"), root.get("createdAt"), root.get("payloadSize"))); List predicates = buildPredicates(cb, root, q); cq.where(predicates.toArray(new Predicate[0])); cq.orderBy(buildOrders(cb, root, pageable.getSort())); diff --git a/src/main/resources/sql/push_record_add_payload_size.sql b/src/main/resources/sql/push_record_add_payload_size.sql new file mode 100644 index 0000000..5092331 --- /dev/null +++ b/src/main/resources/sql/push_record_add_payload_size.sql @@ -0,0 +1,11 @@ +-- 为 push_record 表新增 payload_size 字段:写入时记录请求体 UTF-8 字节数, +-- 供「推送日志」列表按大小切换在线预览/下载(列表只读该整数列,不再关联 payload 大字段)。 +-- 执行方式:由 DBA 手动在对应环境执行,AI 不代跑。 +-- 说明:dev 环境 spring.jpa.hibernate.ddl-auto=update 会自动建列,此时可跳过 ALTER、只跑回填。 + +ALTER TABLE push_record + ADD COLUMN payload_size INT NULL COMMENT '请求体UTF-8字节数(写入时算,列表按此切换预览/下载)'; + +-- 回填历史行:LENGTH() 对 utf8mb4 MEDIUMTEXT 返回字节数,与下载体积一致。 +-- 大表回填会读 off-page blob,建议低峰分批执行。 +UPDATE push_record SET payload_size = LENGTH(payload) WHERE payload_size IS NULL; diff --git a/src/main/resources/sql/sys_oapi_log_add_response_size.sql b/src/main/resources/sql/sys_oapi_log_add_response_size.sql new file mode 100644 index 0000000..124bb42 --- /dev/null +++ b/src/main/resources/sql/sys_oapi_log_add_response_size.sql @@ -0,0 +1,12 @@ +-- 为 sys_oapi_log 表新增 response_size 字段:写入时记录响应原文(GZIP 前)UTF-8 字节数, +-- 供「开放接口日志」列表按大小切换在线预览/下载(列表只读该整数列,不再关联 response_data 大字段)。 +-- 同时本次移除了写时截断(响应改为全量 GZIP 入库),并删除了列表查询里逐行解压回填的 N+1。 +-- 执行方式:由 DBA 手动在对应环境执行,AI 不代跑。 +-- 说明:dev 环境 spring.jpa.hibernate.ddl-auto=update 会自动建列,此时可跳过 ALTER、只跑回填。 + +ALTER TABLE sys_oapi_log + ADD COLUMN response_size INT NULL COMMENT '响应原文GZIP前UTF-8字节数(写入时算,列表按此切换预览/下载)'; + +-- 回填历史行:老数据 response_data 均为截断后(≤10KB)再 GZIP,LENGTH() 取的是压缩字节数(低估), +-- 但老行解压后本就远小于 50KB 预览阈值,按可预览处理安全;新行由切面写入精确值。 +UPDATE sys_oapi_log SET response_size = LENGTH(response_data) WHERE response_size IS NULL; diff --git a/src/main/resources/sql/sys_operation_log_add_response_size.sql b/src/main/resources/sql/sys_operation_log_add_response_size.sql new file mode 100644 index 0000000..e77f590 --- /dev/null +++ b/src/main/resources/sql/sys_operation_log_add_response_size.sql @@ -0,0 +1,12 @@ +-- 为 sys_operation_log 表新增 response_size 字段:写入时记录响应原文(GZIP 前)UTF-8 字节数, +-- 供「操作日志」列表按大小切换在线预览/下载(列表只读该整数列,不再关联 response_data 大字段)。 +-- 同时本次移除了写时截断(响应改为全量 GZIP 入库)。 +-- 执行方式:由 DBA 手动在对应环境执行,AI 不代跑。 +-- 说明:dev 环境 spring.jpa.hibernate.ddl-auto=update 会自动建列,此时可跳过 ALTER、只跑回填。 + +ALTER TABLE sys_operation_log + ADD COLUMN response_size INT NULL COMMENT '响应原文GZIP前UTF-8字节数(写入时算,列表按此切换预览/下载)'; + +-- 回填历史行:老数据 response_data 均为截断后(≤10KB)再 GZIP,LENGTH() 取的是压缩字节数(低估), +-- 但老行解压后本就远小于 50KB 预览阈值,按可预览处理安全;新行由切面写入精确值。 +UPDATE sys_operation_log SET response_size = LENGTH(response_data) WHERE response_size IS NULL;