在我们平常开发的时候,肯定会遇到比如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下的接口全部都进行数据库记录了,数据库日志表多的能撑死。
这种方式就简单了,哪个接口需要就给哪个接口加上注解进行记录就行。
四、优化建议
-
异步记录日志:日志记录不应该影响主业务流程,可以使用@Async实现异步保存
-
参数过滤:敏感参数(如密码)不应该记录到日志中,需要做过滤处理
-
日志查询:提供按时间、操作人、模块等条件查询日志的功能
-
性能监控:可以扩展注解,添加超时警告功能
但是呢不是说日志越好越多,啥接口都给打上日志,这种方式是不可取的,我们要在满足需求的情况下进行日志管理的操作!