一、需求分析
1. 日志记录目标:
- 记录用户关键操作行为(登录、下单、修改订单、支付等)
- 记录系统管理员操作(权限变更、数据修改等)
- 记录系统异常和错误信息
2. 日志内容要求:
- 操作时间
- 用户ID/用户名
- 操作类型
- 操作对象(如订单ID、商品ID等)
- 操作结果(成功/失败)
- 操作详情(可选)
- 客户端IP地址
3. 日志用途:
- 用户行为分析
- 安全审计
- 故障排查
- 合规性要求
二、系统设计
1. 日志级别设计
```
| 级别 | 说明 | 示例场景 |
|--------|-------------------------------|------------------------------|
| INFO | 常规操作记录 | 用户登录、下单成功 |
| WARN | 潜在问题但不影响系统运行 | 频繁登录失败尝试 |
| ERROR | 操作失败但不影响主要业务流程 | 支付失败但订单状态回滚成功 |
| FATAL | 严重错误导致系统部分功能瘫痪 | 数据库连接完全中断 |
```
2. 日志表结构设计(MySQL示例)
```sql
CREATE TABLE `user_operation_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 日志ID,
`user_id` bigint(20) DEFAULT NULL COMMENT 用户ID,
`username` varchar(50) DEFAULT NULL COMMENT 用户名,
`operation_type` varchar(50) NOT NULL COMMENT 操作类型,
`operation_object` varchar(100) DEFAULT NULL COMMENT 操作对象ID,
`operation_result` varchar(20) NOT NULL COMMENT 操作结果,
`operation_detail` text COMMENT 操作详情,
`client_ip` varchar(50) DEFAULT NULL COMMENT 客户端IP,
`user_agent` varchar(500) DEFAULT NULL COMMENT 用户代理,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间,
`module` varchar(50) DEFAULT NULL COMMENT 所属模块,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_create_time` (`create_time`),
KEY `idx_operation_type` (`operation_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=用户操作日志表;
```
3. 日志采集方式
1. AOP切面实现(Spring Boot示例):
```java
@Aspect
@Component
public class OperationLogAspect {
@Autowired
private UserOperationLogMapper logMapper;
@Autowired
private HttpServletRequest request;
@Pointcut("@annotation(com.kuaile.annotation.OperationLog)")
public void logPointCut() {}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long beginTime = System.currentTimeMillis();
// 执行方法
Object result = joinPoint.proceed();
// 执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
// 保存日志
saveLog(joinPoint, time, result);
return result;
}
private void saveLog(ProceedingJoinPoint joinPoint, long time, Object result) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
OperationLog operationLog = method.getAnnotation(OperationLog.class);
if (operationLog == null) {
return;
}
UserOperationLog log = new UserOperationLog();
// 从切面上下文中获取用户信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof UserDetails) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
log.setUsername(userDetails.getUsername());
// 这里需要根据实际用户系统获取user_id
}
// 获取请求信息
String ip = IpUtils.getIpAddr(request);
log.setClientIp(ip);
log.setUserAgent(request.getHeader("User-Agent"));
// 设置日志其他字段
log.setOperationType(operationLog.value());
log.setModule(operationLog.module());
log.setOperationResult(result instanceof ResponseResult ?
((ResponseResult) result).getCode() == 200 ? "SUCCESS" : "FAIL" : "SUCCESS");
log.setCreateTime(new Date());
// 保存到数据库
logMapper.insert(log);
}
}
```
2. 自定义注解:
```java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {
String value() default ""; // 操作类型
String module() default ""; // 所属模块
String detail() default ""; // 操作详情
}
```
三、实现方案
1. 日志记录方式选择
1. 同步写入数据库:
- 优点:实时性强,数据可靠
- 缺点:影响系统性能,高并发时可能成为瓶颈
2. 异步写入(消息队列):
- 优点:对主业务影响小,吞吐量高
- 缺点:可能丢失少量日志,实现较复杂
3. 混合方案:
- 关键操作同步写入
- 非关键操作异步写入
推荐方案:使用Spring的@Async实现异步日志记录,结合消息队列(如RabbitMQ)作为备选方案
2. 关键代码实现
1. 日志服务接口:
```java
public interface LogService {
void recordLog(UserOperationLog log);
void asyncRecordLog(UserOperationLog log);
}
```
2. 日志服务实现:
```java
@Service
public class LogServiceImpl implements LogService {
@Autowired
private UserOperationLogMapper logMapper;
@Override
public void recordLog(UserOperationLog log) {
logMapper.insert(log);
}
@Async
@Override
public void asyncRecordLog(UserOperationLog log) {
try {
// 可以添加重试机制
logMapper.insert(log);
} catch (Exception e) {
// 记录到文件或发送到错误队列
log.error("日志记录失败", e);
}
}
}
```
3. 日志查询接口
```java
@RestController
@RequestMapping("/api/logs")
public class LogController {
@Autowired
private UserOperationLogMapper logMapper;
@GetMapping
public ResponseResult queryLogs(
@RequestParam(required = false) Long userId,
@RequestParam(required = false) String operationType,
@RequestParam(required = false) Date startTime,
@RequestParam(required = false) Date endTime,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
List logs = logMapper.selectByCondition(
userId, operationType, startTime, endTime);
PageInfo pageInfo = new PageInfo<>(logs);
return ResponseResult.success(pageInfo);
}
}
```
四、性能优化
1. 批量插入:
- 积累一定数量的日志后批量插入数据库
- 使用MyBatis的``实现批量插入
2. 日志分级存储:
- 重要日志(如支付相关)实时存储
- 普通日志按时间分批存储
3. 日志归档策略:
- 按月/周归档历史日志
- 冷数据存储到低成本存储(如对象存储)
4. 索引优化:
- 为常用查询条件创建索引
- 避免过度索引影响写入性能
五、安全考虑
1. 敏感信息脱敏:
- 避免在日志中记录密码、支付信息等敏感数据
- 对用户手机号、身份证号等进行部分脱敏
2. 访问控制:
- 日志查询接口需要权限验证
- 普通用户只能查询自己的操作日志
- 管理员可查询所有日志但需记录查询行为
3. 日志保留期限:
- 根据合规要求设置日志保留期限(如6个月)
- 定期清理过期日志
六、测试方案
1. 单元测试:
- 测试日志记录方法是否正确捕获参数
- 测试异常情况下的日志记录
2. 集成测试:
- 测试完整操作流程中的日志记录
- 测试高并发场景下的日志写入性能
3. 压力测试:
- 模拟1000+ TPS的日志写入压力
- 测试系统在高负载下的稳定性和性能
七、部署与监控
1. 日志收集与展示:
- 集成ELK(Elasticsearch+Logstash+Kibana)进行日志分析和可视化
- 或使用Prometheus+Grafana监控日志写入性能
2. 告警机制:
- 日志写入失败率超过阈值时告警
- 特定错误类型频繁出现时告警
3. 日志备份:
- 定期备份日志数据到异地存储
- 测试备份数据的可恢复性
八、后续优化方向
1. 日志分析:
- 基于日志的用户行为分析
- 异常操作模式检测
2. 日志压缩:
- 对历史日志进行压缩存储
- 采用列式存储格式(如Parquet)提高查询效率
3. 多维度查询:
- 增强日志查询功能,支持多维度组合查询
- 实现日志数据的实时分析
通过以上设计和实现,快驴生鲜系统可以建立起完善的用户操作日志体系,既满足业务运营和安全审计的需求,又保证系统的高性能和稳定性。