SpringBoot实现操作日志记录的完整指南

 更新时间:2025年08月31日 09:24:58   作者:现在没有牛仔了  
在SpringBoot项目中,若将日志记录代码散乱地嵌入到每个业务方法的try-catch块中,会导致代码重复严重,不利于维护,本文将探讨如何利用SpringBoot面向切面编程(AOP) 来优雅地解决这一难题,希望对大家有所帮助

引言

在项目中,无论是在开发环境还是生产环境,如果系统出现故障,迎面而来的第一个问题往往是: “这个操作是谁在什么时候执行的?具体做了什么改动?” 如果系统对此一无所知,排查工作就如同大海捞针。

操作日志记录正是为了解决这一问题而生。它是一个系统性的、用于追踪用户行为、厘清操作责任以及复现历史流程的核心功能。它是系统的“黑匣子”,也是开发者的“记事本”。 在SpringBoot项目中,若将日志记录代码散乱地嵌入到每个业务方法的try-catch块中,会导致代码重复严重、核心业务逻辑被污染、不利于维护。

本文将探讨如何利用SpringBoot面向切面编程(AOP) 来优雅地解决这一难题。

一、环境准备与项目搭建

1. 创建一个标准的SpringBoot项目

我使用的是SpringBoot3.5.5+JDK17

2. 引入核心依赖

  • mysql-connector-j(数据库驱动)
  • mybatis-plus-spring-boot3-starter(数据持久化框架)
  • spring-boot-starter-aop(AOP核心)
  • aspectjweaver(面向切面工具)
  • spring-boot-starter-web(Web项目)
  • fastjson2(阿里json解析器)

二、实现步骤

1. 创建日志实体类(OperateLog)

@Data
public class OperateLog {
    private Long id; //主键
    private Long userId; //操作人id
    private Date createTime; //创建时间
    private String description; //请求描述
    private String method; //请求方法 如:post、get
    private String ip; //操作人ip
    private String param; //请求参数:json格式
    private String result; //请求结果:json格式
    private Integer success; //操作是否成功
    private String errorMsg; //错误信息
}

2. 创建操作日志记录表(operate_log)

create table operate_log(
	id BIGINT PRIMARY key AUTO_INCREMENT COMMENT '主键',
	user_id BIGINT DEFAULT 100 COMMENT '操作人id',
	create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
	description VARCHAR(200) COMMENT '请求描述',
	method VARCHAR(20) COMMENT '请求方法 如:post、get',
	ip VARCHAR(32) COMMENT '操作人ip',
	param VARCHAR(2000) COMMENT '请求参数:json格式',
	result VARCHAR(2000) COMMENT '请求结果:json格式',
	success TINYINT DEFAULT 0 COMMENT '操作是否成功:1是、0否',
	error_msg VARCHAR(2000) COMMENT '错误消息'
) COMMENT '操作日志记录表';

3. 创建添加日志的相关代码

//mapper接口
public interface OperateLogMapper extends BaseMapper<OperateLog> {}

//service接口
public interface OperateLogService extends IService<OperateLog> {}

//service实现类
@Service
public class OperateLogServiceImpl extends ServiceImpl<OperateLogMapper, OperateLog> implements OperateLogService {}

4. 创建自定义注解(@Log)

在注解类中,指定其可以对方法进行修饰,并设置运行时保留策略

@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
    /**
     * 请求描述
     */
    String title() default "";
}

5. 创建切面类(LogAspect)- 核心

在切面类中,创建前置通知和后置通知,并指定被@Log注解修饰的方法为切点,实现对目标方法的“增强”。

@Aspect
@Component
public class LogAspect {
    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
    @Autowired
    private OperateLogService logService;

    private final ExecutorService executor = Executors.newSingleThreadExecutor();

    /**
     * 请求前执行
     * @param aspLog
     */
    @Before(value = "@annotation(aspLog)")
    public void doBefore(Log aspLog){
        log.info("请求日志记录start");
    }

    /**
     * 请求后执行
     * @param aspLog
     * @param result
     */
    @AfterReturning(pointcut = "@annotation(aspLog)",returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Log aspLog, Object result){
        handleLog(joinPoint,aspLog,null,result);
    }

    /**
     * 请求异常执行
     * @param aspLog
     * @param e
     */
    @AfterThrowing(value = "@annotation(aspLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint,Log aspLog,Exception e){
        handleLog(joinPoint,aspLog,e,null);
    }

    /**
     * 日志处理器-记录日志
     * @param aspLog
     * @param e
     * @param result
     */
    private void handleLog(JoinPoint joinPoint,Log aspLog,Exception e,Object result){
        ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        OperateLog operateLog = new OperateLog();
        operateLog.setIp(attributes.getRequest().getRemoteAddr());
        operateLog.setMethod(attributes.getRequest().getMethod());
        String param = JSON.toJSONString(joinPoint.getArgs());
        operateLog.setParam(param);
        if(e != null){
            operateLog.setErrorMsg(e.getLocalizedMessage());
        }else{
            operateLog.setSuccess(1);
            operateLog.setResult(JSON.toJSONString(result));
        }
        operateLog.setDescription(aspLog.title());
        executor.submit(() -> logService.save(operateLog));
    }
}

