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;