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.
 
 
 
 
 
 

12 KiB

确认清单 — P2(缺陷报告 P2-1 ~ P2-7 三方判定)

方法:原型条款(research/原型依据基准.md)× PRD v1.2 × 代码现状(2026-08-19 逐行复核)。 语义优先级:PRD > 原型 > 缺陷报告;用户既有决策与需求冲突处不自行裁决,列入 §三待确认。 判定口径:真实缺陷 = 已有代码路径在特定场景产生错误结果(越权/脏数据/竞态);实现遗漏 = 定稿要求的逻辑从未实现;误报 = 代码已满足 PRD/原型。

一、汇总

编号 裁决 一句话结论 修复票
P2-1 误报 领取 CAS 幂等已落地(eq status=PENDING + checkCas 65010"已被他人领取"),防抖/置灰属前端 out of scope
P2-2 误报 validatePool 四项数值校验全齐(N/M≥1、daily≥0、hold≥1);报告"daily/hold 均 0=无限"失实(原型仅授权 daily=0) —(hold=0 留白见 §三-2)
P2-3 误报 池名唯一已校验(存活记录查重)+ @TableLogic 全局逻辑删除自动排除软删行 + deleteKey 复用键支持同名重建
P2-4 实现遗漏 savePool 无角色判定:任何调用者可改任何池的归属字段(dept/team/省份),原型"拦截提交"守卫缺失 10
P2-5 实现遗漏* 两 Controller 全接口无权限注解(fail-open),批量/删除接口可被绕过前端直调——与既有用户决策冲突,待确认 10(待确认)
P2-6 误报 反馈三态/内容/最后反馈时间 = 主表快照列,提交时刷新、草稿明确不刷(注释自证),无草稿污染路径
P2-7 误报 状态白名单兜底完备:guard 声明式守卫 + editLead 终态锁(CONVERTED/EXPIRED 拒编辑,按 PRD §6.9 解决原型页间矛盾)

裁决分布:误报 5 / 实现遗漏 2(其中 P2-5 待用户确认是否推翻既有决策)/ 真实缺陷 0 / 需求变更 0。 缺陷报告 P2 段可信度:7 项中 5 项误报(多为"建议核对"类预防性条目,核对后代码已满足)、2 项属实(P2-4/5 同属写路径权限守卫缺失家族)。

二、分项证据链

P2-1 领取并发防抖/幂等(后端兜底)— 误报

  • 原型(A2-1-1):防抖(500ms)+请求中置灰+成功后禁用3秒——纯前端交互条款(基准 §P2-1 );"多人同时点【领取】时,随机选择成功人员"——后端语义=恰好一人成功。
  • 代码LeadTransitionImpl.applyClaim() L120-128 CAS 更新 eq(Lead::getId).eq(Lead::getStatus, STATUS_PENDING)——数据库行级原子 UPDATE,并发下恰好一人成功;L129 checkCas 抛 CODE_CAS_FAIL(65010)"领取失败:线索可能已被他人领取,请刷新后重试"——明确错误码供前端 toast,正是报告要求的后端兜底,且已在 ADR-0021 全量落地。
  • 处理:无需修复。前端防抖/置灰属 out of scope(map 明确本 effort 不动前端)。

P2-2 数值输入校验(每日上限/持有上限/N/M)— 误报(hold=0 留白)

  • 原型(A7-3-1):daily/hold"仅允许输入≥0 的整数";N/M"仅支持≥1 正整数";"非法输入拦截提交"(基准 §P2-2 )。
  • 代码LeadPoolServiceImpl.validatePool() 四项全齐——recycleDays≥1(L206-208)、expireDays≥1(L209-211)、dailyClaimLimit≥0(L212-214)、holdLimit≥1(L215-217),非法值抛 CODE_POOL_INVALID 拦截提交
  • 报告失实点:整改建议"daily/hold ≥0 整数(0=无限制)"——"0=无限"原型仅明说于每日领取上限(基准 §P2-2 备注);hold=0 语义原型留白、PRD §4.3 未定义,代码按实体定稿注释"≥1"(LeadPool.java L56-58)保守拒绝。
  • 处理:主体无需修复。hold=0 输入合法性(原型字面允许 ≥0 vs 代码 ≥1)为原型留白点,低优先,见 §三-2。

