package com.project.operation.aop; import com.project.base.domain.result.Result; import com.project.ding.domain.dto.LoginDTO; import com.project.ding.utils.JwtUtils; import com.project.ding.utils.SecurityUtils; import com.project.operation.annotation.OperationLog; import com.project.operation.application.impl.OperationLogApplicationService; import com.project.operation.domain.dto.OperationLogDTO; import com.project.operation.domain.enums.ModuleEnum; import com.project.operation.domain.service.impl.description.DescriptionManager; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import java.lang.reflect.Method; import java.util.concurrent.CompletableFuture; /** * 操作日志AOP切面 */ @Slf4j @Aspect @Component @RequiredArgsConstructor public class OperationLogAspect { @Autowired private JwtUtils jwtUtils; @Autowired private OperationLogApplicationService operationLogApplicationService; @Autowired private DescriptionManager descriptionManager; // 定义切点,拦截所有添加@OperationLog注解的方法 @Pointcut("@annotation(com.project.operation.annotation.OperationLog)") public void pointcutOperationLog() {} // 环绕通知,在方法执行前后拦截,收集日志(核心改造) @Around("pointcutOperationLog()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { //初始化日志DTO OperationLogDTO operationLogDTO = new OperationLogDTO(); long startTime = System.currentTimeMillis(); Object businessResult = null; try { //收集请求上下文(IP等,不依赖业务结果) collectRequestContext(operationLogDTO); //执行业务方法 businessResult = joinPoint.proceed(); operationLogDTO.setResult(0); } catch (Exception e) { operationLogDTO.setResult(1); operationLogDTO.setException(e.getMessage()); throw e; } finally { operationLogDTO.setCostTime(System.currentTimeMillis() - startTime); // 异步提交后续处理(策略+用户ID+保存),完全不阻塞主线程 submitLogProcessingAsync(joinPoint, operationLogDTO, businessResult); } return businessResult; } /** * 异步提交日志处理(策略信息收集 + 用户ID解析 + 保存) */ private void submitLogProcessingAsync(ProceedingJoinPoint joinPoint, OperationLogDTO operationLogDTO, Object businessResult) { CompletableFuture.runAsync(() -> { try { collectMethodAndAnnotationInfo(joinPoint, operationLogDTO); handleLoginSceneUserId(operationLogDTO, joinPoint, businessResult); } catch (Exception e) { log.error("异步处理日志信息失败", e); } saveOperationLogAsync(operationLogDTO); }); } /** * 收集请求上下文(IP、用户ID等) */ private void collectRequestContext(OperationLogDTO operationLogDTO) { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (requestAttributes == null) { return; } HttpServletRequest request = requestAttributes.getRequest(); // 收集客户端IP operationLogDTO.setClientIp(request.getRemoteAddr()); // 收集登录用户ID String userId = SecurityUtils.getUserId(); if (StringUtils.isNotBlank(userId)) { operationLogDTO.setCreatorId(userId); } } /** * 收集方法和注解信息 */ private void collectMethodAndAnnotationInfo(ProceedingJoinPoint joinPoint, OperationLogDTO operationLogDTO) throws Exception { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); // 方法全路径:包名+类名+方法名 operationLogDTO.setMethod(method.getDeclaringClass().getName() + "." + method.getName()); // 获取注解信息 OperationLog annotation = method.getAnnotation(OperationLog.class); operationLogDTO.setModule(annotation.module()); ModuleEnum moduleEnum = ModuleEnum.findByValue(annotation.module()); if (moduleEnum != null) { descriptionManager.process(moduleEnum.name(), joinPoint.getArgs(), operationLogDTO, method.getName()); } else { operationLogDTO.setAction(annotation.action()); operationLogDTO.setDescription(annotation.description()); } } /** * 处理登录场景的用户ID(从JWT Token解析) */ private void handleLoginSceneUserId(OperationLogDTO operationLogDTO, ProceedingJoinPoint joinPoint, Object businessResult) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); String methodFullName = signature.getMethod().getDeclaringClass().getName() + "." + signature.getMethod().getName(); // 仅登录方法处理JWT解析 if (methodFullName.contains("login") && businessResult instanceof Result) { try { LoginDTO data = ((Result) businessResult).getData(); if (data != null && data.getToken() != null) { String userId = jwtUtils.parseToken(data.getToken()).getSubject(); operationLogDTO.setCreatorId(userId); } } catch (Exception e) { log.error("解析登录Token获取用户ID失败", e); } } } /** * 异步保存操作日志 */ private void saveOperationLogAsync(OperationLogDTO operationLogDTO) { try { // 调用异步保存方法 operationLogApplicationService.saveOperationLogAsync(operationLogDTO); } catch (Exception e) { log.error("提交异步保存日志任务失败", e); } } }