Spring Boot + MyBatis-Plus实现操作日志记录

创建数据库表

sql 复制代码
CREATE TABLE `sys_operation_log` (
  `log_id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志ID',
  `operation_type` varchar(20) NOT NULL COMMENT '操作类型',
  `operation_module` varchar(50) NOT NULL COMMENT '操作模块',
  `operation_desc` varchar(200) DEFAULT NULL COMMENT '操作描述',
  `request_method` varchar(10) DEFAULT NULL COMMENT '请求方法',
  `request_url` varchar(500) DEFAULT NULL COMMENT '请求URL',
  `request_params` text DEFAULT NULL COMMENT '请求参数',
  `response_result` text DEFAULT NULL COMMENT '响应结果',
  `user_id` varchar(36) DEFAULT NULL COMMENT '操作用户ID',
  `user_name` varchar(50) DEFAULT NULL COMMENT '操作用户名',
  `user_ip` varchar(50) DEFAULT NULL COMMENT '用户IP',
  `user_agent` varchar(500) DEFAULT NULL COMMENT '用户代理',
  `operation_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
  `execution_time` bigint DEFAULT NULL COMMENT '执行耗时(ms)',
  `status` tinyint DEFAULT 1 COMMENT '操作状态(0失败 1成功)',
  `error_msg` text DEFAULT NULL COMMENT '错误信息',
  PRIMARY KEY (`log_id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_operation_time` (`operation_time`),
  KEY `idx_module_type` (`operation_module`, `operation_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统操作日志表';

添加pom.xml依赖

XML 复制代码
<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- MyBatis-Plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>最新版本</version>
    </dependency>

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

    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

创建实体类

java 复制代码
import com.baomidou.mybatisplus.annotation.*;

import java.util.Date;

@Data
@TableName("sys_operation_log")
public class SysOperationLog {
    @TableId(value = "log_id", type = IdType.AUTO)
    private Long logId;
    
    private String operationType;
    private String operationModule;
    private String operationDesc;
    private String requestMethod;
    private String requestUrl;
    private String requestParams;
    private String responseResult;
    private String userId;
    private String userName;
    private String userIp;
    private String userAgent;
    
    @TableField(fill = FieldFill.INSERT)
    private Date operationTime;
    
    private Long executionTime;
    private Integer status;
    private String errorMsg;
}

添加mapper

java 复制代码
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface SysOperationLogMapper extends BaseMapper<SysOperationLog> {
}

创建自定义注解

java 复制代码
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Loggable {
    String operationType() default "";
    String module() default "";
    String description() default "";
}

创建AOP切面

java 复制代码
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;

@Aspect
@Component
@Slf4j
public class OperationLogAspect {

    private final SysOperationLogMapper logMapper;
    private final ObjectMapper objectMapper;

    public OperationLogAspect(SysOperationLogMapper logMapper, ObjectMapper objectMapper) {
        this.logMapper = logMapper;
        this.objectMapper = objectMapper;
    }

    @Around("@annotation(com.yourpackage.annotation.Loggable)")
    public Object logOperation(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        SysOperationLog operationLog = new SysOperationLog();
        Object result = null;
        try {
            // 获取注解信息
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            Loggable loggable = method.getAnnotation(Loggable.class);

            // 填充日志基本信息
            operationLog.setOperationType(loggable.operationType());
            operationLog.setOperationModule(loggable.module());
            operationLog.setOperationDesc(loggable.description());
            operationLog.setOperationTime(new Date());

            // 获取请求信息
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                HttpServletRequest request = attributes.getRequest();
                operationLog.setRequestMethod(request.getMethod());
                operationLog.setRequestUrl(request.getRequestURI());
                operationLog.setUserIp(request.getRemoteAddr());
                operationLog.setUserAgent(request.getHeader("User-Agent"));
            }

            // 获取用户信息(根据你的系统实现)
            operationLog.setUserId("当前用户ID");
            operationLog.setUserName("当前用户名");

            // 获取请求参数
            Object[] args = joinPoint.getArgs();
            operationLog.setRequestParams(objectMapper.writeValueAsString(args));

            // 执行目标方法
            result = joinPoint.proceed();

            // 记录响应结果
            operationLog.setResponseResult(objectMapper.writeValueAsString(result));
            operationLog.setStatus(1);
        } catch (Exception e) {
            operationLog.setStatus(0);
            operationLog.setErrorMsg(e.getMessage());
            throw e;
        } finally {
            long endTime = System.currentTimeMillis();
            operationLog.setExecutionTime(endTime - startTime);
            
            // 异步保存日志(建议使用异步方式)
            logMapper.insert(operationLog);
        }
        return result;
    }
}

这是一个获取用户id和用户名的示例方法

java 复制代码
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

@Component
public class UserInfoUtils {
    // 根据你的安全框架实现获取用户信息
    public String getCurrentUserId() {
        // 示例:从Spring Security获取
        return SecurityContextHolder.getContext().getAuthentication().getName();
    }
    
    public String getCurrentUsername() {
        // 实现获取用户名逻辑
        return "用户名";
    }
}

使用

java 复制代码
@RestController
@RequestMapping("/api")
public class DemoController {

    @Loggable(operationType = "QUERY", module = "用户管理", description = "查询用户列表")
    @GetMapping("/users")
    public List<User> getUsers() {
        // 业务逻辑
    }
}

为了防止数据库越来越大,可以删除一年前的数据,我们可以通过数据库层面实现

  1. 创建存储过程
sql 复制代码
DELIMITER $$

CREATE PROCEDURE clean_old_logs()
BEGIN
    DECLARE batch_size INT DEFAULT 1000;
    DECLARE deleted_rows INT DEFAULT 1;
    
    WHILE deleted_rows > 0 DO
        DELETE FROM sys_operation_log 
        WHERE operation_time < NOW() - INTERVAL 1 YEAR 
        LIMIT batch_size;
        
        SET deleted_rows = ROW_COUNT();
        COMMIT;
        DO SLEEP(0.5); -- 防止锁表太久
    END WHILE;
END$$

DELIMITER ;
  1. 创建定时事件,一天一次
sql 复制代码
CREATE EVENT execute_clean_procedure
ON SCHEDULE EVERY 1 DAY
STARTS CURRENT_TIMESTAMP + INTERVAL 1 DAY
DO
BEGIN
    CALL clean_old_logs();
END
相关推荐
diving deep2 小时前
springboot集成日志配置文件
java·spring boot·后端·logback
蟹至之2 小时前
【Java】异常的初步认识
java·开发语言·类和对象·异常
广西千灵通网络科技有限公司3 小时前
基于Java的话剧购票小程序【附源码】
java·小程序·apache
苏小瀚3 小时前
[Java] idea的调试介绍
java·intellij-idea
JWenzz13 小时前
Redis删除策略
java·数据库·redis·缓存
幻听嵩的留香3 小时前
javaEE课程项目-壁纸管理系统
java·java-ee
liubo666_3 小时前
SpringMVC(结合源码浅析工作流程)
java·spring·springmvc
speop4 小时前
TASK05【Datawhale 组队学习】系统评估与优化
android·java·学习
星沁城4 小时前
108. 将有序数组转换为二叉搜索树
java·数据结构·leetcode
在未来等你4 小时前
互联网大厂Java求职面试:云原生架构与AI应用集成解决方案
java·spring cloud·微服务·ai·云原生·kubernetes·大模型