P2-3 公海池名称全局唯一 — 误报

  • 原型(A7-3-1):"全局唯一不可重复"(基准 §P2-3 )。
  • 代码validatePool() L222-231 存活记录查重(eq(poolName).ne(编辑排除自身).count()>0)→ 抛 CODE_POOL_NAME_DUPLICATE"公海池名称已存在"。
  • 软删行规避:BaseEntity.deleted 挂 @TableLogic + application.yml 全局逻辑删除配置 → MyBatis-Plus 查询自动追加 deleted=0,软删行天然不参与查重;LeadPool deleteKey 复用键(uk_pool_name(pool_name, delete_key),删除时 delete_key=id)支持软删后同名重建——双重保障。
  • 处理:无需修复。

P2-4 部门负责人编辑时归属字段应只读 — 实现遗漏

  • 原型(A7-3-1):"部门负责人:编辑时下拉置灰只读";"部门负责人修改归属字段时拦截提交"(基准 §P2-4 )——实现方式已定稿=拦截(拒绝提交),非忽略变更,无需问用户。
  • PRD:§9 定稿写权由"权限点 + service"双守。
  • 代码LeadPoolServiceImpl.savePool() 无任何角色判定——任何调用者可改任何池:deptId 直接入库(L108-110,teamDeptId 直接跟随覆盖)、省份关联全量替换(L124);@DataScope 只做读过滤不拦写。部门负责人越权改归属(绕过前端置灰直接调接口)畅通无阻。
  • 修复方向(票 10):savePool 增加角色分支——操作者非超管(=部门负责人)时:① 仅可编辑本部门池;② 归属字段(deptId/teamDeptId/省份关联)发生变更 → 拦截提交报业务错误码。角色判定机制与 P2-5/P0-7/P1-2 共用(见 §三-1)。

P2-5 批量操作按钮的角色隔离(后端校验)— 实现遗漏*(待用户确认)

  • 原型(A7-3-1/A2-1-4):"超级管理员可见:新增公海池、导入公海池、导出公海池、批量删除;部门负责人仅显示【查询/重置】,批量操作按钮全部隐藏";"删除:仅超级管理员可见"(基准 §P2-5 )。
  • PRD:§9 定稿 RBAC 三机制,写权=权限点 + service 双守。
  • 代码现状LeadPoolController(5 接口)与 LeadController(24 接口)全接口无权限注解;两类注释均声明:"不种子化 button 权限点(用户决策),ApiPermissionInterceptor 对未注册 URL fail-open"。即批量删除/批量分配等接口任何登录用户可调,"防绕过前端隐藏直接调接口"未落地。
  • 冲突(为何不能直接裁):用户建模块时已拍板"不种子化 button 权限点 + fail-open",本项需求(原型+PRD)与之正面冲突。按语义优先级(用户决策 > PRD),不可自行推翻 → 列 §三-1 待用户拍板。
  • 判定:用户确认补后端角色校验 → 实现遗漏(票 10);维持既有决策 → 本项按用户决策关闭(记最终报告风险项)。

P2-6 反馈情况三态与"最后反馈时间"回填 — 误报

  • 原型(A2-1-1/A2-1-2/A2-1-3):列表展示"反馈情况(有效/无效/未反馈)、反馈内容、最后反馈时间"(基准 §P2-6 )。
  • 代码:派生字段=Lead 主表快照列(feedbackStatus/Content/Time/ByUserId/ProductCode)——① 创建初始 FEEDBACK_NONE=未反馈(createLead L136);② 仅提交刷新applyFeedback L204-208);③ 草稿明确不刷快照saveFeedbackDraft L263-291,L290 注释"草稿不写 history,不刷主表快照",实现只写 lead_feedback 表 DRAFT 行)。列表/详情读主表快照直接返回(LeadDTO.fromEntity),无草稿污染路径
  • 处理:无需修复。报告的"避免草稿污染"担忧已被设计排除。

