如何精确记录用户操作行为?Spring AOP实现日志审计方案

在我们平常开发的时候,肯定会遇到比如bug呀这类的问题,有时候报错会直接出现在控制台,有时候可能不会,所以我们查找错误的时候是非常麻烦的,今天就来说说如何使用Spring AOP实现一个优雅的日志审计方案。

一、为什么需要记录用户操作?

想象一下,假如系统出现异常数据,如果没有操作日志,可能要花费大量时间翻看代码和数据库变化。而如果有详细的操作记录,就能快速定位到是谁、在什么时间、执行了什么操作,问题自然迎刃而解。

传统的做法是在每个方法里手动写日志代码,但是这样很麻烦的,而且满篇都是这种看着都烦。比如:

java 复制代码
public void updateUser(User user) {
    // 业务逻辑
    log.info("用户ID:{}修改了用户信息", getUserID());
    // 更多业务代码
}

每个方法都这样写,不仅繁琐,还会让业务代码变得臃肿。这时候,我们就需要改变思路。

二、Spring AOP?

概念就不多说了,都知道就是像一个"拦截器",可以在方法执行前后插入自定义逻辑。

Spring AOP提供了四种类型的通知:

  • 前置通知:在方法执行前执行
  • 后置通知:在方法执行后执行
  • 异常通知:在方法抛出异常时执行
  • 环绕通知:可以在方法执行前后自定义行为

三、具体实现步骤

1. 添加依赖

首先在pom.xml中添加Spring AOP依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2. 创建自定义注解

java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuditLog {
    String value() default "";
    String module() default ""; // 模块名称
    String operation() default ""; // 操作类型
}

3. 实现切面逻辑

java 复制代码
@Aspect
@Component
public class AuditLogAspect {
    
    @Autowired
    private HttpServletRequest request;
    
    // 定义切点:所有带有@AuditLog注解的方法
    @Pointcut("@annotation(com.example.demo.annotation.AuditLog)")
    public void auditPointCut() {}
    
    @Around("auditPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        
        // 执行方法
        Object result = point.proceed();
        
        // 执行时长
        long time = System.currentTimeMillis() - beginTime;
        
        // 保存日志
        saveLog(point, time, result);
        
        return result;
    }
    
    private void saveLog(ProceedingJoinPoint joinPoint, long time, Object result) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        
        AuditLog auditLog = method.getAnnotation(AuditLog.class);
        
        // 获取请求信息
        String username = getUsername();
        String ip = getClientIP();
        String methodName = method.getName();
        String params = getParams(joinPoint);
        
        // 构建日志实体
        Log log = new Log();
        log.setUsername(username);
        log.setIp(ip);
        log.setMethod(methodName);
        log.setParams(params);
        log.setTime(time);
        log.setOperation(auditLog.operation());
        log.setModule(auditLog.module());
        log.setCreateTime(new Date());
        
        // 保存到数据库
        logRepository.save(log);
    }
    
    private String getUsername() {
        // 从安全上下文中获取当前用户
        // 实际项目中可根据使用的安全框架调整
        return "当前用户名";
    }
    
    private String getClientIP() {
        // 获取客户端IP
        return request.getRemoteAddr();
    }
    
    private String getParams(ProceedingJoinPoint point) {
        // 获取方法参数
        Object[] args = point.getArgs();
        if (args == null || args.length == 0) {
            return "";
        }
        try {
            return JSON.toJSONString(args);
        } catch (Exception e) {
            return "参数序列化失败";
        }
    }
}

4. 使用注解记录日志

在需要记录日志的方法上添加注解:

java 复制代码
@AuditLog(module = "用户管理", operation = "更新用户信息")
public void updateUser(User user) {
    // 业务逻辑
}

之前的写法呢是所有的请求接口都进行拦截,并且进行日志记录的,如下图

这种写法就是直接将所有只要是这个controller下的接口全部都进行数据库记录了,数据库日志表多的能撑死。

这种方式就简单了,哪个接口需要就给哪个接口加上注解进行记录就行。

四、优化建议

  1. 异步记录日志:日志记录不应该影响主业务流程,可以使用@Async实现异步保存

  2. 参数过滤:敏感参数(如密码)不应该记录到日志中,需要做过滤处理

  3. 日志查询:提供按时间、操作人、模块等条件查询日志的功能

  4. 性能监控:可以扩展注解,添加超时警告功能

但是呢不是说日志越好越多,啥接口都给打上日志,这种方式是不可取的,我们要在满足需求的情况下进行日志管理的操作!

相关推荐
MediaTea4 小时前
Python 第三方库:Flask(轻量级 Web 框架)
开发语言·前端·后端·python·flask
q***72564 小时前
Spring Boot + Vue 全栈开发实战指南
vue.js·spring boot·后端
清晨细雨~5 小时前
SpringBoot整合EasyExcel实现Excel表头校验
spring boot·后端·excel
canonical_entropy5 小时前
API无缝升级方案:从推模式到拉模式的架构演进
后端·restful·graphql
摆烂工程师5 小时前
今天 Cloudflare 全球事故,连 GPT 和你的网站都一起“掉线”了
前端·后端·程序员
追逐时光者5 小时前
快速构建一个基础、现代化的 WinForm 管理系统
后端·.net
在人间负债^7 小时前
Rust 实战项目:TODO 管理器
开发语言·后端·rust
Moonbit7 小时前
入围名单公布|2025 MGPIC 决赛即将拉开帷幕!
后端·算法
爱吃烤鸡翅的酸菜鱼7 小时前
用【rust】实现命令行音乐播放器
开发语言·后端·rust
黛琳ghz7 小时前
用 Rust 从零构建高性能文件加密工具
开发语言·后端·rust