使用SpringBootAOP记录操作日志、异常日志

本文最后更新于:2025年6月25日 晚上

数据库创建两张表:exception_log、operation_log

exception_log.sql

CREATE TABLE `exception_log`  (
  `exc_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '主键Id',
  `exc_req_param` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '异常请求参数',
  `exc_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '异常名称',
  `exc_message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '异常信息',
  `exc_user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作员Id',
  `exc_user_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作员名称',
  `exc_method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '异常方法',
  `exc_uri` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '异常请求地址',
  `exc_ip` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '异常IP',
  `exc_version` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '版本号',
  `exc_create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`exc_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

operation_log.sql

CREATE TABLE `operation_log`  (
  `oper_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '主键ID',
  `oper_modul` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '功能模块',
  `oper_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作类型',
  `oper_desc` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作描述',
  `oper_req_param` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '请求参数',
  `oper_resp_param` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '响应参数',
  `oper_user_id` varbinary(64) NULL DEFAULT NULL COMMENT '操作员Id',
  `oper_user_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作员名称',
  `oper_method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作方法',
  `oper_uri` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作地址',
  `oper_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作IP',
  `oper_create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `oper_version` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '版本号',
  PRIMARY KEY (`oper_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

添加Maven依赖

<!-- SpringAop  -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

创建操作日志注解类:OperLog.java

import java.lang.annotation.*;

/**
 * 操作日志自定义注解
 */
@Target(ElementType.METHOD)             //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME)     //注解在哪个阶段执行
@Documented
public @interface OperLog {
    String operModul() default "";      // 操作模块
    String operType() default "";       // 操作类型
    String operDesc() default "";       // 操作描述
}

创建切面类记录操作日志:OperLogAspect.java

import com.alibaba.fastjson.JSON;
import com.hh.core.log.entity.ExceptionLog;
import com.hh.core.log.entity.OperationLog;
import com.hh.core.log.service.IExceptionLogService;
import com.hh.core.log.service.IOperationLogService;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @author Smile
 * @version 1.0
 * @description: 切面处理类,操作日志异常日志记录处理
 * @date 2022/3/30 10:54
 */
@Aspect
@Component
@Slf4j
public class OperLogAspect {
    @Autowired
    private IOperationLogService logService;
    @Autowired
    private IExceptionLogService exceptionLogService;

    private Method method;
    private String requestMethod;
    private String requestURI;
    private String paramterJson;

    /**
    *   操作版本号
    *   项目启动时从命令行传入,例如:java -jar xxx.war --version=201902
    */
    @Value("${version}")
    private String operVersion;

    @Pointcut("@annotation(com.hh.core.log.OperLog)")     // 设置操作日志切入点 记录操作日志 在注解的位置切入代码
    public void operLogPointCut() {
    }

    @Pointcut("execution (* com.hh.core.controller..*.*(..))")
    public void operExceptionPointCut() {
    }

    /**
    * 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
    * @param joinPoint 切入点
    * @param keys      返回结果
    */
    @AfterReturning(value = "operLogPointCut()", returning = "keys")
    public void saveOperLog(JoinPoint joinPoint, Object keys) {
        getRequestInfo(joinPoint, keys, null);
        OperLog annotation = method.getAnnotation(OperLog.class);

        String operModul = annotation.operModul();
        String operType = annotation.operType();
        String operDesc = annotation.operDesc();

        OperationLog operationLog = new OperationLog();
        operationLog.setOperId(UUID.randomUUID().toString());
        operationLog.setOperModul(operModul);
        operationLog.setOperType(operType);
        operationLog.setOperDesc(operDesc);
        operationLog.setOperUri(requestURI);
        operationLog.setOperMethod(requestMethod);
        operationLog.setOperReqParam(paramterJson);
        operationLog.setOperRespParam(keys.toString());
        operationLog.setOperCreateTime(LocalDateTime.now());
        operationLog.setOperVersion(operVersion);

        logService.save(operationLog);
    }

    /**
    * 异常返回通知,用于拦截异常日志信息 连接点抛出异常后执行
    * @param joinPoint 切入点
    * @param e 异常信息
    */
    @AfterThrowing(value = "operExceptionPointCut()", throwing  = "e")
    public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {
        getRequestInfo(joinPoint, null, e);
        ExceptionLog exceptionLog = new ExceptionLog();
        exceptionLog.setExcId(UUID.randomUUID().toString());
        exceptionLog.setExcUri(requestURI);
        exceptionLog.setExcReqParam(paramterJson);
        exceptionLog.setExcMethod(requestMethod);
        exceptionLog.setExcName(e.getClass().getName());
        exceptionLog.setExcMessage(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()));
        exceptionLog.setExcVersion(operVersion);
        exceptionLog.setExcCreateTime(LocalDateTime.now());
        exceptionLogService.save(exceptionLog);
    }

    private Map<String, String> coverMap(Map<String, String[]> parameterMap) {
        Map rtnMap = new HashMap<String, String>();
        for (String key : parameterMap.keySet()) {
            rtnMap.put(key, parameterMap.get(key)[0]);
        }
        return rtnMap;
    }

    /**
     * 转换异常信息为字符串
     *
     * @param exceptionName    异常名称
     * @param exceptionMessage 异常信息
     * @param elements         堆栈信息
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuffer strbuff = new StringBuffer();
        for (StackTraceElement stet : elements) {
            strbuff.append(stet + "\n");
        }
        String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
        return message;
    }

    /*
    * 获取一些请求和访问的信息,如请求的Uri,参数,请求的方法等
    */
    private void getRequestInfo(JoinPoint joinPoint, Object keys, Throwable e) {
        // 获取RequestAttributes,从而去获取HttpServletRequest
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        HttpServletRequest req = (HttpServletRequest) attributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);

        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        method = signature.getMethod();
        requestMethod = joinPoint.getTarget().getClass().getName() + "." + method.getName();
        requestURI = req.getRequestURI();
        Map<String, String> map = coverMap(req.getParameterMap());
        paramterJson = JSON.toJSONString(map);
    }
}

在请求的controller方法上添加注解:@OperLog(operModul=”xxx”,operType=”xxx”,operDesc=”xxxx”)

测试


使用SpringBootAOP记录操作日志、异常日志
https://codeofhh.cn/2022/03/31/使用SpringBootAOP记录操作日志、异常日志/
作者
hhu
发布于
2022年3月31日
许可协议