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

news2025/6/1 7:22:05

创建数据库表

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依赖

<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>

创建实体类

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

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface SysOperationLogMapper extends BaseMapper<SysOperationLog> {
}

创建自定义注解

import java.lang.annotation.*;

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

创建AOP切面

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和用户名的示例方法

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 "用户名";
    }
}

使用

@RestController
@RequestMapping("/api")
public class DemoController {

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

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

1. 创建存储过程

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 ;

2. 创建定时事件,一天一次

CREATE EVENT execute_clean_procedure
ON SCHEDULE EVERY 1 DAY
STARTS CURRENT_TIMESTAMP + INTERVAL 1 DAY
DO
BEGIN
    CALL clean_old_logs();
END

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2392249.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

openEuler安装MySql8(tar包模式)

操作系统版本&#xff1a; openEuler release 22.03 (LTS-SP4) MySql版本&#xff1a; 下载地址&#xff1a; https://dev.mysql.com/downloads/mysql/ 准备安装&#xff1a; 关闭防火墙&#xff1a; 停止防火墙 #systemctl stop firewalld.service 关闭防火墙 #systemc…

基于python,html,flask,echart,ids/ips,VMware,mysql,在线sdn防御ddos系统

详细视频:【基于python,html,flask,echart,ids/ips,VMware,mysql,在线sdn防御ddos系统-哔哩哔哩】 https://b23.tv/azUqQXe

Git:现代软件开发的基石——原理、实践与行业智慧·优雅草卓伊凡

Git&#xff1a;现代软件开发的基石——原理、实践与行业智慧优雅草卓伊凡 一、Git的本质与核心原理 1. 技术定义 Git是一个分布式版本控制系统&#xff08;DVCS&#xff09;&#xff0c;由Linus Torvalds在2005年为管理Linux内核开发而创建。其核心是通过快照&#xff08;Sna…

NLua性能对比:C#注册函数 vs 纯Lua实现

引言 在NLua开发中&#xff0c;我们常面临一个重要选择&#xff1a;将C#函数注册到Lua环境调用&#xff0c;还是直接在Lua中实现逻辑&#xff1f; 直觉告诉我们&#xff0c;C#作为编译型语言性能更高&#xff0c;但跨语言调用的开销是否会影响整体性能&#xff1f;本文通过基准…

【计算机网络】第2章:应用层—Web and HTTP

目录 一、Web 与 HTTP 二、总结 &#xff08;一&#xff09;Web 的定义与功能 &#xff08;二&#xff09;HTTP 协议的定义与功能 &#xff08;三&#xff09;HTTP 协议的核心机制 1. HTTP 请求与响应流程 2. HTTP 的连接类型 3. HTTP 的状态码 &#xff08;四&#xf…

数字孪生技术赋能西门子安贝格工厂:全球智能制造标杆的数字化重构实践

在工业4.0浪潮席卷全球制造业的当下&#xff0c;西门子安贝格电子制造工厂&#xff08;Electronic Works Amberg, EWA&#xff09;凭借数字孪生技术的深度应用&#xff0c;构建起全球制造业数字化转型的典范。这座位于德国巴伐利亚州的“未来工厂”&#xff0c;通过虚实融合的数…

【图像处理基石】立体匹配的经典算法有哪些?

1. 立体匹配的经典算法有哪些&#xff1f; 立体匹配是计算机视觉中从双目图像中获取深度信息的关键技术&#xff0c;其经典算法按技术路线可分为以下几类&#xff0c;每类包含若干代表性方法&#xff1a; 1.1 基于区域的匹配算法&#xff08;Local Methods&#xff09; 通过…

day12 leetcode-hot100-19(矩阵2)

54. 螺旋矩阵 - 力扣&#xff08;LeetCode&#xff09; 1.模拟路径 思路&#xff1a;模拟旋转的路径 &#xff08;1&#xff09;设计上下左右方向控制器以及边界。比如zy1向右&#xff0c;zy-1向左&#xff1b;sx1向上&#xff0c;sx-1向下。上边界0&#xff0c;下边界hang-1&a…

密钥管理系统在存储加密场景中的深度实践:以TDE透明加密守护文件服务器安全

引言&#xff1a;数据泄露阴影下的存储加密革命 在数字化转型的深水区&#xff0c;企业数据资产正面临前所未有的安全挑战。据IBM《2025年数据泄露成本报告》显示&#xff0c;全球单次数据泄露事件平均成本已达465万美元&#xff0c;其中存储介质丢失或被盗导致的损失占比高达…

webpack打包基本配置

需要的文件 具体代码 webpack.config.js const path require(path);const HTMLWebpackPlugin require(html-webpack-plugin);const {CleanWebpackPlugin} require(clean-webpack-plugin); module.exports {mode: production,entry: "./src/index.ts",output: {…

酷派Cool20/20S/30/40手机安装Play商店-谷歌三件套-GMS方法

酷派Cool系列主打低端市场&#xff0c;系统无任何GMS程序&#xff0c;也不支持直接开启或者安装谷歌服务等功能&#xff0c;对于国内部分经常使用谷歌服务商店的小伙伴非常不友好。涉及机型有酷派Cool20/Cool20S /30/40/50/60等旗下多个设备。好在这些机型运行的系统都是安卓11…

LabVIEW旋转机械智能监测诊断系统

采用 LabVIEW 开发旋转机械智能监测与故障诊断系统&#xff0c;通过集品牌硬件与先进信号处理技术&#xff0c;实现旋转机械振动信号的实时采集、分析及故障预警。系统突破传统监测手段的局限性&#xff0c;解决了复杂工业环境下信号干扰强、故障特征提取难等问题&#xff0c;为…

【芯片设计中的跨时钟域信号处理:攻克亚稳态的终极指南】

在当今芯片设计中&#xff0c;多时钟域已成为常态。从手机SoC到航天级FPGA&#xff0c;不同功能模块运行在各自的时钟频率下&#xff0c;时钟域间的信号交互如同“语言不通”的对话&#xff0c;稍有不慎就会引发亚稳态、数据丢失等问题。这些隐患轻则导致功能异常&#xff0c;重…

接地气的方式认识JVM(一)

最近在学jvm&#xff0c;浮于表面的学了之后&#xff0c;发现jvm并没有我想象中的那么神秘&#xff0c;这篇文章将会用接地气的方式来说一说这些jvm的相关概念以及名词解释。 带着下面两个问题来阅读 认识了解JVM大致有什么在代码运行时的都在背后做了什么 JVM是个啥&#xf…

JAVA:Kafka 消息可靠性详解与实践样例

🧱 1、简述 Apache Kafka 是高吞吐、可扩展的流处理平台,在分布式架构中广泛应用于日志采集、事件驱动和微服务解耦场景。但在使用过程中,消息是否会丢?何时丢?如何防止丢? 是很多开发者关心的问题。 Kafka 提供了一套完整的机制来保障消息从生产者 ➜ Broker ➜ 消费…

Electron 桌面程序读取dll动态库

序幕&#xff1a;被GFW狙击的第一次构建 当我在工位上输入npm install electron时&#xff0c;控制台跳出的红色警报如同数字柏林墙上的一道弹痕&#xff1a; Error: connect ETIMEDOUT 104.20.22.46:443 网络问题不用愁&#xff0c;请移步我的另外文章进行配置&#xff1a;…

HTTP 与 HTTPS 深度解析:原理、实践与大型项目应用

1. HTTP 与 HTTPS 基础概念 1.1 HTTP&#xff08;超文本传输协议&#xff09; 定义&#xff1a;应用层协议&#xff0c;基于 TCP/IP 通信&#xff0c;默认端口 80 特点&#xff1a; 无状态协议&#xff08;需 Cookie/Session 维护状态&#xff09; 明文传输&#xff08;易被…

API Gateway CLI 实操入门笔记(基于 LocalStack)

API Gateway CLI 实操入门笔记&#xff08;基于 LocalStack&#xff09; Categories: Cloud Google Rank Proof: No Last edited time: May 26, 2025 4:18 AM Status: Early draft Tags: aws 主要先简单的走一下流程&#xff0c;熟悉一下在 terminal 操作 API Gateway local…

数据分析案例-基于红米和华为手机的用户评论分析

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

深度学习入门:从零搭建你的第一个神经网络

深度学习入门&#xff1a;从零搭建你的第一个神经网络 系统化学习人工智能网站&#xff08;收藏&#xff09;&#xff1a;https://www.captainbed.cn/flu 文章目录 深度学习入门&#xff1a;从零搭建你的第一个神经网络摘要引言第一章&#xff1a;神经网络基础原理1.1 神经元…