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 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 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; } }