当前位置: 首页 > news >正文

SpringBoot 项目实现操作日志的记录(使用 AOP 注解模式)


本文是博主在做关于如何记录用户操作日志时做的记录,常见的项目中难免存在一些需要记录重要日志的部分,例如权限和角色设定,重要数据的操作等部分。

博主使用 Spring 中的 AOP 功能,结合注解的方式,对用户操作过的一些重要方法做日志记录,存储到数据库中,可以随时查阅。

本文使用技术:SpringBoot 3 + JDK17 + MyBatis-Plus + MySQL。


文章目录

    • 01、PostMan 测试结果
    • 02、数据库存储数据后展示样式
    • 03、数据库表创建语句
    • 04、项目结构图
    • 05、代码:application.yml
    • 06、代码:pom.xml
    • 07、代码:注解 LogRecord
    • 08、代码:LogRecordAop
    • 09、代码:OperateTypeEnum
    • 10、代码:TestController
    • 11、代码:LogRecordMapper(使用 MyBatis-Plus)
    • 12、代码:ProjectLog(实体类)
    • 13、代码:UserInfo
    • 14、代码:UserServiceImpl
    • 15、代码:IUserService
    • 16、代码:LogAopUtils
    • 17、代码:LogHolder
    • 18、代码:UserHolder
    • 19、代码:Application
    • 20、代码:LogRecordMapper.xml

01、PostMan 测试结果

青灯文案-测试

02、数据库存储数据后展示样式

在这里插入图片描述

03、数据库表创建语句

CREATE TABLE project_log (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志ID',company_id varchar(36) DEFAULT NULL COMMENT '公司ID',company varchar(200) DEFAULT NULL COMMENT '公司',uname varchar(100) DEFAULT NULL COMMENT '用户名',uid bigint(20) DEFAULT NULL COMMENT '用户ID',operate_type int(10) DEFAULT NULL COMMENT '操作类型',operate_module varchar(64) DEFAULT NULL COMMENT '操作模块',operate_desc varchar(255) DEFAULT NULL COMMENT '操作描述',operate_param text DEFAULT NULL COMMENT '操作参数',operate_time bigint(20) DEFAULT NULL COMMENT '操作时间',operate_result tinyint(10) DEFAULT NULL COMMENT '操作结果',PRIMARY KEY (id)
);create index idx_operate_typeon project_log (operate_type);create index idx_operate_moduleon project_log (operate_module);create index idx_uidon project_log (uid);create index idx_companyon project_log (company);create index idx_unameon project_log (uname);

04、项目结构图

青灯文案-项目结构

05、代码:application.yml

server:port: 8089spring:datasource:url: jdbc:mysql://10.100.4.163:3306/test?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: 123456

06、代码:pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.wen</groupId><artifactId>Test-Operate-Log</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.4.1</version><relativePath/> <!-- lookup parent from repository --></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-jsqlparser</artifactId><version>3.5.9</version></dependency><!--mybatis--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.9</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-core</artifactId><version>1.3.5</version><scope>test</scope></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId></dependency><dependency><groupId>com.fhs-opensource</groupId><artifactId>easy-trans-mybatis-plus-extend</artifactId><version>3.0.6</version></dependency><dependency><groupId>com.oracle.database.jdbc</groupId><artifactId>ojdbc8</artifactId></dependency><dependency><groupId>org.mybatis.dynamic-sql</groupId><artifactId>mybatis-dynamic-sql</artifactId><version>1.4.0</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.1</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.83</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><configuration><archive><!--生成的jar 不要包含pom.xml pom.properties 这2个文件--><addMavenDescriptor>false</addMavenDescriptor><manifest><!--是否要不第三番jar放到manifest的classpath中--><addClasspath>true</addClasspath><!--生成的manifest中的classpath的前缀,因为要把第三方jar放到lib目录下,所以classpath前缀是lib/--><classpathPrefix>lib/</classpathPrefix><!--应用的main class --><mainClass>cn.com.wind.server.Application</mainClass></manifest></archive></configuration></plugin></plugins></build></project>

