You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

69 lines
8.5 KiB

5 hours ago
# 02 全局日志 crm-log 模块(A7-4,新建模块)
Type: task
Status: resolved
## Question
按一期票 06 拍板(形态 A:新全局表 + AOP 采集;域内三表不动),新建 crm-log 轻模块,补齐 A7-4 日志三页 5 端点。素材:[matrix-a7-4-a7-5.md](../../a7-parity/matrix-a7-4-a7-5.md) §一 + 文本库 `A7-4-1_用户操作日志.md` / `A7-4-2_登录日志.md` / `A7-4-3_批量操作日志.md`
1. **新模块 crm-log**:pom + crm-app 依赖 + CONTEXT.md(挂 CONTEXT-MAP.md);轻模块不并入 crm-auth(票 06 拍板)。
2. **表设计**(三表贴合原型三页列结构,票内可微调):
- 操作日志:op_time / operator_name / dept_name / account / ip / op_type(登录/查询/新增/修改/删除/处理/导入/导出 8 枚举)/ content / result(成功/失败)
- 登录日志:op_time / operator_name / dept_name / account / ip / status / op_type(登录/退出)
- 批量操作日志:公共列 + op_type(导入/导出/修改/删除)/ page(操作页面)/ result(成功/失败/部分成功)/ 计数明细文本 / 结果文件 id
3. **采集机制**(票内定 AOP 切面 vs 内联埋点;仓库现无 @Aspect 先例,AOP 依赖需核实):写端点 POST + 登录/退出(crm-auth AuthController)+ 批量操作(crm-base BatchResult 写侧,LeadPool 导入导出已有调用点)。⚠️ 原型枚举含「查询」——全量 GET 采集代价大,覆盖面(全采 vs 写操作+登录/批量先行)为本票显式拍板项。采集失败不阻塞业务主流程。
4. **批量结果文件**:复用 crm-file/MinIO 落盘;90 天保留期(GET 过期返「文件已过期」)+ 过期清理定时任务(参照 OrphanChunkCleanupTask)。
5. **5 端点**(`/api/log` 下,扁平动词 + 查询参数规范 ADR-0017):POST /operation/page、POST /login/page、POST /batch/page(三 PageParam 逐字段对齐矩阵 §一筛选清单:所属组织模糊/操作人模糊/时间范围/opType/批量 result)/ POST /batch/download(ids[] 打包下载)/ GET /batch/file?id=。
6. **权限口径**:仅系统管理员及审计权限角色可见(原型三页一致);权限种子 @Order(26) + 菜单「A7 后台管理/日志管理」(票 01 用 25,两票并行不撞号)。
7. **tag**:「A7 后台管理/日志管理/用户操作日志」「…/登录日志」「…/批量操作日志」。
8. **bruno-sync**:sourceRoots 加 crm-log;menuBindings 加日志管理菜单;文档 5 份新增。
9. 错误码 65xxx 起(如文件过期 65001)。
验收基线:编译过 + BOM 0 + crm-log 单测全绿 + 全仓回归。
## Answer
### 开工前两问拍板(2026-09-08,先于建表写码)
**① 采集机制 = 混合:AOP 注解切面兜底写端点 + 内联埋点采登录/退出/批量(SPI 缝合)**
依赖核实:全仓 9 模块 pom 均无 `spring-boot-starter-aop`/`aspectjweaver`,代码无 `@Aspect` 先例;但 `@Transactional` 代理基建已在用(`AuthServiceImpl` self 注入注释明说「走 Spring AOP 事务代理」,spring-aop 随 spring-context 在 classpath),启用注解切面仅需补 `spring-boot-starter-aop`(引入 aspectjweaver,版本由 spring-boot-parent 3.3.5 管),风险低。
利弊摆完:
- 纯 AOP 拿不到两类关键上下文:登录**失败**行(A7-4-2 原型明确有失败行)——异常路径只有错误码无操作人;批量日志的**操作页面/计数明细/结果文件 id**——导出还是 void 写 response 切不到返回值。这两类只能内联。
- 纯内联:全仓 188 个 POST 写端点逐个埋点侵入大、漏埋率高、新增端点易忘。
- 混合各取所长:AOP 兜底切面自动覆盖全 POST(URL 关键词映射 op_type:save/create→新增、update/edit→修改、delete→删除、import→导入、export→导出、其余→处理,content=URI 摘要),`@OpLog` 注解可覆写语义内容;登录/退出/批量走 crm-base SPI(`OpLogPort`,`UserTransferPort` 同构先例)内联埋点。
依赖方向零循环:crm-base 定义 `OpLogPort`;crm-log 实现 SPI + 切面 + 三表/端点,依赖 crm-auth(部门名解析,crm-rule 先例)与 crm-file(批量结果文件);crm-auth / crm-rule 仅依赖 crm-base 接口埋点,不反向依赖 crm-log。切面排除 `/api/auth/**`(登录走内联埋点更精确,避免重复采)与 `/api/log/**`(日志查询自产自销)。
**② 采集覆盖面 = 写侧先行(写 POST + 登录/退出 + 批量),不采「查询」**
量化:全仓 GET 81 / POST 188。全采「查询」= 81 个 GET 全挂 + 每次列表页刷新产行,行数爆炸、审计价值密度低;写侧先行覆盖审计核心诉求(谁改了什么/谁登了/批量结果),`op_type` 枚举仍定义原型 8 项(筛选下拉不缺项),采集侧现阶段不产「查询」行,未来要采零改表。符合 map.md 纪律「对齐原型操作类型枚举即可,不追求全端点全采」。
### 实现清单
| 票面项 | 落地 |
| --- | --- |
| 1 新模块 | `crm-log`(pom + root pom modules/dependencyManagement + crm-app 依赖 + CONTEXT.md 挂 CONTEXT-MAP) |
| 2 三表 | `sys_log_operation` / `sys_log_login` / `sys_log_batch`(Hibernate columnDefinition 管 DDL + MyBatis-Plus BaseMapper 管 CRUD,均继承 BaseEntity;op_time 建索引) |
| 3 采集机制(拍板①混合) | `OpLogAspect`(全仓首个 @Aspect,切 `execution(* com.crm..*Controller.*(..))`,只采 POST,URL 关键词映射 op_type,`@OpLog` 注解可覆写;排除 `/api/auth/` `/api/log/` `/api/file/`)+ `OperationLogCollector`(OpLogPort 实现:快照补全/IP/结果文件落盘,全入口自吞异常) |
| 3 内联埋点 | crm-auth `AuthServiceImpl` 5 钩子(login 成功 / confirmLogin 顶号成功 / doLoginInTx 账号禁用失败 / verifyOrgMembership 非组织成员匿名失败 / logout 退出);crm-rule `LeadPoolServiceImpl` 3 处(批量删除 / 导入含失败明细 / 导出含 Excel 留档);埋点方一律 `ObjectProvider<OpLogPort>` 容忍缺 Bean,依赖方向零循环 |
| 4 结果文件 | collector 统一落盘(导出留档载荷优先,failures 自动生成错误报告 Excel,FileApi.upload bizDomain=log)+ 下载时校验 90 天(66001)+ `LogFileCleanupTask`(cron `crm.log.cleanup-cron` 默认 3:30,与 crm.file 3:00 错峰;删文件 + 置空 fileId 防重扫,日志行长期保留) |
| 5 端点 | 5 端点全部落地(三页 POST 分页 + POST /batch/download zip 打包重名去重 + GET /batch/file 流式下载对称 FileController) |
| 6 权限 | LogController 类级 `@PreAuthorize("hasRole('ADMIN')")`;`LogPermissionInitializer` @Order(26) 种子(菜单「A7 后台管理/日志管理」path=/log,仅 ROLE_ADMIN) |
| 7 tag | 三组深 tag 按 ADR-0024 挂齐(方法级多值 tags) |
| 8 bruno-sync | sourceRoots + menuBindings 三页配置;文档 5 份已生成到文档仓 `A7 后台管理/日志管理/`(三页文件夹) |
| 9 错误码 | 66001 文件已过期 / 66002 无可下载结果文件(偏差:66xxx 段,见下) |
单测:crm-log 34 用例(aspect 12 / collector 7 / service 11 / cleanup 4);全仓回归 1273 用例全绿(含 crm-auth AuthServiceImplTest 18、crm-rule LeadPoolServiceImplTest 28 埋点兼容回归)。实现过程修复一处自暴露缺陷:MP 条件方法 boolean 前缀不阻止参数求值,`startOfDay(null)` NPE → startOfDay/endOfDay 改空安全。
### 偏差
1. **错误码 65xxx → 66xxx**:票面 9 与 map.md 预核均为 65xxx 起,但 README 错误码表证实 65001~65999 已被 crm-lead 占用 → 改取 66xxx 段(66001/66002),README 已补登记行。map.md 预核笔误一并勘误:62xxx=crm-file、65xxx=crm-lead(预核写反)。
2. **切面排除清单多一项 `/api/file/**`**:拍板时只列 auth/log 两项,实现时补排除(分片上传高频 POST,非业务写操作),CONTEXT.md 已记录。
3. **登录失败行仅覆盖两种可定位场景**(账号禁用、非组织成员-匿名行):其余失败形态(钉钉调用异常等)无本地操作人,不落库。
4. **批量埋点本票只接线 LeadPool 一处**(票面指定调用点):其余模块未来新增批量导入/导出时按 BatchOpLog SPI 补埋,零改表。
5. **菜单粒度**:「日志管理」一级菜单(path=/log,component=log/index),三页为前端 tab;未为三页各建菜单行(bruno 文档仍按三页深 tag 分文件夹)。
6. **「查询」枚举不产行**(拍板②写侧先行的自然结果):op_type 8 枚举保留定义,筛选下拉不缺项,采集侧现阶段不产「查询」行。