一、需求分析
1. 日志记录范围:
- 用户登录/登出操作
- 关键业务操作(下单、修改订单、取消订单等)
- 敏感数据访问(查看价格、库存等)
- 系统配置变更
- 异常操作尝试
2. 日志内容要求:
- 操作时间
- 操作用户ID及角色
- 操作类型
- 操作对象(如订单ID、商品ID等)
- 操作结果(成功/失败)
- 操作前后的数据变化(可选)
- 客户端IP地址
二、系统设计
1. 数据库设计
```sql
CREATE TABLE user_operation_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL COMMENT 用户ID,
username VARCHAR(50) NOT NULL COMMENT 用户名,
operation_type VARCHAR(50) NOT NULL COMMENT 操作类型,
operation_target VARCHAR(100) COMMENT 操作对象ID,
operation_content TEXT COMMENT 操作详情,
operation_result VARCHAR(20) NOT NULL COMMENT 操作结果(SUCCESS/FAILED),
client_ip VARCHAR(50) COMMENT 客户端IP,
user_agent VARCHAR(255) COMMENT 用户代理,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 操作时间,
module_name VARCHAR(50) COMMENT 所属模块
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=用户操作日志表;
```
2. 日志级别设计
- INFO:常规操作记录
- WARN:可疑操作或边界情况
- ERROR:操作失败记录
3. 架构设计
```
用户操作
↓
AOP切面拦截
↓
日志服务层
↓
异步写入数据库/消息队列
↓
日志分析系统(可选)
```
三、技术实现
1. Spring AOP实现日志拦截
```java
@Aspect
@Component
public class OperationLogAspect {
@Autowired
private OperationLogService logService;
@Autowired
private HttpServletRequest request;
// 定义切点:拦截所有Controller层方法
@Pointcut("execution(* com.kuailefresh.controller.*.*(..))")
public void controllerPointcut() {}
@Around("controllerPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 获取操作类型注解
OperationLog operationLog = method.getAnnotation(OperationLog.class);
if (operationLog == null) {
return joinPoint.proceed();
}
// 获取用户信息
Long userId = UserContext.getCurrentUserId();
String username = UserContext.getCurrentUsername();
// 记录开始时间
long startTime = System.currentTimeMillis();
try {
// 执行方法
Object result = joinPoint.proceed();
// 计算耗时
long costTime = System.currentTimeMillis() - startTime;
// 构建日志内容
String operationType = operationLog.value();
String moduleName = operationLog.module();
// 获取方法参数
Object[] args = joinPoint.getArgs();
StringBuilder params = new StringBuilder();
for (Object arg : args) {
if (arg != null) {
params.append(arg.toString()).append(";");
}
}
// 保存日志
OperationLogDTO logDTO = new OperationLogDTO();
logDTO.setUserId(userId);
logDTO.setUsername(username);
logDTO.setOperationType(operationType);
logDTO.setModuleName(moduleName);
logDTO.setOperationResult("SUCCESS");
logDTO.setClientIp(getClientIp());
logDTO.setParams(params.toString());
logDTO.setCostTime((int) costTime);
// 异步保存日志
asyncLogService.saveLog(logDTO);
return result;
} catch (Exception e) {
// 异常处理
long costTime = System.currentTimeMillis() - startTime;
OperationLogDTO logDTO = new OperationLogDTO();
logDTO.setUserId(userId);
logDTO.setUsername(username);
logDTO.setOperationType(operationLog.value());
logDTO.setModuleName(operationLog.module());
logDTO.setOperationResult("FAILED");
logDTO.setClientIp(getClientIp());
logDTO.setExceptionMsg(e.getMessage());
logDTO.setCostTime((int) costTime);
asyncLogService.saveLog(logDTO);
throw e;
}
}
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;
}
}
```
2. 自定义注解
```java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {
String value() default ""; // 操作类型
String module() default ""; // 所属模块
}
```
3. 异步日志服务
```java
@Service
public class AsyncLogServiceImpl implements AsyncLogService {
@Autowired
private OperationLogMapper logMapper;
@Async
@Override
public void saveLog(OperationLogDTO logDTO) {
try {
// 转换为实体
OperationLogEntity logEntity = new OperationLogEntity();
BeanUtils.copyProperties(logDTO, logEntity);
// 保存到数据库
logMapper.insert(logEntity);
} catch (Exception e) {
// 可以添加重试机制或写入失败日志
log.error("保存操作日志失败", e);
}
}
}
```
4. 控制器示例
```java
@RestController
@RequestMapping("/orders")
public class OrderController {
@OperationLog(value = "创建订单", module = "订单管理")
@PostMapping
public Result createOrder(@RequestBody OrderCreateDTO dto) {
// 业务逻辑
return Result.success();
}
@OperationLog(value = "取消订单", module = "订单管理")
@PutMapping("/{orderId}/cancel")
public Result cancelOrder(@PathVariable Long orderId) {
// 业务逻辑
return Result.success();
}
}
```
四、高级功能实现
1. 日志查询接口
```java
@RestController
@RequestMapping("/admin/logs")
public class LogController {
@Autowired
private OperationLogService logService;
@GetMapping
public Result queryLogs(
@RequestParam(required = false) Long userId,
@RequestParam(required = false) String operationType,
@RequestParam(required = false) String module,
@RequestParam(required = false) Date startTime,
@RequestParam(required = false) Date endTime,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
PageInfo pageInfo = logService.queryLogs(
userId, operationType, module, startTime, endTime, pageNum, pageSize);
return Result.success(pageInfo);
}
}
```
2. 日志分析(示例:统计各模块操作次数)
```java
@Service
public class LogAnalysisServiceImpl implements LogAnalysisService {
@Autowired
private OperationLogMapper logMapper;
@Override
public Map countOperationsByModule(Date date) {
List