This commit is contained in:
2026-08-14 12:20:52 +08:00
parent ce0812211c
commit afd7c9d704
17 changed files with 1641 additions and 0 deletions
@@ -0,0 +1,147 @@
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.UserContext;
import lombok.extern.slf4j.Slf4j;
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;
@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(200);
fillResponse(entity, result, DEFAULT_MAX_RESPONSE_SIZE);
return result;
} catch (Throwable e) {
entity.setStatusCode(500);
entity.setErrorMsg(truncate(e.getMessage(), 2000));
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());
e.setOperationType(OperationLogResolver.resolveOperationType(httpMethod, uri, methodName));
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);
}
}
private HttpServletRequest getRequest() {
ServletRequestAttributes a = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return a != null ? a.getRequest() : null;
}
private String truncate(String s, int max) {
return s != null && s.length() > max ? s.substring(0, max) : s;
}
}