一、需求分析 1.日志记录目的: -审计追踪:记录用户所有关键操作,便于问题排查和安全审计 -行为分析:分析用户使用模式,优化系统功能 -故障回溯:当系统出现问题时,可通过日志快速定位问题原因 2.需要记录的信息: -操作时间 -操作用户ID/用户名 -操作类型(登录
一、需求分析
1. 日志记录目的:
- 审计追踪:记录用户所有关键操作,便于问题排查和安全审计
- 行为分析:分析用户使用模式,优化系统功能
- 故障回溯:当系统出现问题时,可通过日志快速定位问题原因
2. 需要记录的信息:
- 操作时间
- 操作用户ID/用户名
- 操作类型(登录、查询、修改、删除等)
- 操作对象(具体数据或功能模块)
- 操作结果(成功/失败及错误信息)
- 客户端信息(IP地址、设备类型等)
二、技术方案设计
1. 日志存储方案
- 数据库存储:使用MySQL表存储结构化日志数据
- 文件存储:同时写入日志文件,便于快速查看和归档
- 考虑因素:高并发写入性能、查询效率、存储成本
2. 日志表设计
```sql
CREATE TABLE `user_operation_logs` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID,
`user_id` bigint(20) NOT NULL COMMENT 用户ID,
`username` varchar(50) DEFAULT NULL COMMENT 用户名,
`operation_type` varchar(20) NOT NULL COMMENT 操作类型(LOGIN/QUERY/CREATE/UPDATE/DELETE等),
`module_name` varchar(50) NOT NULL COMMENT 操作模块(商品管理/订单管理等),
`operation_content` text COMMENT 操作内容详情(JSON格式),
`operation_result` varchar(20) NOT NULL COMMENT 操作结果(SUCCESS/FAILED),
`error_message` varchar(500) DEFAULT NULL COMMENT 错误信息,
`client_ip` varchar(50) DEFAULT NULL COMMENT 客户端IP,
`user_agent` varchar(200) DEFAULT NULL COMMENT 用户代理信息,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP 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. 实现方式选择
- AOP切面实现:使用Spring AOP拦截Controller层方法,统一记录日志
- 手动记录:在关键业务代码中显式调用日志记录方法
- 最终方案:结合两者,核心业务使用AOP,特殊场景手动记录
三、核心代码实现
1. 日志注解定义
```java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {
String module() default ""; // 模块名称
String operationType() default ""; // 操作类型
String contentDesc() default ""; // 内容描述模板
}
```
2. AOP切面实现
```java
@Aspect
@Component
public class OperationLogAspect {
@Autowired
private OperationLogService logService;
@Autowired
private HttpServletRequest request;
@Pointcut("@annotation(com.kuailu.annotation.OperationLog)")
public void logPointCut() {}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
// 执行方法
Object result = point.proceed();
// 记录日志
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
OperationLog operationLog = method.getAnnotation(OperationLog.class);
// 获取请求参数
Object[] args = point.getArgs();
String params = JSON.toJSONString(args);
// 构建日志内容
OperationLogDTO logDTO = new OperationLogDTO();
logDTO.setModuleName(operationLog.module());
logDTO.setOperationType(operationLog.operationType());
logDTO.setOperationContent(buildContent(operationLog.contentDesc(), args));
logDTO.setOperationResult(result instanceof ResponseResult ?
((ResponseResult)result).isSuccess() ? "SUCCESS" : "FAILED" : "SUCCESS");
// 获取用户信息
UserInfo user = (UserInfo) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (user != null) {
logDTO.setUserId(user.getId());
logDTO.setUsername(user.getUsername());
}
// 获取客户端信息
logDTO.setClientIp(getClientIp());
logDTO.setUserAgent(request.getHeader("User-Agent"));
// 保存日志
logService.saveLog(logDTO);
return result;
}
private String getClientIp() {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
private String buildContent(String template, Object[] args) {
// 根据模板和参数构建操作内容描述
// 示例: template="修改商品ID为{0}的价格为{1}", args=[123, 9.99]
// 返回: "修改商品ID为123的价格为9.99"
try {
return MessageFormat.format(template, args);
} catch (Exception e) {
return template;
}
}
}
```
3. 日志服务实现
```java
@Service
public class OperationLogServiceImpl implements OperationLogService {
@Autowired
private OperationLogMapper logMapper;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(OperationLogDTO logDTO) {
OperationLogEntity entity = new OperationLogEntity();
BeanUtils.copyProperties(logDTO, entity);
// 记录到数据库
logMapper.insert(entity);
// 异步记录到文件(可使用MQ或线程池实现异步)
asyncSaveToFile(logDTO);
}
private void asyncSaveToFile(OperationLogDTO logDTO) {
// 实现文件日志记录逻辑
// 可以使用Log4j2或Logback的异步日志功能
}
}
```
四、日志查询功能实现
1. 日志查询接口
```java
@RestController
@RequestMapping("/api/logs")
public class LogController {
@Autowired
private OperationLogService logService;
@GetMapping("/search")
public ResponseResult
> searchLogs(
@RequestParam(required = false) Long userId,
@RequestParam(required = false) String username,
@RequestParam(required = false) String operationType,
@RequestParam(required = false) String moduleName,
@RequestParam(required = false) Date startTime,
@RequestParam(required = false) Date endTime,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "20") Integer pageSize) {
LogQueryDTO query = new LogQueryDTO();
query.setUserId(userId);
query.setUsername(username);
query.setOperationType(operationType);
query.setModuleName(moduleName);
query.setStartTime(startTime);
query.setEndTime(endTime);
query.setPageNum(pageNum);
query.setPageSize(pageSize);
PageResult result = logService.queryLogs(query);
return ResponseResult.success(result);
}
}
```
2. 日志查询服务
```java
@Service
public class OperationLogServiceImpl implements OperationLogService {
@Autowired
private OperationLogMapper logMapper;
@Override
public PageResult queryLogs(LogQueryDTO query) {
PageHelper.startPage(query.getPageNum(), query.getPageSize());
List entities = logMapper.selectByCondition(query);
List dtos = entities.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
return new PageResult<>(dtos, ((Page<?>)entities).getTotal());
}
private OperationLogDTO convertToDTO(OperationLogEntity entity) {
OperationLogDTO dto = new OperationLogDTO();
BeanUtils.copyProperties(entity, dto);
return dto;
}
}
```
五、性能优化考虑
1. 异步写入:使用消息队列或线程池实现日志异步写入,避免阻塞主业务
2. 批量插入:积累一定数量日志后批量插入数据库
3. 日志分级:重要操作实时记录,一般操作定期归档
4. 分区表:按时间对日志表进行分区,提高查询效率
5. 缓存热点数据:对频繁查询的日志条件进行缓存
六、安全考虑
1. 敏感信息脱敏:对日志中的密码、支付信息等敏感数据进行脱敏处理
2. 访问控制:日志查询接口需要适当的权限控制
3. 日志保留策略:制定合理的日志保留期限,避免无限增长
七、部署与监控
1. 日志收集:使用Filebeat或Fluentd收集日志文件
2. 日志分析:集成ELK(Elasticsearch+Logstash+Kibana)进行日志分析和可视化
3. 告警机制:对关键操作失败设置告警阈值
八、测试用例示例
1. 正常操作日志记录:
- 用户登录成功
- 创建新商品
- 修改订单状态
2. 异常操作日志记录:
- 用户登录失败(密码错误)
- 创建商品时缺少必填字段
- 修改不存在的订单
3. 边界条件测试:
- 超长操作内容
- 特殊字符处理
- 并发操作日志记录
九、后续优化方向
1. 增加操作前后的数据对比记录
2. 实现操作回滚功能(基于日志)
3. 增加日志压缩和归档功能
4. 实现跨系统的操作链路追踪
通过以上方案,快驴生鲜系统可以建立起完善的用户操作日志体系,既满足安全审计需求,又为系统运维和业务分析提供有力支持。