Files
novalon-manage-system/docs/plans/2026-04-03-operation-log-design.md
T
张翔 03a9eb73c7 docs: add operation log feature design document
- Define annotation-driven operation log recording approach
- Specify key business operations to be logged
- Outline testing and deployment strategy
- Address performance and security considerations
2026-04-03 19:26:16 +08:00

262 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 操作日志记录功能设计文档
**日期**: 2026-04-03
**作者**: 张翔
**版本**: 1.0
## 1. 概述
### 1.1 背景
当前系统的Dashboard操作日志一直显示0,原因是缺少操作日志记录功能。虽然数据库表、服务层和API都已就绪,但没有自动记录用户操作的机制。
### 1.2 目标
实现一个基于注解的操作日志记录功能,自动记录关键业务操作,为系统审计和问题追踪提供数据支持。
### 1.3 范围
只记录关键业务操作,包括:
- 用户管理:创建、更新、删除用户、修改密码、分配角色
- 角色管理:创建、更新、删除角色、分配权限
- 菜单管理:创建、更新、删除菜单
- 系统配置:创建、更新、删除配置
- 数据字典:创建、更新、删除字典
- 公告管理:创建、更新、删除公告
## 2. 架构设计
### 2.1 整体架构
采用**AOP切面 + 注解驱动**的架构:
```
用户请求 → Handler方法(带@OperationLog注解)
OperationLogAspect拦截
记录开始时间、获取请求参数
执行业务方法
记录结束时间、获取返回结果
异步保存操作日志到数据库
返回结果给用户
```
### 2.2 核心组件
1. **`@OperationLog`注解**:标记需要记录日志的方法
2. **`OperationLogAspect`切面**:拦截注解方法,自动记录操作日志
3. **`OperationLogService`服务**:已有的服务层,负责保存日志到数据库
4. **异步处理**:使用Reactor的异步机制,不阻塞主业务流程
### 2.3 关键设计点
- **响应式编程**:使用Reactor的Mono/Flux,与现有WebFlux架构保持一致
- **异步记录**:日志记录不影响主业务流程性能
- **错误容错**:日志记录失败不影响业务方法执行
- **自动获取上下文**:从SecurityContext获取当前用户,从ServerWebExchange获取IP地址
## 3. 详细设计
### 3.1 注解定义
```java
package cn.novalon.manage.sys.audit;
import java.lang.annotation.*;
/**
* 操作日志注解
* 标记需要记录操作日志的方法
*
* @author 张翔
* @date 2026-04-03
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLog {
/**
* 操作名称
* 例如:"创建用户"、"删除角色"
*/
String operation();
/**
* 模块名称
* 例如:"用户管理"、"角色管理"
*/
String module();
}
```
### 3.2 切面实现
```java
@Aspect
@Component
public class OperationLogAspect {
private final IOperationLogService logService;
private final ObjectMapper objectMapper;
@Around("@annotation(operationLog)")
public Object around(ProceedingJoinPoint point, OperationLog operationLog) throws Throwable {
long startTime = System.currentTimeMillis();
// 1. 获取请求信息
String username = getCurrentUsername();
String ip = getCurrentIp();
String method = point.getSignature().toShortString();
String params = serializeParams(point.getArgs());
// 2. 执行业务方法
Object result = null;
String status = "0"; // 0-成功, 1-失败
String errorMsg = null;
try {
result = point.proceed();
// 3. 处理响应式结果
if (result instanceof Mono) {
return ((Mono<?>) result)
.doOnSuccess(res -> {
long duration = System.currentTimeMillis() - startTime;
saveLogAsync(operationLog, username, ip, method,
params, res, duration, "0", null);
})
.doOnError(error -> {
long duration = System.currentTimeMillis() - startTime;
saveLogAsync(operationLog, username, ip, method,
params, null, duration, "1", error.getMessage());
});
}
return result;
} catch (Throwable error) {
status = "1";
errorMsg = error.getMessage();
throw error;
} finally {
if (!(result instanceof Mono)) {
long duration = System.currentTimeMillis() - startTime;
saveLogAsync(operationLog, username, ip, method,
params, result, duration, status, errorMsg);
}
}
}
private void saveLogAsync(OperationLog operationLog, String username,
String ip, String method, String params,
Object result, long duration, String status,
String errorMsg) {
// 异步保存日志,不阻塞主流程
Mono.fromRunnable(() -> {
cn.novalon.manage.sys.core.domain.OperationLog log =
new cn.novalon.manage.sys.core.domain.OperationLog();
log.setUsername(username);
log.setOperation(operationLog.module() + " - " + operationLog.operation());
log.setMethod(method);
log.setParams(params);
log.setResult(serializeResult(result));
log.setIp(ip);
log.setDuration(duration);
log.setStatus(status);
log.setErrorMsg(errorMsg);
logService.save(log).subscribe();
}).subscribeOn(Schedulers.boundedElastic()).subscribe();
}
}
```
### 3.3 需要记录的操作
#### 用户管理模块
- `createUser()` - 创建用户
- `updateUser()` - 更新用户
- `deleteUser()` - 删除用户
- `changePassword()` - 修改密码
- `assignRoles()` - 分配角色
#### 角色管理模块
- `createRole()` - 创建角色
- `updateRole()` - 更新角色
- `deleteRole()` - 删除角色
- `assignPermissionsToRole()` - 分配权限
#### 菜单管理模块
- `createMenu()` - 创建菜单
- `updateMenu()` - 更新菜单
- `deleteMenu()` - 删除菜单
#### 其他模块
- 系统配置:创建、更新、删除配置
- 数据字典:创建、更新、删除字典
- 公告管理:创建、更新、删除公告
## 4. 测试策略
### 4.1 单元测试
**`OperationLogAspectTest`**:测试切面的核心逻辑
- 测试成功场景:方法执行成功,日志正确记录
- 测试失败场景:方法抛出异常,日志记录错误信息
- 测试响应式场景:Mono返回值的处理
- 测试上下文获取:用户、IP等信息的正确获取
### 4.2 集成测试
**`OperationLogIntegrationTest`**:测试完整的日志记录流程
- 调用带注解的API接口
- 验证日志是否正确保存到数据库
- 验证日志内容的完整性
### 4.3 E2E测试
在现有E2E测试中验证:
- 执行用户管理操作后,检查Dashboard操作日志数量是否增加
- 验证操作日志显示是否正确
## 5. 部署计划
### 5.1 阶段1:开发与测试(当前)
1. 创建`@OperationLog`注解
2. 实现`OperationLogAspect`切面
3. 编写单元测试和集成测试
4. 在关键Handler方法上添加注解
### 5.2 阶段2:验证
1. 运行所有测试,确保功能正常
2. 手动测试Dashboard操作日志显示
3. 验证日志记录不影响系统性能
### 5.3 阶段3:上线
1. 提交代码到Git
2. 更新文档
3. 部署到开发环境验证
## 6. 性能考虑
- **异步保存**:日志保存使用异步方式,不阻塞主业务流程
- **索引优化**:数据库表已有索引(created_at, username
- **日志清理**:建议后续添加定时任务清理历史日志(保留最近3个月)
## 7. 风险与缓解
| 风险 | 影响 | 缓解措施 |
|------|------|----------|
| 日志记录失败影响业务 | 高 | 使用异步保存,失败时只记录错误日志,不影响业务流程 |
| 日志量过大影响性能 | 中 | 只记录关键操作,使用异步保存,定期清理历史日志 |
| 敏感信息泄露 | 高 | 参数序列化时排除敏感字段(如password) |
## 8. 后续优化
1. **日志查询优化**:添加更多查询条件(时间范围、操作类型等)
2. **日志导出功能**:支持导出操作日志为Excel
3. **日志统计分析**:统计用户操作频率、操作类型分布等
4. **日志清理任务**:定时清理历史日志,保留最近3个月数据