Files
UserclassService/src/main/java/com/yunhai/bschclassmanage/aspect/SysOperationLogAspect.java
T
2026-08-21 09:47:45 +08:00

196 lines
8.2 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.yunhai.bschclassmanage.aspect;
import com.alibaba.fastjson.JSON;
import com.yunhai.bschclassmanage.entity.SysOperationLog;
import com.yunhai.bschclassmanage.service.ISysOperationLogService;
import com.yunhai.bschclassmanage.utils.GzipUtil;
import com.yunhai.bschclassmanage.utils.OperationLogResolver;
import com.yunhai.bschclassmanage.utils.OtherUtil;
import com.yunhai.bschclassmanage.utils.SensitiveDataUtil;
import com.yunhai.bschclassmanage.utils.SyncLogRetryContext;
import com.yunhai.bschclassmanage.utils.UserContext;
import com.yunhai.platform.apistd.ApiResponse;
import com.yunhai.platform.utils.exception.DException;
import com.yunhai.platform.utils.exception.ErrorCode;
import com.yunhai.platform.utils.exception.IErrorCode;
import com.yunhai.platform.utils.http.ApiResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* 系统审计日志切面(双轨之审计轨)
* - 全量拦截 controller 包所有方法,不依赖注解
* - 排除日志查询控制器自身(防递归)
* - 与业务日志切面 OperationLogAspect(Order 2) 物理隔离,互不影响
*/
@Slf4j
@Aspect
@Component
@Order(3)
public class SysOperationLogAspect {
/** 排除的控制器(日志查询自身,防递归刷屏) */
private static final List<String> EXCLUDE_CONTROLLERS = Arrays.asList(
"com.yunhai.bschclassmanage.controller.api.LogController", // 业务日志查询
"com.yunhai.bschclassmanage.controller.api.SysOperationLogController" // 审计日志查询
);
private static final int DEFAULT_MAX_RESPONSE_SIZE = 10240;
private static final int MAX_ERROR_MSG = 8000;
@Autowired
private ISysOperationLogService sysOperationLogService;
@Around("execution(* com.yunhai.bschclassmanage.controller..*.*(..))")
public Object around(ProceedingJoinPoint point) throws Throwable {
// 1. 排除日志控制器
String declType = point.getSignature().getDeclaringTypeName();
if (EXCLUDE_CONTROLLERS.contains(declType)) {
return point.proceed();
}
HttpServletRequest request = getRequest();
String httpMethod = request != null ? request.getMethod() : "";
long start = System.currentTimeMillis();
SysOperationLog entity = buildEntity(point, request, httpMethod);
Object result = null;
try {
result = point.proceed();
entity.setStatusCode(resolveSuccessCode(result));
fillResponse(entity, result, DEFAULT_MAX_RESPONSE_SIZE);
return result;
} catch (Throwable e) {
entity.setStatusCode(resolveErrorCode(e));
entity.setErrorMsg(buildErrorMsg(e));
throw e;
} finally {
entity.setCostTime(System.currentTimeMillis() - start);
sysOperationLogService.saveAsync(entity);
}
}
private SysOperationLog buildEntity(ProceedingJoinPoint point, HttpServletRequest request, String httpMethod) {
SysOperationLog e = new SysOperationLog();
e.setCreateTime(LocalDateTime.now());
// 用户信息(来自 UserContext,由 ControllerUserContextAspect Order1 已填充)
e.setUserId(UserContext.getUserId());
e.setUserName(StringUtils.hasText(UserContext.getUserName()) ? UserContext.getUserName() : "anonymous");
e.setSchId(UserContext.getSchId());
e.setRequestIp(StringUtils.hasText(UserContext.getIp()) ? UserContext.getIp() : OtherUtil.getClientIp());
String uri = request != null ? request.getRequestURI() : "";
String methodName = point.getSignature().getName();
e.setRequestUri(uri);
e.setRequestMethod(httpMethod);
e.setClassMethod(point.getTarget().getClass().getName() + "." + methodName);
e.setLogLevel(OperationLogResolver.resolveLevel(httpMethod, uri, methodName).getCode());
String opType = OperationLogResolver.resolveOperationType(httpMethod, uri, methodName);
if (SyncLogRetryContext.isRetry()) {
// 重试重放时,在操作类型上追加"(重试)"标记,便于与首次调用区分
opType = (opType == null ? "" : opType) + "(重试)";
}
e.setOperationType(opType);
if (request != null) {
e.setUserAgent(request.getHeader("User-Agent"));
e.setRequestParams(SensitiveDataUtil.mask(buildRequestParams(point, request)));
}
return e;
}
private String buildRequestParams(ProceedingJoinPoint point, HttpServletRequest request) {
try {
if (request != null && "GET".equalsIgnoreCase(request.getMethod())) {
String qs = request.getQueryString();
if (StringUtils.hasText(qs)) return qs;
}
Object[] args = point.getArgs();
if (args == null || args.length == 0) return "";
List<Object> filtered = new ArrayList<>();
for (Object arg : args) {
if (arg == null) continue;
if (arg instanceof HttpServletRequest || arg instanceof HttpServletResponse
|| arg instanceof HttpSession) continue;
filtered.add(arg);
}
return filtered.isEmpty() ? "" : JSON.toJSONString(filtered);
} catch (Exception ex) {
return "";
}
}
private void fillResponse(SysOperationLog e, Object result, int max) {
if (result == null) return;
try {
String resp = SensitiveDataUtil.mask(JSON.toJSONString(result));
if (resp.length() > max) resp = resp.substring(0, max) + "...[截断]";
e.setResponseData(GzipUtil.compress(resp));
} catch (Exception ex) {
log.warn("审计日志响应序列化失败, uri={}", e.getRequestUri(), ex);
}
}
/**
* 成功路径:提取全局返回对象的业务状态码。
* - Controller 直接返回 ApiResponse / ApiResult 时取其 code;
* - 其余(普通对象 / void / null)由平台 ApiResponseAdvice 统一包装为 success,code = SUCCESS(0)。
*/
private int resolveSuccessCode(Object result) {
if (result instanceof ApiResponse) {
return ((ApiResponse<?>) result).getCode();
}
if (result instanceof ApiResult) {
return ((ApiResult<?>) result).getCode();
}
return ErrorCode.SUCCESS.getCode();
}
/**
* 异常路径:从业务异常提取错误码,兜底 INTERNAL_SERVER_ERROR。
* DException.getCode() 返回 IErrorCode(可能为 null),需二次取 int code。
*/
private int resolveErrorCode(Throwable e) {
if (e instanceof DException) {
IErrorCode ec = ((DException) e).getCode();
if (ec != null) {
return ec.getCode();
}
}
return ErrorCode.INTERNAL_SERVER_ERROR.getCode();
}
private HttpServletRequest getRequest() {
ServletRequestAttributes a = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return a != null ? a.getRequest() : null;
}
private String buildErrorMsg(Throwable e) {
String msg = e.getMessage();
String head = (msg == null || msg.trim().isEmpty()) ? e.getClass().getName() : msg;
return truncate(head + System.lineSeparator() + ExceptionUtils.getStackTrace(e), MAX_ERROR_MSG);
}
private String truncate(String s, int max) {
return s != null && s.length() > max ? s.substring(0, max) : s;
}
}