Spring AOP + RocketMQ 实现企业级操作日志异步采集(实战全流程)
Spring AOP + RocketMQ 实现企业级操作日志异步采集(实战全流程)
📌 项目背景
在企业级微服务架构中,记录操作日志是一项刚需。传统方式常使用数据库直接写入或通过 Feign 调用日志微服务,但这样存在耦合高、主流程阻塞、扩展性差等问题。
为此,我们将使用:
- Spring AOP 实现非侵入式日志采集
- RocketMQ 实现异步解耦投递
- Redis 实现消息幂等控制
- DLQ 死信队列保障日志消息最终可达
🧱 技术选型
模块 | 技术 |
---|---|
日志采集 | Spring AOP + 自定义注解 |
消息中间件 | RocketMQ + Spring Cloud Stream |
幂等控制 | Redis |
安全框架 | Sa-Token |
监控 & 补偿 | RocketMQ DLQ、自定义消费处理 |
🚦 实现目标
- 通过
@Log
注解拦截业务方法 - 捕获操作人、IP、请求参数、响应结果、执行耗时等日志信息
- 使用 RocketMQ 异步投递日志消息
- 使用 Redis 做幂等处理,防止重复消费
- 消费失败自动重试,最终由 DLQ 消费者处理
📦 Maven 依赖
确保主业务系统和日志服务都引入 RocketMQ 依赖:
<!-- RocketMQ Stream -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
</dependency>
1️⃣ 日志注解定义
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String title() default "";
BusinessType businessType() default BusinessType.OTHER;
OperatorType operatorType() default OperatorType.MANAGE;
boolean isSaveRequestData() default true;
boolean isSaveResponseData() default true;
String[] excludeParamNames() default {
};
}
2️⃣ AOP 切面实现(LogAspect)
- 使用
@Before/@AfterReturning/@AfterThrowing
统一处理日志 - 日志采集后调用
logMqService.saveSysLog()
异步发送到 MQ - 使用
ThreadLocal
计算执行耗时
@Aspect
@Component
public class LogAspect {
private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<>("Cost Time");
private static final String[] EXCLUDE_PROPERTIES = {
"password", "oldPassword", "newPassword", "confirmPassword", "credentials"};
@Resource private HttpServletRequest request;
@Resource private LogMqService logMqService;
@Before("@annotation(controllerLog)")
public void boBefore(JoinPoint joinPoint, Log controllerLog) {
TIME_THREADLOCAL.set(System.currentTimeMillis());
}
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
handleLog(joinPoint, controllerLog, null, jsonResult);
}
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
handleLog(joinPoint, controllerLog, e, null);
}
protected void handleLog(final JoinPoint joinPoint, Log controllerLog,