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.
 
 
 
 
 
 

16 KiB

线索模块缺陷报告(A2 线索 / A7-3-1 线索规则·公海池)

范围:crm-lead(线索业务)+ crm-rule(线索规则/公海池配置) 依据:原型(蓝湖 A2-1-1~A2-1-5、A7-3-1)+ 线索业务 PRD v1.2 + 现有代码 说明:仅列出需要整改项,已实现且符合预期的部分不重复罗列。 状态常量:UNDISTRIBUTED=1 待分发 / PENDING=2 待领取 / CLAIMED=3 已领取 / FOLLOWING=4 跟进中 / CONVERTED=5 已转商机 / EXPIRED=6 过期失效 / VOID=7 作废


严重程度约定

级别 含义
P0 阻断性:违背核心业务约束,会造成数据错乱 / 越权 / 规则失效,必须先修
P1 严重:功能不完整或与原型/PRD 有明显出入,影响验收
P2 一般:健壮性、性能、体验类问题

P0 阻断性缺陷

P0-1 删除公海池未校验"存在活跃线索",允许删除有绑定线索的池

  • 位置crm-rule/.../service/impl/LeadPoolServiceImpl.javadeletePool() / deleteBatch()
  • 原型依据(A7-3-1 线索规则):

    "删除:仅超级管理员可见;单条删除前弹出二次确认;已存在线索绑定的公海池禁止删除。"

  • 代码现状deletePool() 直接软删除,未做任何线索占用检查。RuleConstants.CODE_POOL_HAS_ACTIVE_LEAD = 64005 已定义但从未被引用
  • 影响:删除后,该池下 UNDISTRIBUTED/PENDING/CLAIMED/FOLLOWING 线索变成孤儿(poolId 指向已删除池),公海列表、回收/失效定时任务、领取拦截全部错乱。
  • 整改建议:删除前执行 SELECT COUNT(*) FROM lead WHERE pool_id = ? AND status IN (1,2,3,4)

    0 时抛 CODE_POOL_HAS_ACTIVE_LEAD(64005)。批量删除逐个校验或聚合校验后整体拒绝。


P0-2 领取线索未校验"个人持有上限 / 个人每日领取上限"

  • 位置crm-lead/.../service/impl/LeadServiceImpl.javaclaimLead()
  • 原型依据(A7-3-1 其他设置 / A2-1-1 线索公海):

    "个人每日领取上限:限制单个销售每日从本公海池最多领取线索条数……输入 0 则代表无每日领取限制" "个人持有上限:单个销售名下同时持有的、未释放/未转商机的本公海线索最大总量……达到上限后销售无法再领取该池线索" "当销售达到每日领取上限、个人持有上限时,线索公海页面【领取】按钮置灰不可点击,悬浮提示已达对应上限。"

  • 代码现状claimLead() 仅做状态 CAS(PENDING→CLAIMED),无 hold_limit、无 daily_claim_limit 校验
  • 影响:销售可无限领取,突破公海池配置的持有/每日上限,规则形同虚设。
  • 整改建议:领取前,按目标池配置校验:
    • 持有上限:COUNT(*) FROM lead WHERE claim_user_id=? AND pool_id=? AND status IN (3,4)hold_limit → 抛 65007(limit=0 跳过)
    • 每日上限:统计当日该用户从该池领取历史条数 ≥ daily_claim_limit → 抛 65006(limit=0 跳过)

P0-3 管理员分配线索给销售时,未校验被分配人的持有/每日上限

  • 位置crm-lead/.../service/impl/LeadServiceImpl.javaassignToUser()
  • 原型/PRD 依据:分配与领取共用同一套"个人持有上限"约束(PRD §6.3);分配等价于让被分配人"持有"该线索,同样受上限约束。
  • 代码现状assignToUser() 无任何上限校验。
  • 影响:管理员分配可绕过上限,导致某销售名下线索超过持有上限,破坏规则一致性。
  • 整改建议:以被分配人(assignee)为主体,用目标池hold_limit/daily_claim_limit 做与 P0-2 相同的两项校验,超限抛 65007/65006。

