You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

161 lines
6.3 KiB

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.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.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
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.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.lang.reflect.Method;
/**
* 操作日志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、用户ID等)
collectRequestContext(operationLogDTO);
//收集方法和注解信息
collectMethodAndAnnotationInfo(joinPoint, operationLogDTO);
//执行业务方法
businessResult = joinPoint.proceed();
//获取用户ID
handleLoginSceneUserId(operationLogDTO, joinPoint, businessResult);
operationLogDTO.setResult(0);
} catch (Exception e) {
operationLogDTO.setResult(1);
operationLogDTO.setException(e.getMessage());
throw e;
} finally {
operationLogDTO.setCostTime(System.currentTimeMillis() - startTime);
saveOperationLogAsync(operationLogDTO);
}
return businessResult;
}
/**
* 收集请求上下文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
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() != null) {
Object principal = authentication.getPrincipal();
try {
Long userId = Long.parseLong(principal.toString());
operationLogDTO.setCreatorId(userId);
} catch (NumberFormatException e) {
log.warn("解析用户ID失败,principal:{}", principal, e);
}
}
}
/**
* 收集方法和注解信息
*/
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<LoginDTO>) businessResult).getData();
if (data != null && data.getToken() != null) {
String userId = jwtUtils.parseToken(data.getToken()).getSubject();
operationLogDTO.setCreatorId(Long.parseLong(userId));
}
} catch (Exception e) {
log.error("解析登录Token获取用户ID失败", e);
}
}
}
/**
* 异步保存操作日志
*/
private void saveOperationLogAsync(OperationLogDTO operationLogDTO) {
try {
// 调用异步保存方法
operationLogApplicationService.saveOperationLogAsync(operationLogDTO);
} catch (Exception e) {
log.error("提交异步保存日志任务失败", e);
}
}
}