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

线索模块缺陷验证与修复报告

范围: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(查 leadstatus 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.javaLeadPoolServiceImpl.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.javaLeadServiceImpl.javaLeadController.javaLeadConstants.java
  • 单测:LeadServiceImplTest +3(主部门无池/管理员建未分发/选人即分配)+ LeadViewQueryImplTest 更新

Ticket 08:P1-1/2/3 service 守卫

  • P1-1LeadServiceImpl.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.javaLeadViewQueryImpl.javaLeadMaintenanceJob.java(新)、LeadTransition.java(javadoc)
  • 删除文件LeadExpireJob.javaLeadRecycleJob.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.javaLeadPoolDTO.javaLeadPool.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 前端配合点

  1. 新增错误码 toast 文案:65006(每日超限)、65007(持有超限)已激活但前端无对应文案
  2. 按钮置灰:P2-1 防抖/置灰属前端交互(后端 CAS 兜底已存在)
  3. 公海列表领取按钮:仅 status=待领取 行可点(后端可配置状态集已支持混合展示,前端需按行状态控制按钮 disabled)
  4. 新增线索表单:管理侧"选人即分配"需前端传 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 风险项

  1. P2-5 fail-open 风险:批量删除/批量分配等接口无后端角色校验(用户决策 B 维持),可被绕过前端隐藏直接调接口。建议后续在 PermissionInitializer seam 补齐 button 权限点种子时统一收口。
  2. 集成环境验证缺口: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 按原型+用户拍板实现)