P0-4 新增线索并勾选"直接领取"时,绕过领取规则与上限校验

  • 位置crm-lead/.../service/impl/LeadServiceImpl.javacreateLead()claimOnCreate=true 分支)
  • 原型依据(A2-1-2-2 新增线索):

    "是否领取:复选框,勾选则新增完成后自动领取该线索"

  • 代码现状claimOnCreate=true 时直接把线索置为 CLAIMED 并赋 claim_user,未走 claimLead 的规则/上限校验链路
  • 影响:销售通过"新增即领取"可绕过 claimRule(管理员可分配模式下本不应能自领)与持有/每日上限,形成后门。
  • 整改建议claimOnCreate=true 时复用 P0-2 的校验(claimRule + hold_limit + daily_claim_limit);不满足则创建为公海待领取状态或直接拒绝(按产品定稿)。

P0-5 领取规则"管理员可分配"未拦截销售自主领取

  • 位置crm-lead/.../service/impl/LeadServiceImpl.javaclaimLead()
  • 原型依据(A7-3-1):

    "选项2:管理员可分配 —— 销售仅能查看公海线索,无自主领取按钮,所有线索只能由管理员统一分配下发。"

  • 代码现状claimLead() 未读取所属池的 claim_rule,任何销售都能领取任意池线索。
  • 影响:设置为"仅管理员可分配"的公海池,销售仍可自领,权限模型被击穿。
  • 整改建议:领取前读取池 claim_rule;若为"仅管理员可分配",销售主体领取直接抛 CODE_CLAIM_RULE_DENIED(65005)。(前端置灰是展示层,后端必须兜底。)

P0-6 线索公海列表硬编码只展示"待领取",无法防撞单混合展示

  • 位置crm-lead/.../query/impl/LeadViewQueryImpl.javabuildViewWrapper()(PUBLIC_POOL 分支)
  • 原型依据(A2-1-1 列表视图):领取人列展示对应销售姓名,线索状态含"待领取/已领取/跟进中",且规则强调"一条线索仅可被 1 人领取""当前线索已被其他销售抢先认领……可关注该线索,待对方释放后再次认领" —— 说明公海需混合展示已被领取的线索用于防撞单,而非只显示待领取。
  • 代码现状buildViewWrapper() PUBLIC_POOL 硬编码 status = PENDING
  • 影响:公海只能看到待领取线索,销售无法看到"哪些已被别人领走",无法防撞单;与原型的领取人/状态列展示不符。
  • 整改建议:PUBLIC_POOL 视图改为可配置状态集合(至少 {PENDING, CLAIMED, FOLLOWING}),并保证"领取人"字段随状态回填。

P0-7 新增线索缺少角色约束(poolId 为空时应限管理员)

  • 位置crm-lead/.../service/impl/LeadServiceImpl.javacreateLead()
  • 原型依据(A2-1-4 线索管理 / A2-1-2-2):销售新增线索默认归属当前销售所属公海池,不可编辑poolId 由后端根据当前用户回填);管理员新增可切换任意公海池。销售不应能传入任意 poolId 或空 poolId 创建"未分发"线索。
  • 代码现状createLead() 未做角色判定;当 poolId=null 时的归属/权限处理缺失。
  • 影响:普通销售可能创建未分发线索或指定非自身池,越权。
  • 整改建议:非管理员且 poolId=null 时拒绝;销售创建强制回填其所属池,忽略前端传入的 poolId。

(P1 / P2 见后续追加)


P1 严重缺陷

P1-1 反馈提交后未清理草稿(DRAFT)行

  • 位置crm-lead/.../state/impl/LeadTransitionImpl.javaapplyFeedback()
  • 原型依据(A2-1-2-1 我的线索详情):反馈分"草稿/提交",历史记录 Tab 只应展示已提交反馈;草稿是临时暂存,提交后不应残留。
  • 代码现状applyFeedback() 插入 SUBMITTED 反馈行,但未 DELETE/UPDATE 同一 (user_id, lead_id) 的 DRAFT 行。
  • 影响:草稿残留,下次进入反馈弹窗回显旧草稿,或统计/历史混入草稿数据。
  • 整改建议:提交成功后删除(或状态置为已提交)该用户该线索的 DRAFT 行。

