refactor(common-log): 优化操作日志切面处理

- 改为 @Around 统一处理耗时、返回值和异常
  - 修复集合和 Map 参数过滤不完整的问题
  - 抽取日志长度常量,减少魔法数字
  - 拆分参数序列化逻辑,提升可读性
  - 异常日志改为输出完整堆栈
  - 参数日志支持 Collection 统一处理
This commit is contained in:
疯狂的狮子Li
2026-05-16 14:15:44 +08:00
parent 2af4b2c7a3
commit f4b4c710c5
@@ -9,10 +9,9 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch; import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.dromara.common.core.constant.SystemConstants; import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.utils.ServletUtils; import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;
@@ -28,6 +27,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.lang.reflect.Array;
import java.util.*; import java.util.*;
/** /**
@@ -41,45 +41,40 @@ import java.util.*;
public class LogAspect { public class LogAspect {
/** /**
* 计时 key * URL 最大记录长度
*/ */
private static final ThreadLocal<StopWatch> KEY_CACHE = new ThreadLocal<>(); private static final int MAX_URL_LENGTH = 255;
/** /**
* 在目标方法执行前启动耗时统计。 * 客户端标识最大记录长度
*/
private static final int MAX_CLIENT_KEY_LENGTH = 32;
/**
* 日志内容最大记录长度
*/
private static final int MAX_CONTENT_LENGTH = 3800;
/**
* 执行目标方法并记录操作日志。
* *
* @param joinPoint 切点 * @param joinPoint 切点
* @param controllerLog 日志注解 * @param controllerLog 日志注解
* @return 目标方法返回值
* @throws Throwable 目标方法异常
*/ */
@Before(value = "@annotation(controllerLog)") @Around(value = "@annotation(controllerLog)")
public void doBefore(JoinPoint joinPoint, Log controllerLog) { public Object doAround(ProceedingJoinPoint joinPoint, Log controllerLog) throws Throwable {
StopWatch stopWatch = new StopWatch(); StopWatch stopWatch = new StopWatch();
KEY_CACHE.set(stopWatch);
stopWatch.start(); stopWatch.start();
} try {
Object jsonResult = joinPoint.proceed();
/** handleLog(joinPoint, controllerLog, null, jsonResult, stopWatch);
* 在目标方法正常返回后记录操作日志。 return jsonResult;
* } catch (Exception e) {
* @param joinPoint 切点 handleLog(joinPoint, controllerLog, e, null, stopWatch);
* @param controllerLog 日志注解 throw e;
* @param jsonResult 返回结果 }
*/
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
handleLog(joinPoint, controllerLog, null, jsonResult);
}
/**
* 在目标方法抛出异常后记录操作日志。
*
* @param joinPoint 切点
* @param controllerLog 日志注解
* @param e 异常
*/
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
handleLog(joinPoint, controllerLog, e, null);
} }
/** /**
@@ -89,8 +84,9 @@ public class LogAspect {
* @param controllerLog 日志注解 * @param controllerLog 日志注解
* @param e 异常信息 * @param e 异常信息
* @param jsonResult 返回结果 * @param jsonResult 返回结果
* @param stopWatch 耗时统计
*/ */
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) { protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult, StopWatch stopWatch) {
try { try {
// *========数据库日志=========*// // *========数据库日志=========*//
@@ -100,8 +96,8 @@ public class LogAspect {
// 请求的地址 // 请求的地址
String ip = ServletUtils.getClientIP(); String ip = ServletUtils.getClientIP();
operLog.setOperIp(ip); operLog.setOperIp(ip);
operLog.setOperUrl(StringUtils.substring(request.getRequestURI(), 0, 255)); operLog.setOperUrl(limit(request.getRequestURI(), MAX_URL_LENGTH));
operLog.setClientKey(StringUtils.substring(request.getHeader(LoginHelper.CLIENT_KEY), 0, 32)); operLog.setClientKey(limit(request.getHeader(LoginHelper.CLIENT_KEY), MAX_CLIENT_KEY_LENGTH));
LoginUser loginUser = LoginHelper.getLoginUser(); LoginUser loginUser = LoginHelper.getLoginUser();
if (ObjectUtil.isNotNull(loginUser)) { if (ObjectUtil.isNotNull(loginUser)) {
operLog.setOperName(loginUser.getUsername()); operLog.setOperName(loginUser.getUsername());
@@ -118,7 +114,7 @@ public class LogAspect {
if (e != null) { if (e != null) {
operLog.setStatus(BusinessStatus.FAIL.ordinal()); operLog.setStatus(BusinessStatus.FAIL.ordinal());
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 3800)); operLog.setErrorMsg(limit(e.getMessage(), MAX_CONTENT_LENGTH));
} }
// 设置方法名称 // 设置方法名称
String className = joinPoint.getTarget().getClass().getName(); String className = joinPoint.getTarget().getClass().getName();
@@ -129,16 +125,13 @@ public class LogAspect {
// 处理设置注解上的参数 // 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
// 设置消耗时间 // 设置消耗时间
StopWatch stopWatch = KEY_CACHE.get();
stopWatch.stop(); stopWatch.stop();
operLog.setCostTime(stopWatch.getDuration().toMillis()); operLog.setCostTime(stopWatch.getDuration().toMillis());
// 发布事件保存数据库 // 发布事件保存数据库
SpringUtils.context().publishEvent(operLog); SpringUtils.context().publishEvent(operLog);
} catch (Exception exp) { } catch (Exception exp) {
// 记录本地异常日志 // 记录本地异常日志
log.error("异常信息:{}", exp.getMessage()); log.error("记录操作日志异常", exp);
} finally {
KEY_CACHE.remove();
} }
} }
@@ -165,7 +158,7 @@ public class LogAspect {
} }
// 是否需要保存response,参数和值 // 是否需要保存response,参数和值
if (log.isSaveResponseData() && ObjectUtil.isNotNull(jsonResult)) { if (log.isSaveResponseData() && ObjectUtil.isNotNull(jsonResult)) {
operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 3800)); operLog.setJsonResult(limit(JsonUtils.toJsonString(jsonResult), MAX_CONTENT_LENGTH));
} }
} }
@@ -182,11 +175,11 @@ public class LogAspect {
String requestMethod = operLog.getRequestMethod(); String requestMethod = operLog.getRequestMethod();
if (MapUtil.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name())) { if (MapUtil.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name())) {
String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames); String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
operLog.setOperParam(StringUtils.substring(params, 0, 3800)); operLog.setOperParam(limit(params, MAX_CONTENT_LENGTH));
} else { } else {
MapUtil.removeAny(paramsMap, SystemConstants.EXCLUDE_PROPERTIES); MapUtil.removeAny(paramsMap, SystemConstants.EXCLUDE_PROPERTIES);
MapUtil.removeAny(paramsMap, excludeParamNames); MapUtil.removeAny(paramsMap, excludeParamNames);
operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 3800)); operLog.setOperParam(limit(JsonUtils.toJsonString(paramsMap), MAX_CONTENT_LENGTH));
} }
} }
@@ -205,55 +198,111 @@ public class LogAspect {
String[] exclude = ArrayUtil.addAll(excludeParamNames, SystemConstants.EXCLUDE_PROPERTIES); String[] exclude = ArrayUtil.addAll(excludeParamNames, SystemConstants.EXCLUDE_PROPERTIES);
for (Object o : paramsArray) { for (Object o : paramsArray) {
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) { if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
String str = ""; params.add(serializeArg(o, exclude));
if (o instanceof List<?> list) {
List<Dict> list1 = new ArrayList<>();
for (Object obj : list) {
String str1 = JsonUtils.toJsonString(obj);
Dict dict = JsonUtils.parseMap(str1);
if (MapUtil.isNotEmpty(dict)) {
MapUtil.removeAny(dict, exclude);
list1.add(dict);
}
}
str = JsonUtils.toJsonString(list1);
} else {
str = JsonUtils.toJsonString(o);
Dict dict = JsonUtils.parseMap(str);
if (MapUtil.isNotEmpty(dict)) {
MapUtil.removeAny(dict, exclude);
str = JsonUtils.toJsonString(dict);
}
}
params.add(str);
} }
} }
return params.toString(); return params.toString();
} }
/**
* 序列化单个方法参数,并移除排除字段。
*
* @param arg 参数对象
* @param exclude 排除字段名
* @return 参数日志字符串
*/
private String serializeArg(Object arg, String[] exclude) {
if (arg instanceof Collection<?> collection) {
List<Dict> list = new ArrayList<>(collection.size());
for (Object item : collection) {
Dict dict = toFilteredDict(item, exclude);
if (MapUtil.isNotEmpty(dict)) {
list.add(dict);
}
}
return JsonUtils.toJsonString(list);
}
String str = JsonUtils.toJsonString(arg);
Dict dict = JsonUtils.parseMap(str);
if (MapUtil.isNotEmpty(dict)) {
MapUtil.removeAny(dict, exclude);
return JsonUtils.toJsonString(dict);
}
return str;
}
/**
* 将参数转为已排除指定字段的字典。
*
* @param value 参数值
* @param exclude 排除字段名
* @return 已过滤字段的字典
*/
private Dict toFilteredDict(Object value, String[] exclude) {
String str = JsonUtils.toJsonString(value);
Dict dict = JsonUtils.parseMap(str);
if (MapUtil.isNotEmpty(dict)) {
MapUtil.removeAny(dict, exclude);
}
return dict;
}
/**
* 限制日志字段长度。
*
* @param value 原始字符串
* @param maxLength 最大长度
* @return 截断后的字符串
*/
private String limit(String value, int maxLength) {
return StringUtils.substring(value, 0, maxLength);
}
/** /**
* 判断是否需要过滤的对象。 * 判断是否需要过滤的对象。
* *
* @param o 对象信息。 * @param o 对象信息。
* @return 如果是需要过滤的对象,则返回true;否则返回false。 * @return 如果是需要过滤的对象,则返回true;否则返回false。
*/ */
@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o) { public boolean isFilterObject(final Object o) {
Class<?> clazz = o.getClass(); Class<?> clazz = o.getClass();
if (clazz.isArray()) { if (clazz.isArray()) {
return MultipartFile.class.isAssignableFrom(clazz.getComponentType()); if (MultipartFile.class.isAssignableFrom(clazz.getComponentType())) {
return true;
}
int length = Array.getLength(o);
for (int i = 0; i < length; i++) {
if (isFilterValue(Array.get(o, i))) {
return true;
}
}
return false;
} else if (Collection.class.isAssignableFrom(clazz)) { } else if (Collection.class.isAssignableFrom(clazz)) {
Collection collection = (Collection) o; Collection<?> collection = (Collection<?>) o;
for (Object value : collection) { for (Object value : collection) {
return value instanceof MultipartFile; if (isFilterValue(value)) {
return true;
}
} }
} else if (Map.class.isAssignableFrom(clazz)) { } else if (Map.class.isAssignableFrom(clazz)) {
Map map = (Map) o; Map<?, ?> map = (Map<?, ?>) o;
for (Object value : map.values()) { for (Object value : map.values()) {
return value instanceof MultipartFile; if (isFilterValue(value)) {
return true;
}
} }
} }
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse return isFilterValue(o);
|| o instanceof BindingResult; }
/**
* 判断是否为日志参数过滤类型。
*
* @param value 参数值
* @return true 需要过滤 false 不需要过滤
*/
private boolean isFilterValue(Object value) {
return value instanceof MultipartFile || value instanceof HttpServletRequest || value instanceof HttpServletResponse
|| value instanceof BindingResult;
} }
} }