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
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
# 操作日志记录功能设计文档
|
||||
|
||||
**日期**: 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个月数据
|
||||
Reference in New Issue
Block a user