P1-2 删除线索未真正校验创建人/角色权限

  • 位置crm-lead/.../service/impl/LeadServiceImpl.javadeleteLead()
  • 原型依据(A2-1-4):

    "删除:仅管理员角色可见;过期失效、未分发、待领取线索可删除,已领取/已转商机线索删除增加二次强确认。"

  • 代码现状deleteLead() 计算了 isCreator 变量,但从未用于判定,删除权限实际不受控。
  • 影响:非管理员/非创建人可能删除他人线索,越权。
  • 整改建议:按角色(管理员)+ 状态白名单(EXPIRED/UNDISTRIBUTED/PENDING 可删)落地校验;isCreator 若为业务需要则真正参与判定,否则移除死变量。

P1-3 编辑线索未记录完整字段变更历史

  • 位置crm-lead/.../service/impl/LeadServiceImpl.javaeditLead()
  • 原型依据(A2-1-2-1 历史记录 Tab):

    "操作类型标签(领取/反馈/释放/转商机/编辑)、完整操作详情文本""所有操作永久留存至历史记录"

  • 代码现状editLead() 更新字段但未记录 old→new 字段级差异到 history。
  • 影响:历史记录 Tab 的"编辑"日志缺少变更详情,无法审计"改了什么"。
  • 整改建议:编辑时对全部业务字段做 diff,将变更项(字段名/旧值/新值)写入 EDIT 类型 history。

P1-4 提交反馈缺少入口级状态合法性校验

  • 位置crm-lead/.../service/impl/LeadServiceImpl.javasubmitFeedback()
  • 原型依据(A2-1-2-1):

    "反馈:仅【未过期失效】线索可见……过期失效线索隐藏此按钮";反馈情况仅"有效/无效/未反馈"。

  • 代码现状submitFeedback() 未在入口校验 feedbackStatus ∈ {有效,无效},也未拦截 EXPIRED/VOID/CONVERTED 线索的反馈。
  • 影响:非法反馈状态值或对失效/作废/已转商机线索反馈,产生脏数据。
  • 整改建议:入口校验反馈状态枚举合法;线索状态不在可反馈集合(CLAIMED/FOLLOWING)时拒绝。

P1-5 公海池归属(部门/省份)变更后,存量线索归属未刷新

  • 位置crm-rule 池编辑 → PoolChangedEvent 监听器(crm-lead 侧)
  • 原型依据(A7-3-1):

    "联动更新:所有线索模块公海池下拉、领取按钮、上限拦截逻辑同步刷新"

  • 代码现状:需确认池 dept/省份变更时,UNDISTRIBUTED/PENDING 线索的 dept_id 是否同步刷新。
  • 影响:池改归属后,存量线索仍指向旧部门,数据可见性/分配错乱。
  • 整改建议PoolChangedEvent 监听器对该池下未领取线索批量刷新 dept_id 等归属字段。

P1-6 回收天数 N / 失效天数 M 修改后,存量 deadline 未按新规则刷新

  • 位置PoolChangedEvent 监听器 / 定时任务
  • 原型依据(A7-3-1):

    "规则联动生效:修改领取上限、回收天数、失效天数后,已领取/存量线索次日起按照新规则执行,存量已超时线索当日不批量回溯处理。"

  • 代码现状:需确认改 N/M 后是否重算 recycle_deadline/expire_deadline
  • 影响:改配置后存量线索仍按旧 deadline 回收/失效,与"次日起按新规则"不符。
  • 整改建议:N/M 变更时批量重算存量线索 deadline(以"次日起生效、不回溯已超时"为准)。

P1-7 视图统计走内存循环,未用 SQL 聚合

  • 位置crm-lead/.../query/impl/LeadViewQueryImpl.javacountStats()
  • 原型依据(A2-1-1/A2-1-3):顶部统计卡片"线索总量/待领取/今日新增""有效/无效/未反馈/已转商机"需随筛选实时统计。
  • 代码现状countStats() 全量 selectList 后在 Java 内存里循环累加。
  • 影响:大数据量下性能差、内存压力大,统计随数据增长退化。
  • 整改建议:改为 SQL GROUP BY status(及反馈情况维度)聚合,带上当前筛选条件。

P1-8 定时任务需保证"先失效后回收"的执行顺序

  • 位置crm-lead 定时任务(回收/失效)
  • PRD 依据(v1.1 grill:定时任务顺序):失效判定(M)与回收(N)若为两个独立 @Scheduled,并发/顺序不确定会导致同一线索状态判定竞争。
  • 整改建议:在同一方法内串行执行(先失效判定、再回收),或用统一调度顺序,避免两个独立任务竞态。

