From 03a9eb73c75121be87a0020ae3357d26f57962e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Fri, 3 Apr 2026 19:26:16 +0800 Subject: [PATCH] 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 --- docs/plans/2026-04-03-operation-log-design.md | 261 ++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 docs/plans/2026-04-03-operation-log-design.md diff --git a/docs/plans/2026-04-03-operation-log-design.md b/docs/plans/2026-04-03-operation-log-design.md new file mode 100644 index 0000000..842670e --- /dev/null +++ b/docs/plans/2026-04-03-operation-log-design.md @@ -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个月数据