SpringBoot+AOP+自定义注解实现系统操作日志

前言

一个系统必备可少的就是用户的操作日志了,通过操作日志可以解决很多问题

实现

数据库设计

/*
 Navicat Premium Data Transfer

 Source Server         : MySQL 5.5
 Source Server Type    : MySQL
 Source Server Version : 50554 (5.5.54)
 Source Host           : localhost:3306
 Source Schema         : tgadmin

 Target Server Type    : MySQL
 Target Server Version : 50554 (5.5.54)
 File Encoding         : 65001

 Date: 08/08/2023 23:34:49
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_opera_log
-- ----------------------------
DROP TABLE IF EXISTS `sys_opera_log`;
CREATE TABLE `sys_opera_log`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `module` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求模块',
  `operation` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求动作',
  `opera_user` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作人',
  `ip` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求ip',
  `location` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地理位置',
  `path` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求地址',
  `method` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求方式',
  `class_method` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求类方法',
  `params` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求参数',
  `result` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '返回结果',
  `status` tinyint(1) NULL DEFAULT NULL COMMENT '请求状态 ',
  `code` int(20) NULL DEFAULT NULL COMMENT '状态码',
  `msg` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '提示信息',
  `cost_time` bigint(20) NULL DEFAULT NULL COMMENT '消耗时间',
  `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 330 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

自定义注解

package com.tg.admin.common.annotation;

import java.lang.annotation.*;

/**
 * @Program: tg-admin
 * @ClassName SysLog
 * @Author: liutao
 * @Description: 系统日志
 * @Create: 2023-08-04 20:36
 * @Version 1.0
 **/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysOperaLog {
    String module() default " ";
    String path() default " ";
    String operation() default " ";
}

aop切面

package com.tg.admin.common.aop;

import com.alibaba.fastjson.JSON;
import com.tg.admin.common.Result;
import com.tg.admin.common.annotation.SysOperaLog;
import com.tg.admin.common.exception.ServiceException;
import com.tg.admin.entity.OperaLog;
import com.tg.admin.service.OperaLogService;
import com.tg.admin.utils.HttpContextUtil;
import com.tg.admin.utils.IpUtils;
import com.tg.admin.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @Program: tg-admin
 * @ClassName SysLog
 * @Author: liutao
 * @Description: 系统日志切面
 * @Create: 2023-08-04 20:39
 * @Version 1.0
 **/
@Slf4j
@Aspect
@Component
public class SysLogAspect {
    private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");
    private OperaLog operaLog;
    @Autowired
    private OperaLogService operaLogService;

    /***
     * @MethodName: log
     * @description: 定义一个切点
     * @Author: LiuTao
     * @UpdateTime: 2023/8/4 20:46
     * @Return: void
     **/
    @Pointcut("@annotation(com.tg.admin.common.annotation.SysOperaLog)")
    public void logPointCut() {

    }

    @Before("logPointCut()")
    public void start() {
        TIME_THREADLOCAL.set(System.currentTimeMillis());
    }

    @Around("logPointCut()")
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        operaLog = new OperaLog();
        operaLog.setOperaUser(JwtUtil.getCurrentUser().getUsername());
        // 获取请求对象
        HttpServletRequest request = HttpContextUtil.getRequest();
        // 记录日志
        log.info("===============系统操作日志===============");
        Signature signature = pjp.getSignature();
        // 请求的类
        String className = pjp.getTarget().getClass().getName();
        String methodName = signature.getName();
        String classMethod = className + "." + methodName + "()";
        Object[] args = pjp.getArgs();
        String[] parameterNames = ((MethodSignature) signature).getParameterNames();
        Map<Object,Object> map = new HashMap<Object,Object>();
        log.info("请求方式:{}", request.getMethod());
        operaLog.setMethod(request.getMethod());
        log.info("请求ip:{}", request.getRemoteAddr());
        operaLog.setIp(IpUtils.getIpAddr());
        log.info("请求类方法:{}", classMethod);
        operaLog.setClassMethod(classMethod);
        if (ObjectUtils.isNotEmpty(args)){
            for (int i = 0; i < args.length;i++) {
                map.put(JSON.toJSON(parameterNames[i]),JSON.toJSON(args[i]));
            }
            if (Objects.equals(request.getMethod(), "POST")){
                log.info("请求参数:{}", map.get("user"));
                operaLog.setParams(JSON.toJSONString(map.get("user")));
            }else {
                log.info("请求参数:{}", JSON.toJSONString(map));
                operaLog.setParams(JSON.toJSONString(map));
            }
        }
        MethodSignature handlerMethod = (MethodSignature) signature;
        Method method = handlerMethod.getMethod();
        if (method.isAnnotationPresent(SysOperaLog.class)) {
            SysOperaLog sysOperaLog = method.getAnnotation(SysOperaLog.class);
            operaLog.setModule(sysOperaLog.module());
            operaLog.setOperation(sysOperaLog.operation());
            operaLog.setPath(sysOperaLog.path());
        }
        return pjp.proceed();
    }

    @AfterThrowing(throwing = "ex",pointcut = "logPointCut()")
    public void throwEx(JoinPoint joinPoint, ServiceException ex) {
        log.info("异常:{}",ex);
        long time = System.currentTimeMillis() - TIME_THREADLOCAL.get();
        operaLog.setResult(JSON.toJSONString(ex));
        operaLog.setCode(ex.getCode());
        operaLog.setMsg(ex.getMessage());
        operaLog.setCostTime(time);
        boolean isSaved = operaLogService.save(operaLog);
        if (isSaved) {
            TIME_THREADLOCAL.remove();
        }
    }

    @AfterReturning(returning = "response", pointcut = "logPointCut()")
    public void end(Object response) {
        long time = System.currentTimeMillis() - TIME_THREADLOCAL.get();
        Result res = (Result) response;
        if (res.getCode() == 200) {
            operaLog.setStatus(true);
        }
        operaLog.setResult(JSON.toJSONString(response));
        operaLog.setCode(res.getCode());
        operaLog.setMsg(res.getMsg());
        operaLog.setCostTime(time);
        boolean isSaved = operaLogService.save(operaLog);
        if (isSaved) {
            TIME_THREADLOCAL.remove();
        }
    }
}

使用 在需要的web接口层,加上注解即可

@SysOperaLog(module = "所属模块", path = "请求地址", operation = "操作内容")

效果图

阅读剩余
THE END