P2 一般缺陷 / 体验与健壮性

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

  • 原型依据(A2-1-1):"领取按钮需支持防抖(500ms)+请求中置灰+成功后禁用3秒""多人同时点【领取】时,随机选择成功人员"。
  • 建议:前端防抖是体验层;后端必须以状态 CAS(PENDING→CLAIMED,WHERE status=2)保证幂等,抢占失败返回明确"已被他人认领"错误码,供前端 toast。

P2-2 数值输入校验(每日上限/持有上限/N/M)

  • 原型依据(A7-3-1):"仅允许≥0 整数……N、M 仅支持≥1 正整数;非法输入拦截提交"。
  • 建议LeadPoolDTO 保存接口后端校验:daily/hold ≥0 整数(0=无限制);N、M ≥1 整数。前端校验不可替代后端。

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

  • 原型依据(A7-3-1):"占位提示【输入名称】,全局唯一不可重复"。
  • 建议saveOrUpdate 时校验名称唯一,重复抛业务错误码。

P2-4 部门负责人编辑时归属字段应只读(后端拒绝越权改归属)

  • 原型依据(A7-3-1):"部门负责人仅可修改本部门公海池人员配置,归属类下拉(团队/部门/省份)置灰不可编辑""无法修改归属团队/部门/省份"。
  • 建议:后端在 saveOrUpdate 中,若操作者为部门负责人,忽略/拒绝对 team/dept/province 的变更,仅接受人员与规则字段。

P2-5 批量操作按钮的角色隔离(后端校验)

  • 原型依据(A7-3-1/A2-1-4):新增/导入/导出/批量删除仅超管;部门负责人仅查询+单条编辑。
  • 建议delete-batch、批量分配等接口后端校验角色,非超管拒绝,防止绕过前端隐藏直接调接口。

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

  • 原型依据(A2-1-2):列表含"反馈情况(有效/无效/未反馈)""反馈内容""最后反馈时间"。
  • 建议:确认这些派生字段在列表查询中正确回填(最近一条已提交反馈),避免草稿或空值污染。

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

  • 原型依据(A2-1-2-1):"已转商机隐藏释放;过期失效隐藏反馈/释放,仅编辑/取消关注"。
  • 建议:后端对 release/feedback/convert 接口按状态白名单兜底拒绝,不依赖前端隐藏。

原型 ↔ 代码 差异速查表

原型要求(出处) 代码现状 缺陷编号
已绑定线索的公海池禁止删除(A7-3-1) 无守卫,64005 未用 P0-1
个人持有/每日领取上限拦截(A7-3-1/A2-1-1) claim 无校验 P0-2
分配同受持有上限约束(PRD §6.3) assign 无校验 P0-3
新增即领取(A2-1-2-2) 绕过规则/上限 P0-4
"仅管理员可分配"禁止销售自领(A7-3-1) 未读 claim_rule P0-5
公海混合展示防撞单(A2-1-1) 硬编码 status=PENDING P0-6
销售新增归属本人池不可编辑(A2-1-2-2) 无角色约束 P0-7
反馈草稿/提交分离(A2-1-2-1) 提交后未清草稿 P1-1
删除仅管理员+状态白名单(A2-1-4) isCreator 死变量 P1-2
编辑写完整变更历史(A2-1-2-1) 无字段级 diff P1-3
反馈仅未失效线索+状态枚举(A2-1-2-1) 入口无校验 P1-4
池归属变更联动刷新(A7-3-1) 存量未刷新 P1-5
改 N/M 次日起生效(A7-3-1) 存量 deadline 未重算 P1-6
统计卡片实时聚合(A2-1-1/3) 内存循环 P1-7

建议整改顺序

  1. 先修 P0-1、P0-2、P0-3、P0-4、P0-5(规则/上限/删除守卫,防数据错乱与越权)
  2. 再修 P0-6、P0-7(公海展示 + 新增归属)
  3. P1 批次:草稿清理(P1-1) → 删除权限(P1-2) → 反馈校验(P1-4) → 联动刷新(P1-5/P1-6) → 历史(P1-3) → 统计聚合(P1-7) → 定时任务顺序(P1-8)
  4. P2 收尾:后端兜底校验(幂等、数值、唯一、角色隔离、状态白名单)

注:本报告仅输出需整改项,不含代码改动。tmp/test-lead.html 可对各接口做经验性验证(重点验 P0)。