diff --git a/docs/plans/2026-03-17-project-bank-statement-tagging-logging-design.md b/docs/plans/2026-03-17-project-bank-statement-tagging-logging-design.md new file mode 100644 index 00000000..4e9973aa --- /dev/null +++ b/docs/plans/2026-03-17-project-bank-statement-tagging-logging-design.md @@ -0,0 +1,412 @@ +# 项目流水标签后端详细日志设计 + +## 概述 + +本次设计面向“项目流水标签”后端链路补充详细日志提醒能力,覆盖手动重算、自动触发、项目级互斥、规则级执行、参数解析、结果落库和自动补跑全过程。 + +目标同时满足两类需求: + +- 排障:出现“没有触发”“任务卡住”“规则没执行”“结果为 0”“自动补跑未生效”等问题时,能够通过日志快速定位断点 +- 审计:能够追踪是谁在什么时间,对哪个项目、哪个模型发起了手动重算,以及本次重算的结果摘要 + +本次设计只补应用日志,不调整数据库表结构,不新增前端展示,不引入 AOP、链路追踪框架或独立审计表。 + +## 已确认范围 + +- 日志面向“排障 + 审计”双目标 +- 日志保存位置沿用现有后端应用日志 +- 记录手动重算与自动触发两类入口 +- 记录项目级互斥、补跑标记、补跑消费过程 +- 记录任务级摘要、规则级执行、结果清理和结果写入 +- 记录规则参数解析来源和结果 +- 阈值参数值允许打印 +- 身份证号、账号、`objectKey` 等敏感字段不打印明文 +- 不打印 SQL 明细 +- 命中明细不按条展开到 `info` + +## 现状问题 + +当前与流水标签相关的主要代码位于: + +- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java` +- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/ProjectBankTagRebuildCoordinator.java` +- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolver.java` +- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java` + +现状上,“文件上传 / 拉取本行信息”链路已有较多日志,但“流水标签重算”核心链路日志不足,主要存在以下问题: + +1. 手动重算和自动触发进入标签链路后,缺少统一入口日志 +2. 项目级互斥和 `needRerun` 标记逻辑几乎不可观测 +3. 标签任务开始、结束、失败都没有清晰摘要 +4. 单条规则执行的耗时、命中数、阈值来源无法定位 +5. 结果清理和批量写库缺少过程确认 + +这会导致问题发生时难以区分: + +- 是入口未触发 +- 是被项目级互斥拦截 +- 是规则没有命中 +- 是规则执行失败 +- 是结果写库失败 +- 是补跑标记了但未真正再次执行 + +## 方案对比 + +### 方案一:在现有方法中直接补日志 + +- 在现有类中按节点直接补 `info / debug / warn / error` +- 不做统一模板 + +优点: + +- 改动最小 +- 落地最快 +- 与当前项目写法最接近 + +缺点: + +- 日志字段格式容易分散 +- 后续继续扩展时容易重复和漂移 + +### 方案二:统一字段格式的轻量日志设计 + +- 在现有类中补日志 +- 统一核心上下文字段和文案结构 +- 允许通过少量私有辅助方法减少重复 + +优点: + +- 兼顾快速落地和长期可检索性 +- 同时适合排障和审计 +- 不引入额外框架,风险较低 + +缺点: + +- 比方案一多一点整理成本 + +### 方案三:日志之外再做持久化审计摘要 + +- 除应用日志外,再把关键摘要落到任务表或独立审计表 + +优点: + +- 审计能力最强 +- 不依赖日志平台 + +缺点: + +- 超出本次“补详细日志提醒”的范围 +- 会引入表结构或数据模型变更 + +## 最终方案 + +采用方案二:统一字段格式的轻量日志设计。 + +具体原则如下: + +1. 只在现有后端类补日志,不改数据库结构 +2. `info` 负责任务摘要和关键里程碑 +3. `debug` 负责规则参数、命中数量、批处理细节 +4. `warn` 负责互斥、降级、参数缺失、无命中等非致命异常 +5. `error` 负责任务失败、规则异常、写库失败、触发失败 +6. 日志字段尽量统一,保证同一任务可以通过 `projectId/taskId` 串起来 + +## 日志等级设计 + +### info + +用于记录: + +- 手动重算入口 +- 自动触发入口 +- 获取项目锁成功 +- 任务创建成功 +- 规则加载完成 +- 历史结果清理开始 +- 单规则开始和结束摘要 +- 结果批量写入摘要 +- 任务成功摘要 +- 自动补跑开始和结束 + +### debug + +用于记录: + +- 规则阈值参数 +- 参数来源项目 +- 规则命中明细数量 +- 无需写库时的空结果分支 +- 补跑标记消费细节 + +### warn + +用于记录: + +- 手动重算被运行中任务拒绝 +- 自动触发命中运行中任务,仅标记 `needRerun` +- 自动触发被跳过 +- 规则参数缺失 +- 规则执行结果为空或无命中 + +### error + +用于记录: + +- 任务整体失败 +- 单规则执行异常 +- 结果写库异常 +- 参数解析过程中出现不可恢复异常 +- 自动触发或补跑异常 + +## 统一上下文字段 + +建议所有流水标签日志尽量带上以下字段: + +- `projectId` +- `taskId` +- `modelCode` +- `triggerType` +- `operator` +- `ruleCode` +- `costMs` +- `hitCount` + +其中: + +- 任务创建前无法取得 `taskId` 的场景允许缺省 +- 与单规则无关的日志可以不打印 `ruleCode` +- 与耗时无关的日志可以不打印 `costMs` + +## 脱敏规则 + +本次日志遵循以下脱敏边界: + +- 允许打印阈值参数编码和值 +- 不打印身份证号明文 +- 不打印账号明文 +- 不打印完整 `objectKey` +- 不打印规则 SQL +- 不在 `info` 级别展开逐条命中结果 + +若后续需要定位对象级命中,可在 `debug` 级别打印脱敏后的对象标识摘要,例如前 3 位加后 2 位,但本次设计不要求默认展开。 + +## 打点设计 + +### 1. 入口层 + +涉及类: + +- `CcdiBankTagController` +- `CcdiFileUploadServiceImpl` + +目标: + +- 明确日志是从手动入口还是自动入口进入 +- 对自动触发链路补“已触发 / 已跳过”判断 + +建议日志: + +```text +【流水标签】收到手动重算请求: projectId={}, modelCode={}, operator={} +【流水标签】批处理完成,准备触发自动重算: projectId={}, triggerType={}, anySuccess={} +【流水标签】跳过自动重算: projectId={}, triggerType={}, reason=all_records_failed +``` + +### 2. 协调层 + +涉及类: + +- `ProjectBankTagRebuildCoordinator` + +目标: + +- 观测项目级互斥 +- 区分“任务丢失”和“任务被合并补跑” +- 记录锁获取、锁释放、补跑消费全过程 + +建议日志: + +```text +【流水标签】手动重算开始排队: projectId={}, modelCode={}, operator={} +【流水标签】项目已有运行中任务,拒绝手动重算: projectId={}, modelCode={}, operator={} +【流水标签】项目正在重算,已标记完成后补跑: projectId={}, runningTaskId={}, triggerType={} +【流水标签】获取项目重算锁成功: projectId={} +【流水标签】检测到需要补跑,准备再次执行: projectId={}, previousTaskId={} +【流水标签】未检测到补跑标记,结束自动重算: projectId={}, taskId={} +【流水标签】释放项目重算锁: projectId={} +``` + +### 3. 执行层 + +涉及类: + +- `CcdiBankTagServiceImpl` + +目标: + +- 形成任务级生命周期日志 +- 形成规则级执行和写库摘要 + +建议日志: + +```text +【流水标签】任务创建成功: taskId={}, projectId={}, modelCode={}, triggerType={}, operator={} +【流水标签】加载启用规则完成: taskId={}, projectId={}, modelCode={}, ruleCount={} +【流水标签】开始清理历史结果: taskId={}, projectId={}, modelCode={} +【流水标签】规则开始执行: taskId={}, projectId={}, ruleCode={}, resultType={} +【流水标签】规则执行参数: taskId={}, ruleCode={}, thresholds={} +【流水标签】规则执行完成: taskId={}, projectId={}, ruleCode={}, hitCount={}, costMs={} +【流水标签】规则无命中: taskId={}, projectId={}, ruleCode={}, costMs={} +【流水标签】批量写入标签结果: taskId={}, projectId={}, resultCount={} +【流水标签】任务执行成功: taskId={}, projectId={}, modelCode={}, triggerType={}, ruleCount={}, hitCount={}, costMs={} +【流水标签】任务执行失败: taskId={}, projectId={}, modelCode={}, triggerType={}, error={} +``` + +### 4. 参数解析层 + +涉及类: + +- `BankTagRuleConfigResolver` + +目标: + +- 说明阈值从项目默认配置还是项目专属配置解析而来 +- 在参数缺失时明确记录缺了哪些编码 + +建议日志: + +```text +【流水标签】解析规则参数: projectId={}, effectiveProjectId={}, ruleCode={}, requiredParams={} +【流水标签】规则参数解析结果: projectId={}, ruleCode={}, thresholdValues={} +【流水标签】规则参数缺失: projectId={}, ruleCode={}, missingParams={} +``` + +## 关键异常场景设计 + +### 手动重算被拒绝 + +当项目已经存在运行中任务时: + +- 抛出原有业务异常 +- 额外补 `warn`,明确项目、模型、操作人和拒绝原因 + +目的: + +- 便于区分“接口未进来”和“接口进来了但被互斥挡住” + +### 自动重算被合并 + +当批量上传或拉取本行触发自动重算时,如果项目已在运行中: + +- 不再直接丢弃 +- 通过 `markNeedRerun` 标记补跑 +- 日志明确说明当前触发未丢失,而是等待本轮完成后自动重跑 + +### 参数缺失但继续执行 + +当前规则参数解析器未对所有缺失参数直接抛错,部分规则可能按空值或 `0` 继续执行。 + +此场景需要: + +- `warn` 记录缺失参数编码 +- `debug` 记录实际解析到的阈值集合 +- 文案中说明本次按当前默认值继续执行 + +### 单规则执行失败 + +当某条规则在查询或结果构造过程中抛异常时: + +- `error` 记录 `taskId/projectId/ruleCode` +- 保留原有失败传播语义 +- 由任务级失败日志补充整任务摘要 + +### 结果写库失败 + +当历史结果已清理但新结果写入失败时: + +- `error` 记录失败发生在“结果写入”阶段 +- 日志中带上准备写入的结果条数 + +这样可以避免误判为“规则无命中”。 + +### 自动补跑消费 + +自动重算完成后如果检测到 `needRerun=1`: + +- `info` 记录上一轮任务 ID 和将进入补跑 +- `debug` 记录补跑标记消费结果 + +如果没有检测到补跑标记,也应记录收尾日志,避免看到“开始”却看不到“结束”。 + +## 建议实现方式 + +为控制改动范围,本次不新增独立日志组件,优先采用以下实现策略: + +1. 在相关类中直接补充结构统一的日志 +2. 对重复字段较多的场景,可增加少量私有辅助方法拼接摘要 +3. 不为了日志而引入 ThreadLocal、MDC、AOP 或统一切面 + +这样可以把改动集中在当前标签链路相关类中,降低回归风险。 + +## 验证策略 + +本次重点验证“行为分支可被覆盖”,不建议编写对日志文本本身高度耦合的脆弱断言。 + +建议关注以下测试: + +### 协调器测试 + +- 已有运行中任务时,手动重算被拒绝 +- 自动触发时命中运行中任务并标记 `needRerun` +- 自动触发完成后成功消费补跑标记 + +### 标签服务测试 + +- 启用规则数为 0 +- 规则执行有命中且成功写库 +- 规则执行无命中时不写库 +- 单规则抛异常导致任务失败 + +### 参数解析测试 + +- 使用默认项目参数 +- 使用项目专属参数 +- 存在参数缺失时返回缺失状态 + +## 非目标 + +本次设计不包含以下内容: + +- 新增数据库表或审计表 +- 前端页面展示任务执行日志 +- 接入链路追踪系统 +- 打印规则 SQL +- 打印对象或账号明细 +- 调整标签任务执行策略或线程池模型 + +## 预期效果 + +落地后,开发和运维应能够通过日志快速回答以下问题: + +- 手动重算是否真正进入后端 +- 自动触发是否提交成功,还是因为整批失败而跳过 +- 项目当前是否被互斥锁住 +- 自动触发是否被合并为补跑 +- 本次任务创建了哪个 `taskId` +- 加载了多少条规则 +- 哪条规则执行最慢、命中多少 +- 参数是否来自默认配置,是否存在缺失 +- 结果是否已删除旧数据并完成新写入 +- 任务最终成功还是失败,失败在哪个阶段 + +## 落地范围 + +建议本次代码改动控制在以下文件附近: + +- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankTagController.java` +- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java` +- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/ProjectBankTagRebuildCoordinator.java` +- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolver.java` +- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java` + +如需进一步实现,可在此设计基础上继续拆分为具体实现计划。