07、代码:注解 LogRecord

package com.wen.common;import com.wen.common.OperateTypeEnum;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author : rjw* @date : 2025-05-19*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogRecord {OperateTypeEnum operateType() default OperateTypeEnum.OTHER;
}

08、代码:LogRecordAop

package com.wen.common;import cn.hutool.core.util.ArrayUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wen.mapper.LogRecordMapper;
import com.wen.model.UserInfo;
import com.wen.model.ProjectLog;
import com.wen.service.IUserService;
import com.wen.utils.LogAopUtils;
import com.wen.utils.LogHolder;
import com.wen.utils.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.Arrays;/*** @author : rjw* @date : 2025-05-07*/
@Aspect
@Component
@Slf4j
public class LogRecordAop {@Autowiredprivate IUserService userService;@Autowiredprivate LogRecordMapper logRecordMapper;/*** 切入点 使用注解路径*/@Pointcut("@annotation(com.wen.common.LogRecord)")public void record() {}/*** 前置操作* @param point 切入点*/@Before("record() && @annotation(logRecord)")public void before(JoinPoint point, LogRecord logRecord) {ProjectLog logInfo = new ProjectLog();UserInfo user = UserHolder.getCurrentUser();// id 自增if (user != null) {logInfo.setUid(user.getUid());logInfo.setUname(user.getName());logInfo.setCompanyId(user.getCompanyId() == null ? "" : user.getCompanyId());logInfo.setCompany(user.getCompany());} else {// 如果为null,则说明开发人员使用接口调用logInfo.setUid(-1L);logInfo.setUname("开发人员");logInfo.setCompanyId("");logInfo.setCompany("tx");}logInfo.setOperateType(logRecord.operateType().getCode());logInfo.setOperateModule(logRecord.operateType().getModule());logInfo.setOperateTime(System.currentTimeMillis());Object[] args = LogAopUtils.removeUnnecessaryArgs(point.getArgs());logInfo.setOperateDesc(genOperateDesc(logRecord.operateType(), args, logInfo));if (ArrayUtil.isNotEmpty(args)) {if (logRecord.operateType() == OperateTypeEnum.EXPORT) {logInfo.setOperateParam(JSON.toJSONString(args[0]));} else {logInfo.setOperateParam(JSON.toJSONString(args));}}LogHolder.setProjectLog(logInfo);}private String genOperateDesc(OperateTypeEnum operateType, Object[] args, ProjectLog projectLog) {String operateDesc = String.format("【%s】-%s", operateType.getModule(), operateType.getFunction());if (ArrayUtil.isEmpty(args)) {return operateDesc;}switch (operateType) {case OPERATE:operateDesc = String.format(operateDesc, Arrays.toString(args));break;default:JSONObject req = JSONObject.parseObject(JSON.toJSONString(args[0]));operateDesc = String.format(operateDesc, req.toJSONString());break;}log.info("operateDesc: {}", operateDesc);return operateDesc;}@AfterReturning(value = "record()", returning = "response")public void afterReturning(Object response) {try {ProjectLog logInfo = LogHolder.getProjectLog();//   ======== 这里要求要使用返回值   if (logInfo == null || response == null) {return;}logInfo.setOperateResult((byte) 400);if (!checkParam(logInfo)) {log.warn("add log to DB failed cause param error! complianceLog:{}", logInfo);return;}addPrivateQuoteLog(logInfo);} finally {LogHolder.removeProjectLog();}}@AfterThrowing(value = "record()", throwing = "t")public void afterThrowing(Throwable t) {try {ProjectLog infoLog = LogHolder.getProjectLog();if (infoLog == null) {return;}infoLog.setOperateResult((byte) 400);if (!checkParam(infoLog)) {log.warn("add log to DB failed cause param error! complianceLog:{}", infoLog);return;}addPrivateQuoteLog(infoLog);} finally {LogHolder.getProjectLog();}}@Asyncprivate void addPrivateQuoteLog(ProjectLog projectLog) {logRecordMapper.insert(projectLog);}private boolean checkParam(ProjectLog projectLog) {return projectLog != null &&projectLog.getCompanyId() != null &&projectLog.getCompany() != null &&projectLog.getUid() != null &&projectLog.getOperateResult() != null &&projectLog.getOperateTime() != null &&projectLog.getOperateType() != null &&projectLog.getUname() != null;}
}

09、代码:OperateTypeEnum

package com.wen.common;import lombok.Getter;/*** @author : rjw* @date : 2025-05-19*/
@Getter
public enum OperateTypeEnum {OTHER(0, "系统","其它类型"),OPERATE(1, "内部接口—操作", "操作"),EXPORT(2, "内部接口—导出", "导出"),MAX(100, "", "");private final int code;private final String module;private final String function;OperateTypeEnum(int code, String module, String function) {this.code = code;this.module = module;this.function = function;}
}

10、代码:TestController

package com.wen.controller;import com.wen.common.LogRecord;
import com.wen.common.OperateTypeEnum;
import com.wen.model.UserInfo;
import com.wen.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;/*** @author : rjw* @date : 2025-05-19*/
@Slf4j
@RestController
@RequestMapping("/system")
public class TestController {@Autowiredprivate IUserService userService;@GetMapping("/testLog")@LogRecord(operateType = OperateTypeEnum.OPERATE)public UserInfo updateQuoteChannelPush(@RequestParam("id") long id, @RequestParam("name") String name,@RequestParam("company") String company, @RequestParam("companyId") String companyId) {userService.getCurrentUser();// 和内部数据无关UserInfo userInfo = new UserInfo();userInfo.setUid(id);userInfo.setName(name);userInfo.setCompany(company);userInfo.setCompanyId(companyId);log.info("userInfo is {}", userInfo);// 要有返回值,不然不能记录,可以在AOP中自己调return userInfo;}
}

11、代码:LogRecordMapper(使用 MyBatis-Plus)

package com.wen.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wen.model.ProjectLog;/*** @author : rjw* @date : 2025-05-19*/
public interface LogRecordMapper extends BaseMapper<ProjectLog> {}

12、代码:ProjectLog(实体类)

package com.wen.model;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;/*** @author : rjw* @date : 2025-05-19*/
@Data
@TableName("project_log")
public class ProjectLog {/*** 日志 ID*/@TableId(value = "id", type = IdType.AUTO)private Long logId;/*** 公司ID*/private String companyId;/*** 公司*/private String company;/*** 用户名*/private String uname;/*** 用户ID*/private Long uid;/*** 操作类型*/private Integer operateType;/*** 操作模块*/private String operateModule;/*** 操作描述*/private String operateDesc;/*** 操作参数*/private String operateParam;/*** 操作时间*/private Long operateTime;/*** 操作结果*/private Byte operateResult;
}

13、代码:UserInfo

package com.wen.model;import lombok.Data;/*** @author : rjw* @date : 2025-05-19*/
@Data
public class UserInfo {private long uid;private String name;private String company;private String companyId;
}

14、代码:UserServiceImpl

package com.wen.service.impl;import com.wen.model.UserInfo;
import com.wen.service.IUserService;
import com.wen.utils.UserHolder;
import org.springframework.stereotype.Service;/*** @author : rjw* @date : 2025-05-19*/
@Service
public class UserServiceImpl implements IUserService {@Overridepublic UserInfo getCurrentUser() {/*** 一般是登陆的时候设置当前用户* 下线的时候删除当前用户*/UserInfo userInfo = new UserInfo();userInfo.setUid(1000001);userInfo.setName("张三");userInfo.setCompany("tx");userInfo.setCompanyId("tx");UserHolder.setCurrentUser(userInfo);return userInfo;}
}

15、代码:IUserService

package com.wen.service;import com.wen.model.UserInfo;/*** @author : rjw* @date : 2025-05-19*/
public interface IUserService {UserInfo getCurrentUser();}

16、代码:LogAopUtils

package com.wen.utils;import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.ResponseFacade;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;public class LogAopUtils {public final static Set<Class<?>> UNNECESSARY_CLASS = new HashSet<Class<?>>(){{add(ServletRequest.class);add(ServletResponse.class);add(HttpServletRequest.class);add(HttpServletResponse.class);add(RequestFacade.class);add(ResponseFacade.class);}};public final static Set<Class<?>> UNNECESSARY_SUPER_CLASSES = new HashSet<Class<?>>() {{add(MultipartFile.class);}};public static Object[] removeUnnecessaryArgs(Object[] args) {List<Object> list = new ArrayList<>();outer: for (Object arg : args) {if (UNNECESSARY_CLASS.contains(arg.getClass())) {continue;}for (Class<?> superClass : UNNECESSARY_SUPER_CLASSES) {if(superClass.isAssignableFrom(arg.getClass())) {continue outer;}}list.add(arg);}return list.toArray();}
}

17、代码:LogHolder

package com.wen.utils;import com.wen.model.ProjectLog;public class LogHolder {private static final ThreadLocal<ProjectLog> LOCAL_PROJECT_LOG = new ThreadLocal<>();public static ProjectLog getProjectLog() {return LOCAL_PROJECT_LOG.get();}public static void setProjectLog(ProjectLog user) {LOCAL_PROJECT_LOG.set(user);}public static void removeProjectLog() {LOCAL_PROJECT_LOG.remove();}
}

18、代码:UserHolder

package com.wen.utils;import com.wen.model.UserInfo;/*** @author : rjw* @date : 2025-05-07*/
public class UserHolder {private static final ThreadLocal<UserInfo> CURRENT_USER = new ThreadLocal<>();public static UserInfo getCurrentUser() {return CURRENT_USER.get();}public static void setCurrentUser(UserInfo user) {CURRENT_USER.set(user);}public static void removeCurrentUser() {CURRENT_USER.remove();}
}

19、代码:Application

package com.wen;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** @author : rjw* @date : 2025-05-19*/
@SpringBootApplication
@MapperScan(basePackages = {"com.wen.mapper"})
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class);}
}