6. 编写控制层代码进行测试

@RestController
public class TestController {
    @Log(title = "测试操作")
    @GetMapping("/test")
    public String test(){
        return "没毛病";
    }

    @Log(title = "测试参数操作")
    @PostMapping("/testParam")
    public String testParam(@RequestBody String data){
        return data;
    }

    @Log(title = "测试异常操作")
    @GetMapping("/testExecption")
    public String testExecption(){
        return (1/0)+"";
    }
}

7. 其他注意事项

如果你使用的Springboot也是3.0+,在引入mybatis-plus依赖的时候,一定要导入mybatis-plus-spring-boot3-starter,而不是mybatis-plus-boot-starter。如果导入的依赖不正确,会导致Spring无法注入mybatis相关的内容并报错。

三、功能测试与验证(Postman)

测试三种情况,1、测试无参get请求,2、测试传参post请求,3、测试异常请求,对于成功的请求要在日志记录中标记为成功,对于异常的请求要记录异常信息。

1. 测试无参get请求

2. 测试post带参数请求

3. 测试异常请求

四、 总结

到这里,就完成了操作日志的记录功能,需要注意的是,在日常开发中,一定要将记录日志的代码交给另一个线程执行,避免持久化操作对主线程产生性能上的影响。面向切面的本质上是对切点方法做了一层代理,在不影响业务代码的前提下对其进行功能扩展,降低了业务代码和非业务代码的耦合度,这种设计适用于很多其他的业务场景,比如异常处理、接口性能监控、参数校验与预处理等。

到此这篇关于SpringBoot实现操作日志记录的完整指南的文章就介绍到这了,更多相关SpringBoot操作日志记录内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 一文搞懂Java克隆及深拷贝与浅拷贝的区别

    一文搞懂Java克隆及深拷贝与浅拷贝的区别

    在编程中,通常通过实现Cloneable接口和重写clone方法来实现对象的克隆,然而,需要注意的是克隆操作可能存在深拷贝和浅拷贝的区别,在使用时需要根据实际需求选择合适的克隆方式,本文就给大家详细讲讲什么是克隆以及深拷贝与浅拷贝的区别,需要的朋友可以参考下
    2023-08-08
  • 浅谈SpringMVC+Spring3+Hibernate4开发环境搭建

    浅谈SpringMVC+Spring3+Hibernate4开发环境搭建

    MVC已经是现代Web开发中的一个很重要的部分,本文介绍一下SpringMVC+Spring3+Hibernate4的开发环境搭建,有兴趣的可以了解一下。
    2017-01-01
  • springboot 自定义配置Boolean属性不生效的解决

    springboot 自定义配置Boolean属性不生效的解决

    这篇文章主要介绍了springboot 自定义配置Boolean属性不生效的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • log4j2 xml配置文件屏蔽第三方依赖包的日志方式

    log4j2 xml配置文件屏蔽第三方依赖包的日志方式

    这篇文章主要介绍了log4j2 xml配置文件屏蔽第三方依赖包的日志方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04
  • Java并发读写锁ReentrantReadWriteLock 使用场景

    Java并发读写锁ReentrantReadWriteLock 使用场景

    ReentrantReadWriteLock是Java中一种高效的读写锁,适用于读多写少的并发场景,它通过允许多个线程同时读取,但在写入时限制为单线程访问,从而提高了程序的并发性和性能,本文给大家介绍Java并发读写锁ReentrantReadWriteLock 使用场景,感兴趣的朋友跟随小编一起看看吧
    2024-10-10
  • spring中EnvironmentPostProcessor接口的实现

    spring中EnvironmentPostProcessor接口的实现

    EnvironmentPostProcessor是SpringBoot用于动态修改环境配置的接口,本文主要介绍了spring中EnvironmentPostProcessor接口的实现,具有一定的参考价值,感兴趣的可以了解一下
    2025-05-05
  • MyBatis-Plus 中 的动态SQL 片段(sqlSegment)详解

    MyBatis-Plus 中 的动态SQL 片段(sqlSegment)详解

    MyBatis-Plus的sqlSegment通过Wrapper动态生成SQL片段,支持XML中${ew.customSqlSegment}引用,结合Lambda表达式避免硬编码,适用于动态查询、逻辑删除等场景,提升代码可维护性与灵活性,本文给大家介绍MyBatis-Plus中的动态SQL片段(sqlSegment)讲解,感兴趣的朋友一起看看吧
    2025-06-06
  • SpringMVC拦截器创建配置及执行顺序

    SpringMVC拦截器创建配置及执行顺序

    这篇文章主要为大家介绍了SpringMVC拦截器创建配置及执行顺序,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-05-05
  • 工厂模式_动力节点Java学院整理

    工厂模式_动力节点Java学院整理

    这篇文章主要介绍了工厂模式_动力节点Java学院整理的相关资料,需要的朋友可以参考下
    2017-08-08
  • springboot 正确的在异步线程中使用request的示例代码

    springboot 正确的在异步线程中使用request的示例代码

    这篇文章主要介绍了springboot中如何正确的在异步线程中使用request,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-07-07

最新评论