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,