20、代码:LogRecordMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wen.mapper.LogRecordMapper"></mapper>
http://www.xdnf.cn/news/559081.html

相关文章:

  • C++类与对象--6 特性二:继承
  • springMVC拦截器,拦截器拦截策略设置
  • 破解误区:WebView 调试常见认知误区与 WebDebugX 实践指南
  • AnyText2 在图片里玩文字而且还是所想即所得
  • V2X协议|如何做到“车联万物”?【无线通信小百科】
  • Hutool 常用工具类实战指南
  • selenium——基础知识
  • 数据一致性校验算法
  • 创建与管理MySQL数据库
  • Google精准狙击OpenAI Codex,发布AI编程助手Jules!
  • Fluent Bit持久化配置指南:保障日志不丢失的关键策略
  • Elasticsearch高级面试题汇总及答案
  • AMBA三种总线详解并比较
  • 元宇宙赋能广州文化传承,激发新活力​
  • C语言学习之内存函数
  • AI练习:毛发旋转效果
  • 再议AOI算法
  • 通过强化学习让大模型自适应开启思考模式
  • 101个α因子#9
  • C++(3)
  • idea如何让文件夹分层显示,而不是圆点分割
  • Python训练营打卡 Day31
  • 模板引擎:FreeMarker
  • 基于pyttsx3的文字转语音实现
  • 输出字母在字符串中位置索引
  • 超强吸水宠物垫行业2025数据分析报告
  • 从计算机组成原理寻址方式参悟指针的本质
  • cpu的x86架构跟ARM架构的区别介绍
  • SpringBoot整合LangChain4J
  • 深入探讨 Java 中的函数式编程