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.
11 KiB
11 KiB
线索模块缺陷验证与修复报告
范围:
tmp/缺陷报告-线索模块-A2-A7-3-1.md的 22 项缺陷(P0×7 / P1×8 / P2×7) 方法:蓝湖原型(MCP 实时版 2026-08-15)× PRD v1.2 × 代码现状(2026-08-19 逐行复核) 语义优先级:PRD > 原型 > 缺陷报告;用户决策 > PRD 执行时间:2026-08-19
一、缺陷确认清单(22 项全量)
裁决汇总
| 类别 | 数量 | 编号 |
|---|---|---|
| 实现遗漏 | 9 | P0-1/2/3/4/6/7、P1-1/3/6 |
| 真实缺陷 | 3 | P1-2/7/8 |
| 误报 | 8 | P0-5、P1-4/5、P2-1/2/3/6/7 |
| 需求变更 | 0 | — |
| 用户决策关闭 | 2 | P2-4(已修)、P2-5(关闭) |
P0(7 项)
| 编号 | 裁决 | 原型依据 | PRD 依据 | 代码现状(修复前) | 处置 |
|---|---|---|---|---|---|
| P0-1 | 实现遗漏 | A7-3-1"已存在线索绑定的池禁止删除" | §4.3"池下无非终态线索" | deletePool 无占用检查,64005 从未产出 | 票 05 已修:跨模块 port + deletePool 守卫 |
| P0-2 | 实现遗漏 | A7-3-1"达到上限禁止领取" | §6.2 前置 2/3 | claimLead 无 hold/daily 校验 | 票 06 已修:ClaimLimitChecker |
| P0-3 | 实现遗漏 | —(PRD 主导) | §6.3 分配走两上限 | assignToUser 无上限校验 | 票 06 已修:复用 ClaimLimitChecker |
| P0-4 | 实现遗漏 | A2-1-2-2"勾选则自动领取" | §6.1 副作用+§6.2 前置 | createLead claimOnCreate 绕过全部校验 | 票 06 已修:claimOnCreate 走完整校验链 |
| P0-5 | 误报 | A7-3-1"仅管理员可分配" | §6.2 前置 1 | claimLead L217 已拦 ADMIN_ONLY→65005 | 无需修复 |
| P0-6 | 实现遗漏 | A2-1-1 混合展示 | §7(E) 可配置状态集 | PUBLIC_POOL 硬编码 status=PENDING | 票 07 已修:可配置状态集 |
| P0-7 | 实现遗漏 | A2-1-4/A2-1-4-1 角色分支 | §6.1 grill #6 | createLead 无角色判定,销售可建未分发 | 票 07 已修:角色分支+选人即分配 |
P1(8 项)
| 编号 | 裁决 | 原型依据 | PRD 依据 | 代码现状(修复前) | 处置 |
|---|---|---|---|---|---|
| P1-1 | 实现遗漏 | —(PRD §6.4) | §6.4 清除草稿行 | submitFeedback 提交后不清 DRAFT | 票 08 已修:submitFeedback 提交后清 DRAFT |
| P1-2 | 真实缺陷 | A2-1-4"仅管理员"(被 PRD 推翻) | §3.3 管理员OR创建人 | isCreator 死变量+DataScope 兜底假设不成立 | 票 08 已修:service 显式权限守卫 |
| P1-3 | 实现遗漏 | A2-1-2-1 历史记录 | §6.9/§6.10 字段级 diff | editLead 只记 leadName 单值 | 票 08 已修:字段级 old→new diff |
| P1-4 | 误报 | A2-1-2-1"未过期失效可见" | §3.3 矩阵 | guard 白名单已完备(FeedbackCmd) | 无需修复(单测确认) |
| P1-5 | 误报 | A7-3-1 联动刷新(前端语义) | §4.1.1 dept_id 跟池走 | PoolChangedEventListener 已实现 | 无需修复(常量修复在票 09) |
| P1-6 | 实现遗漏 | A7-3-1"次日起按新规则" | §5.3 刷新公式 | N 重算无代码,M 注释自认预留 | 票 09 已修:3 条 setSql deadline 重算 |
| P1-7 | 真实缺陷 | A2-1-1 实时统计 | ADR-0023 D3 分组计数 | countStats selectList 内存循环 | 票 09 已修:SQL GROUP BY 聚合 |
| P1-8 | 真实缺陷 | —(PRD/ADR 主导) | §6.7+ADR-0019 顺序硬保证 | 两独立 @Scheduled 软间隔竞态 | 票 09 已修:合并为 LeadMaintenanceJob |
P2(7 项)
| 编号 | 裁决 | 原型依据 | PRD 依据 | 代码现状(修复前) | 处置 |
|---|---|---|---|---|---|
| P2-1 | 误报 | A2-1-1 防抖(前端) | §6.2 CAS 语义 | applyClaim CAS+checkCas 65010 | 无需修复 |
| P2-2 | 误报 | A7-3-1"≥0 整数" | §4.3 默认值 | validatePool 四项全齐(hold≥1→≥0 票 06 修) | 票 10 已修:注释 ≥1→≥0 |
| P2-3 | 误报 | A7-3-1"全局唯一" | — | 唯一校验+@TableLogic+deleteKey | 无需修复 |
| P2-4 | 实现遗漏 | A7-3-1"拦截提交" | §9 写权双守 | savePool 无角色判定 | 票 10 已修:非管理员 deptId/geography 变更→拒绝 |
| P2-5 | 实现遗漏* | A7-3-1 批量按钮隔离 | §9 RBAC | 全接口 fail-open | 用户决策 B 关闭:维持 fail-open |
| P2-6 | 误报 | A2-1-1 反馈三态 | — | 快照列+草稿不刷 | 无需修复 |
| P2-7 | 误报 | A2-1-2-1 终态收敛 | §6.9+§3.3 | guard+editLead 终态锁完备 | 无需修复 |
二、修复方案摘要
Ticket 05:P0-1 池删除守卫(跨模块 seam)
- 方案:crm-rule 定义
PoolLeadOccupationPort(outbound port 接口),crm-lead 实现PoolLeadOccupationAdapter(查lead表status IN (1,2,3,4)) - 改动文件:
PoolLeadOccupationPort.java(新)、PoolLeadOccupationAdapter.java(新)、LeadPoolServiceImpl.java(注入 ObjectProvider + deletePool 守卫) - ADR:ADR-0025(跨模块 port seam);双模块 CONTEXT.md 更新
- 单测:+4(拒删/放行/port null/批量部分失败)
Ticket 06:P0-2/3/4/5 额度校验链
- 方案:新建
ClaimLimitChecker组件——hold 计数status IN (3,4)+ daily 计数当日 CLAIM 日志;claimLead/assignToUser/createLead 三入口注入校验 - 用户决策:hold=0=字面 0(不是无限),validatePool holdLimit≥1→≥0
- 改动文件:
ClaimLimitChecker.java(新)、LeadServiceImpl.java、LeadPoolServiceImpl.java - 单测:+7(ClaimLimitCheckerTest)+ LeadServiceImplTest 额度边界
Ticket 07:P0-6/7 + 选人即分配
- P0-6:PUBLIC_POOL 视图从硬编码
status=PENDING改为可配置状态集(LeadConstants.PUBLIC_POOL_VISIBLE_STATUSES = {2,3,4}) - P0-7:createLead 角色分支——销售强制回填主部门池(无池报错),管理员可指定任意池/未分发
- 选人即分配:createLead 4 参数重载(+assignToUserId),管理员选人→创建即分配(走额度校验)
- 改动文件:
LeadViewQueryImpl.java、LeadServiceImpl.java、LeadController.java、LeadConstants.java - 单测:LeadServiceImplTest +3(主部门无池/管理员建未分发/选人即分配)+ LeadViewQueryImplTest 更新
Ticket 08:P1-1/2/3 service 守卫
- P1-1:
LeadServiceImpl.submitFeedback在 transition 提交成功后,删除同 (user_id, lead_id) 的 DRAFT 行(草稿随提交转为 SUBMITTED,见 LeadServiceImpl L405-408) - P1-2:deleteLead 显式权限守卫
(状态≠CONVERTED) AND (管理员 OR 创建人),SecurityUtils.hasRole 判定 - P1-3:editLead 对 §6.9 可编辑 13 字段做 old→new diff,写入 history
- 改动文件:
LeadServiceImpl.java(P1-1 草稿清理在 submitFeedback / P1-2 / P1-3) - 单测:LeadServiceImplTest 守卫测试
Ticket 09:P1-5/6/7/8 联动/Job/统计
- P1-5:常量修复
in(status, 1, 2)→in(status, STATUS_UNDISTRIBUTED, STATUS_PENDING) - P1-6:3 条 setSql deadline 重算(CLAIMED→claim_time+N、FOLLOWING→feedback_time+N、非CONVERTED→create_time+M)
- P1-7:countStats 从 selectList 内存循环改为 3 次 selectMaps + GROUP BY 聚合
- P1-8:LeadExpireJob + LeadRecycleJob 合并为 LeadMaintenanceJob(单一 @Scheduled 串行 expire→recycle)
- 改动文件:
PoolChangedEventListener.java、LeadViewQueryImpl.java、LeadMaintenanceJob.java(新)、LeadTransition.java(javadoc) - 删除文件:
LeadExpireJob.java、LeadRecycleJob.java - 单测:+4(PoolChangedEventListenerTest)+1(LeadMaintenanceJobTest)+ LeadViewQueryImplTest 更新
Ticket 10:P2-4/2/5 健壮性
- P2-4:savePool 角色拦截——非管理员编辑时 deptId/geography 变更→拒绝(CODE_POOL_INVALID),用 Set 集合比较 geography 忽略顺序
- P2-2:LeadPoolDTO/LeadPool 注释 ≥1→≥0,0=不可领取(validatePool ≥0 已在票 06 修)
- P2-5:用户决策 B——维持 fail-open,不种子化 button 权限点(记风险项)
- 改动文件:
LeadPoolServiceImpl.java、LeadPoolDTO.java、LeadPool.java - 单测:+4(P2-4 角色拦截)+ 适配 1(mockStatic SecurityUtils)
三、回归测试结果
详见 回归测试结果.md
全量单测:BUILD SUCCESS
| 模块 | 测试用例数 | Failures | Errors |
|---|---|---|---|
| crm-base | 33 | 0 | 0 |
| crm-rule | 30 | 0 | 0 |
| crm-lead | 97 | 0 | 0 |
| 全 9 模块合计 | 447+ | 0 | 0 |
前后对比:crm-lead 6→9 测试类(+3 新增),crm-rule LeadPoolServiceImplTest 18→22 用例。
BOM 扫描:ALL CLEAN
本 effort 修改的 19 个 .java 文件逐个检查无 BOM。全库扫描因文件量大超时,未修改文件可由既有 mvn compile 通过推定无 BOM。
接口模拟:降级为单测覆盖映射
沙箱环境无法启动 Spring Boot 服务。6 个 P0 场景均有对应单测覆盖(详见回归测试结果.md §三)。
构建前提(复核者须知):本 effort 修改了 crm-base 的
SecurityUtils.hasRole(String)。若直接mvn test -pl crm-rule,crm-lead而未先刷新 crm-base 本地仓产物,会报NoSuchMethod 'SecurityUtils.hasRole'(用的是旧 jar)。正确顺序:先mvn -pl crm-base install -DskipTests(或带-am全链重装),再跑下游模块测试。已验证:刷新后 crm-rule 30 + crm-lead 97 全绿。
四、建议后续(不做,只建议)
4.1 前端配合点
- 新增错误码 toast 文案:65006(每日超限)、65007(持有超限)已激活但前端无对应文案
- 按钮置灰:P2-1 防抖/置灰属前端交互(后端 CAS 兜底已存在)
- 公海列表领取按钮:仅
status=待领取行可点(后端可配置状态集已支持混合展示,前端需按行状态控制按钮 disabled) - 新增线索表单:管理侧"选人即分配"需前端传
assignToUserId参数;销售侧 poolId 字段需隐藏(后端强制回填主部门池)
4.2 Bruno API 文档同步
以下接口行为/错误码在本 effort 发生变化,Bruno collection 需同步:
| 接口 | 变化点 |
|---|---|
| DELETE /pool/{id} | 新增 64005(池下有活跃线索) |
| DELETE /pool/batch | 批量删除产出 HAS_ACTIVE_LEAD 失败项 |
| POST /lead/claim | 新增 65006/65007(额度超限) |
| POST /lead/assign | 新增 65006/65007(被分配人额度超限) |
| POST /lead | claimOnCreate 超限时整体失败;新增 assignToUserId 可选参数 |
| PUT /pool | 非管理员改归属字段→64001(CODE_POOL_INVALID) |
4.3 风险项
- P2-5 fail-open 风险:批量删除/批量分配等接口无后端角色校验(用户决策 B 维持),可被绕过前端隐藏直接调接口。建议后续在 PermissionInitializer seam 补齐 button 权限点种子时统一收口。
- 集成环境验证缺口:JWT 认证链路、@DataScope 实际 SQL 注入、PoolChangedEvent 跨模块事件、@Scheduled 定时触发均未在集成环境验证(单测用 mock 模拟)。
4.4 文档更新
- ADR-0025(跨模块 port seam)已落
docs/adr/ - crm-rule / crm-lead CONTEXT.md 已更新(ticket 05)
- 建议 PRD v1.3 补录:管理侧"选人即分配"流程(A2-1-4-1 原型有页、PRD §6.1 未定义,本 effort 按原型+用户拍板实现)