P2-7 过期失效/作废/已转商机线索的操作按钮收敛 — 误报

  • 原型(A2-1-2-1):"已转商机隐藏【释放】;过期失效隐藏【反馈】【释放】"(基准 §P2-7 );页间矛盾:A2-1-1"过期失效不能编辑"vs A2-1-2-1"过期失效仅支持编辑、取消关注"。
  • PRD(§6.9 定稿,裁决页间矛盾):"已转商机 / 过期失效 → 不可编辑(终态锁/只读);线索作废 → 可编辑";§3.3 流转矩阵 CONVERTED/EXPIRED 各操作全 ✗。
  • 代码:白名单兜底完备——① guard 声明式守卫(guard() L311-318 + TransitionCmd 各 Cmd allowedFromStatuses):release={已领取,跟进中}、feedback={已领取,跟进中,作废}、convert={已领取,跟进中}——CONVERTED/EXPIRED 调任何流转接口抛 CODE_STATUS_NOT_ALLOWED(65003);原型"已转商机可反馈"已被 PRD §3.3 推翻,代码按 PRD ;② 领取:ClaimCmd 仅 {待领取},EXPIRED 不可领(A2-1-1-1 条款);③ editLead L156-161 终态锁:CONVERTED 抛 CODE_LEAD_CONVERTED(65009)、EXPIRED 抛 65003——按 PRD §6.9 落地,正确解决原型页间矛盾;④ 作废可编辑符合 PRD(VOID 非终态)。
  • 处理:无需修复。

三、待用户确认

  1. P2-5 权限校验与既有决策冲突(高优先,影响票 07/08/10 共用机制选型):原型 + PRD §9 要求批量删除/批量分配/新增池等写接口做后端角色校验;但两 Controller 类注释记载既有用户决策"不种子化 button 权限点,未注册 URL fail-open"。选项:A. 推翻既有决策补角色校验(参照 crm-auth 角色管理"纯 hasAuthority + 数据种子"既有实践,或 service 层判定);B. 维持 fail-open,本项按用户决策关闭(记最终报告风险项)。该决定同时定调 P0-7(新增线索角色分支)/P1-2(删除的管理员判定)/P2-4(部门负责人拦截)的角色判定机制。
  2. P2-2 hold=0 输入合法性(低优先):原型字面允许持有上限"≥0 整数"但未定义 0 的语义("0=无限"仅明说于每日上限);代码按实体定稿注释"≥1"拒绝 0。维持 ≥1(现状,保守)还是放开 0=无限?默认维持现状,最终报告给建议即可。

四、结论

  1. P2-1~P2-7 判定完毕:误报 5(P2-1/2/3/6/7)、实现遗漏 2(P2-4、P2-5*),无真实缺陷、无需求变更。P2 段多为"建议核对"类预防性条目,核对后 5/7 已由既有设计满足(CAS 状态机、validatePool、@TableLogic 软删、反馈快照列、guard 白名单)。
  2. 两项实现遗漏同属写路径权限守卫缺失家族(P2-4 归属字段越权、P2-5 接口角色隔离),与 P0-7/P1-2 同根——根因是"不种子化 button 权限点 + fail-open"决策下写路径无角色判定基础设施。是否补齐由 §三-1 用户拍板统一裁决,拍板后票 07/08/10 共用同一机制落地。
  3. 修复映射:P2-4 → 票 10(savePool 角色拦截);P2-5 → 视 §三-1 拍板入票 10 或按既有决策关闭;其余 5 项无需修复。
  4. 判定口径与 P0/P1 清单一致;22 项判定至此全部完成(P0: 遗漏 6/误报 1;P1: 遗漏 3/真实 3/误报 2;P2: 遗漏 2/误报 5)。