|
@@ -0,0 +1,271 @@
|
|
|
|
|
+package com.haha.admin.aspect;
|
|
|
|
|
+
|
|
|
|
|
+import cn.dev33.satoken.stp.StpUtil;
|
|
|
|
|
+import com.alibaba.fastjson2.JSON;
|
|
|
|
|
+import com.haha.admin.utils.IpUtils;
|
|
|
|
|
+import com.haha.common.annotation.Log;
|
|
|
|
|
+import com.haha.entity.OperationLog;
|
|
|
|
|
+import com.haha.service.OperationLogService;
|
|
|
|
|
+import jakarta.servlet.http.HttpServletRequest;
|
|
|
|
|
+import jakarta.servlet.http.HttpServletResponse;
|
|
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.aspectj.lang.JoinPoint;
|
|
|
|
|
+import org.aspectj.lang.annotation.AfterReturning;
|
|
|
|
|
+import org.aspectj.lang.annotation.AfterThrowing;
|
|
|
|
|
+import org.aspectj.lang.annotation.Aspect;
|
|
|
|
|
+import org.aspectj.lang.annotation.Pointcut;
|
|
|
|
|
+import org.aspectj.lang.reflect.MethodSignature;
|
|
|
|
|
+import org.springframework.scheduling.annotation.Async;
|
|
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
|
|
+import org.springframework.web.multipart.MultipartFile;
|
|
|
|
|
+
|
|
|
|
|
+import java.lang.reflect.Method;
|
|
|
|
|
+import java.time.LocalDateTime;
|
|
|
|
|
+import java.util.HashMap;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 操作日志切面
|
|
|
|
|
+ * 拦截带有@Log注解的方法,记录操作日志
|
|
|
|
|
+ */
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@Aspect
|
|
|
|
|
+@Component
|
|
|
|
|
+@RequiredArgsConstructor
|
|
|
|
|
+public class OperationLogAspect {
|
|
|
|
|
+
|
|
|
|
|
+ private final OperationLogService operationLogService;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 记录方法开始时间的ThreadLocal
|
|
|
|
|
+ */
|
|
|
|
|
+ private static final ThreadLocal<Long> START_TIME = new ThreadLocal<>();
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 切点:所有带有@Log注解的方法
|
|
|
|
|
+ */
|
|
|
|
|
+ @Pointcut("@annotation(com.haha.common.annotation.Log)")
|
|
|
|
|
+ public void logPointcut() {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 方法执行后记录日志
|
|
|
|
|
+ */
|
|
|
|
|
+ @AfterReturning(pointcut = "logPointcut()", returning = "result")
|
|
|
|
|
+ public void doAfterReturning(JoinPoint joinPoint, Object result) {
|
|
|
|
|
+ handleLog(joinPoint, null, result);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 方法抛出异常后记录日志
|
|
|
|
|
+ */
|
|
|
|
|
+ @AfterThrowing(pointcut = "logPointcut()", throwing = "e")
|
|
|
|
|
+ public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
|
|
|
|
|
+ handleLog(joinPoint, e, null);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 处理日志记录
|
|
|
|
|
+ */
|
|
|
|
|
+ private void handleLog(JoinPoint joinPoint, Exception e, Object result) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 获取注解信息
|
|
|
|
|
+ MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
|
|
|
|
+ Method method = signature.getMethod();
|
|
|
|
|
+ Log logAnnotation = method.getAnnotation(Log.class);
|
|
|
|
|
+
|
|
|
|
|
+ if (logAnnotation == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 创建日志对象
|
|
|
|
|
+ OperationLog operationLog = new OperationLog();
|
|
|
|
|
+
|
|
|
|
|
+ // 设置基本信息
|
|
|
|
|
+ operationLog.setModule(logAnnotation.module());
|
|
|
|
|
+ operationLog.setOperationType(logAnnotation.operation().getDescription());
|
|
|
|
|
+ operationLog.setSummary(logAnnotation.summary());
|
|
|
|
|
+ operationLog.setCreateTime(LocalDateTime.now());
|
|
|
|
|
+
|
|
|
|
|
+ // 获取请求信息
|
|
|
|
|
+ HttpServletRequest request = getHttpServletRequest();
|
|
|
|
|
+ if (request != null) {
|
|
|
|
|
+ operationLog.setRequestUrl(request.getRequestURI());
|
|
|
|
|
+ operationLog.setRequestMethod(request.getMethod());
|
|
|
|
|
+ operationLog.setIp(IpUtils.getIpAddr(request));
|
|
|
|
|
+ operationLog.setAddress(IpUtils.getIpAddress(request));
|
|
|
|
|
+
|
|
|
|
|
+ // 解析User-Agent
|
|
|
|
|
+ String userAgent = request.getHeader("User-Agent");
|
|
|
|
|
+ if (userAgent != null) {
|
|
|
|
|
+ parseUserAgent(userAgent, operationLog);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 设置请求参数
|
|
|
|
|
+ if (logAnnotation.saveParams()) {
|
|
|
|
|
+ String params = getRequestParams(joinPoint, request);
|
|
|
|
|
+ operationLog.setRequestParams(params);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 设置操作人信息
|
|
|
|
|
+ try {
|
|
|
|
|
+ Object loginId = StpUtil.getLoginIdDefaultNull();
|
|
|
|
|
+ if (loginId != null) {
|
|
|
|
|
+ operationLog.setAdminId(Long.parseLong(loginId.toString()));
|
|
|
|
|
+ // 获取用户名 - 尝试从session或token中获取
|
|
|
|
|
+ Object username = StpUtil.getSession().get("username");
|
|
|
|
|
+ if (username != null) {
|
|
|
|
|
+ operationLog.setAdminName(username.toString());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception ex) {
|
|
|
|
|
+ log.debug("获取登录用户信息失败: {}", ex.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 设置操作状态
|
|
|
|
|
+ if (e != null) {
|
|
|
|
|
+ operationLog.setStatus(OperationLog.STATUS_FAIL);
|
|
|
|
|
+ String errorMsg = e.getMessage();
|
|
|
|
|
+ if (errorMsg != null && errorMsg.length() > 500) {
|
|
|
|
|
+ errorMsg = errorMsg.substring(0, 500);
|
|
|
|
|
+ }
|
|
|
|
|
+ operationLog.setErrorMsg(errorMsg);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ operationLog.setStatus(OperationLog.STATUS_SUCCESS);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 设置响应结果
|
|
|
|
|
+ if (logAnnotation.saveResult() && result != null) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ String resultStr = JSON.toJSONString(result);
|
|
|
|
|
+ if (resultStr.length() > 2000) {
|
|
|
|
|
+ resultStr = resultStr.substring(0, 2000);
|
|
|
|
|
+ }
|
|
|
|
|
+ operationLog.setResponseResult(resultStr);
|
|
|
|
|
+ } catch (Exception ex) {
|
|
|
|
|
+ log.debug("序列化响应结果失败: {}", ex.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 异步保存日志
|
|
|
|
|
+ saveLogAsync(operationLog);
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception ex) {
|
|
|
|
|
+ log.error("记录操作日志异常: {}", ex.getMessage(), ex);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 异步保存日志
|
|
|
|
|
+ */
|
|
|
|
|
+ @Async
|
|
|
|
|
+ public void saveLogAsync(OperationLog operationLog) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ operationLogService.save(operationLog);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("保存操作日志失败: {}", e.getMessage(), e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取HttpServletRequest对象
|
|
|
|
|
+ */
|
|
|
|
|
+ private HttpServletRequest getHttpServletRequest() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ jakarta.servlet.http.HttpServletRequest request =
|
|
|
|
|
+ (jakarta.servlet.http.HttpServletRequest) org.springframework.web.context.request.RequestContextHolder
|
|
|
|
|
+ .currentRequestAttributes()
|
|
|
|
|
+ .resolveReference(org.springframework.web.context.request.RequestAttributes.REFERENCE_REQUEST);
|
|
|
|
|
+ return request;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取请求参数
|
|
|
|
|
+ */
|
|
|
|
|
+ private String getRequestParams(JoinPoint joinPoint, HttpServletRequest request) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ Object[] args = joinPoint.getArgs();
|
|
|
|
|
+ if (args == null || args.length == 0) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> params = new HashMap<>();
|
|
|
|
|
+ String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
|
|
|
|
|
+
|
|
|
|
|
+ for (int i = 0; i < args.length; i++) {
|
|
|
|
|
+ Object arg = args[i];
|
|
|
|
|
+ String paramName = (paramNames != null && i < paramNames.length) ? paramNames[i] : "arg" + i;
|
|
|
|
|
+
|
|
|
|
|
+ // 过滤不需要的参数
|
|
|
|
|
+ if (arg instanceof HttpServletRequest
|
|
|
|
|
+ || arg instanceof HttpServletResponse
|
|
|
|
|
+ || arg instanceof MultipartFile) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ params.put(paramName, arg);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ params.put(paramName, arg.getClass().getName());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String paramsStr = JSON.toJSONString(params);
|
|
|
|
|
+ if (paramsStr.length() > 2000) {
|
|
|
|
|
+ paramsStr = paramsStr.substring(0, 2000);
|
|
|
|
|
+ }
|
|
|
|
|
+ return paramsStr;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.debug("获取请求参数失败: {}", e.getMessage());
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析User-Agent
|
|
|
|
|
+ */
|
|
|
|
|
+ private void parseUserAgent(String userAgent, OperationLog operationLog) {
|
|
|
|
|
+ if (userAgent == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ userAgent = userAgent.toLowerCase();
|
|
|
|
|
+
|
|
|
|
|
+ // 解析操作系统
|
|
|
|
|
+ if (userAgent.contains("windows")) {
|
|
|
|
|
+ operationLog.setOs("Windows");
|
|
|
|
|
+ } else if (userAgent.contains("mac")) {
|
|
|
|
|
+ operationLog.setOs("Mac OS");
|
|
|
|
|
+ } else if (userAgent.contains("linux")) {
|
|
|
|
|
+ operationLog.setOs("Linux");
|
|
|
|
|
+ } else if (userAgent.contains("android")) {
|
|
|
|
|
+ operationLog.setOs("Android");
|
|
|
|
|
+ } else if (userAgent.contains("iphone") || userAgent.contains("ipad")) {
|
|
|
|
|
+ operationLog.setOs("iOS");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ operationLog.setOs("Unknown");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 解析浏览器
|
|
|
|
|
+ if (userAgent.contains("edge")) {
|
|
|
|
|
+ operationLog.setBrowser("Edge");
|
|
|
|
|
+ } else if (userAgent.contains("chrome")) {
|
|
|
|
|
+ operationLog.setBrowser("Chrome");
|
|
|
|
|
+ } else if (userAgent.contains("firefox")) {
|
|
|
|
|
+ operationLog.setBrowser("Firefox");
|
|
|
|
|
+ } else if (userAgent.contains("safari") && !userAgent.contains("chrome")) {
|
|
|
|
|
+ operationLog.setBrowser("Safari");
|
|
|
|
|
+ } else if (userAgent.contains("opera") || userAgent.contains("opr")) {
|
|
|
|
|
+ operationLog.setBrowser("Opera");
|
|
|
|
|
+ } else if (userAgent.contains("msie") || userAgent.contains("trident")) {
|
|
|
|
|
+ operationLog.setBrowser("IE");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ operationLog.setBrowser("Unknown");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|