为了设计一个高效、可靠且可扩展的操作日志模块,可以结合 AOP(面向切面编程)、异步处理(多线程或MQ)以及合理的存储策略,具体方案如下:
1. 技术选型与架构设计
(1) AOP 实现非侵入式日志拦截
- 目的:通过切面自动拦截需要记录日志的操作,避免业务代码耦合。
- 实现方式:
- 自定义注解(如
@Loggable
),标记需要记录日志的方法。 - 使用 Spring AOP 或 AspectJ 定义切面,在方法执行前后捕获操作信息(如方法名、参数、返回值、异常等)。
- 结合 SpEL 表达式动态解析日志内容(例如从参数中提取业务ID)。
- 自定义注解(如
- 示例注解:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Loggable {String operation() default "";String detail() default ""; }
(2) 异步处理:选择多线程或MQ
- 目标:将日志记录与业务逻辑解耦,避免同步写入的性能瓶颈。
- 方案对比:
- 多线程线程池:
- 优点:实现简单,无外部依赖,适合中小型系统。
- 缺点:系统宕机可能导致内存中未处理的日志丢失。
- 实现:在切面中将日志对象提交到
ThreadPoolTaskExecutor
。
- 消息队列(MQ):
- 优点:解耦彻底,支持削峰填谷,数据可靠性高(如 Kafka 持久化)。
- 缺点:依赖中间件,增加系统复杂度。
- 实现:切面中发送日志消息到MQ(如 RabbitMQ/Kafka),消费者服务异步消费并存储。
- 多线程线程池:
(3) 存储策略
- 数据库存储:
- 结构化存储,便于查询和管理(如 MySQL)。
- 需设计合理的日志表(字段:操作类型、操作人、时间、IP、参数、结果状态等)。
- Elasticsearch:
- 适合海量日志的高效检索与分析。
- 混合存储:核心操作存数据库,辅助分析日志存ES。
2. 核心实现步骤
(1) 定义日志实体
public class OperationLog {private Long id;private String operation; // 操作类型(如 "新增用户")private String operator; // 操作人(从 SecurityContext 获取)private String params; // 方法参数(JSON序列化)private String result; // 操作结果(成功/失败)private String errorMsg; // 异常信息private LocalDateTime createTime;private String ip; // 操作IP
}
(2) AOP 切面实现
@Aspect
@Component
public class LogAspect {@Autowiredprivate LogService logService; // 异步日志服务@Around("@annotation(loggable)")public Object logOperation(ProceedingJoinPoint joinPoint, Loggable loggable) throws Throwable {// 1. 构建基础日志信息OperationLog log = new OperationLog();log.setOperation(loggable.operation());log.setOperator(getCurrentUser());log.setParams(serializeParams(joinPoint.getArgs()));try {Object result = joinPoint.proceed(); // 执行原方法log.setResult("SUCCESS");return result;} catch (Exception e) {log.setResult("FAIL");log.setErrorMsg(e.getMessage());throw e;} finally {// 2. 异步提交日志logService.asyncSave(log); // 通过线程池或MQ发送}}
}
(3) 异步处理实现
-
方案1:线程池异步提交
@Service public class LogService {@Autowiredprivate LogRepository logRepository;private Executor asyncExecutor = Executors.newFixedThreadPool(4);public void asyncSave(OperationLog log) {asyncExecutor.execute(() -> logRepository.save(log));} }
-
方案2:MQ异步处理
// 切面中发送消息到MQ @Autowired private RabbitTemplate rabbitTemplate;public void asyncSave(OperationLog log) {rabbitTemplate.convertAndSend("log.exchange", "log.routing.key", log); }// MQ消费者服务 @RabbitListener(queues = "log.queue") public void handleLogMessage(OperationLog log) {logRepository.save(log); }
3. 扩展性设计
- 动态开关:通过配置中心(如 Apollo)动态开启/关闭日志记录。
- 日志分表:按时间分表(如按月)避免单表过大。
- 敏感信息脱敏:在切面中对参数进行脱敏处理(如手机号、密码)。
- 链路追踪:集成 TraceID(如 Sleuth)关联操作日志与请求链路。
4. 技术选型建议
- 中小型系统:AOP + 线程池异步,简单高效。
- 分布式/高并发系统:AOP + MQ(如 Kafka),保证可靠性与扩展性。
- 日志分析场景:ES + Logstash + Kibana 实现可视化分析。
5. 注意事项
- 异常处理:确保异步过程有异常捕获机制(如 MQ 重试、死信队列)。
- 性能监控:监控日志存储的耗时和成功率,避免成为系统瓶颈。
- 用户上下文:通过 ThreadLocal 或 SecurityContext 获取操作人信息。