02 领域模型:客户、线索、公海到底是什么关系

第 2 / 10 章
02 领域模型:客户、线索、公海到底是什么关系

改代码之前得先把模型理清楚——不然改完才发现表结构本身有设计缺陷,那就得推倒重来了。

这一章我会画清楚 芋道 CRM 里核心概念的关系图,以及我新增的 Phase 1/2 功能是插在哪一层的。

三个核心概念

CRM 领域里,最核心的三个东西:

概念英文一句话定义关键特征
线索Lead / Clue可能成为客户的人/公司**还没转化**,信息不全,可能是纯广告流量
客户Customer / Account已经确认有业务往来的客户**已转化**,有负责人,有历史订单
公海Public Pool没人管的客户池**owner_user_id IS NULL**,谁都可以领走

三个概念的生命周期:

线索(未跟进) → 线索(已跟进) → 转化为客户 → 成交/流失
                      ↓
                  丢回公海 ← 老客户负责人离职/长期不跟进

芋道原版就是这么设计的,我没改这个模型。我加的是过程里的自动化:

  • 线索自动评分(Phase 2)→ 帮销售优先打高质量电话
  • 公海智能分配(Phase 1)→ 帮负责人离职的老客户找新主人

表结构速览

线索表 crm_clue(原版已有)

芋道原版 20+ 字段,我加了两个:current_score 和 current_grade(评分冗余字段,Phase 2 用)。

关键字段:

id               -- 主键
name             -- 线索名称(公司/个人)
owner_user_id    -- 当前负责人(销售)
transform_status -- 是否已转化为客户
customer_id      -- 转化后的客户 ID(transform_status = true 时填)
follow_up_status -- 是否跟进过
source           -- 客户来源(字典 crm_customer_source)
industry_id      -- 行业(字典 crm_customer_industry)
level            -- 客户等级(字典 crm_customer_level)
mobile / telephone / email / wechat -- 联系方式
area_id          -- 所在地(芋道 AreaUtils 全国行政区划)
remark           -- 备注(LLM 评分重点分析的字段)
-- 我加的:
current_score    -- 当前综合分(冗余,来自 crm_clue_score)
current_grade    -- 当前分级 A/B/C/D
tenant_id        -- 租户 ID(多租户插件自动加)
deleted          -- 软删标记
create_time

客户表 crm_customer(原版已有)

结构和线索类似,但多了 level(A/B/C 级,销售可以手动调)、contract_start/end_time(合同起止)、website 等。关键差异:客户有 owner_user_id,但没有公海状态——因为客户一旦创建就必须有负责人。芋道原版的"丢回公海"其实是把 owner_user_id 设为 NULL,客户本身还是客户表的一条记录。

公海是一种状态,不是一张表

芋道原版没有 crm_public_pool 表。公海客户就是 crm_customer 表里 owner_user_id IS NULL 的那些行。线索也同理——线索里 owner_user_id IS NULL 的就是"公海线索"。

Phase 1 的改造点:我没建公海表,沿用这个"状态即公海"的设计,但加了回收定时任务和智能分配 Service,让"丢回公海 → 自动认领"这个链路闭环。

Phase 1/2 新增的表

Phase 1:公海智能分配

三张表:

表作用
`crm_customer_assign_rule`分配规则配置(哪种客户用哪种策略)
`crm_customer_assign_area`片区分配的片区-销售绑定
`crm_customer_assign_record`分配留痕(谁抢了谁、为什么)

这三张表构成了 Phase 1 的配置层和追溯层。策略本身不在表里,在 Java 代码里(CrmCustomerAssignStrategy 接口 + 5 种实现)。

Phase 2:AI 线索评分

三张表:

表作用
`crm_clue_score`评分主表(每次评分一条,version 递增)
`crm_clue_score_detail`评分明细(可解释性核心,每条规则一行)
`crm_score_rule_config`规则权重配置(运营可调,改完不用重启)

关系图

┌────────────────────────────────────────────────────────────┐
│                    crm_clue(线索)                         │
│  id │ source │ industry_id │ level │ current_score │ ...   │
└──────────┬──────────────────────────┬──────────────────────┘
           │ 1:N                       │ 1:1 (每条线索最新版本的评分)
           ▼                           ▼
  crm_clue_score              crm_clue_score_detail
  ├─ source_score (20)        ├─ SOURCE   HIGH   20/20  "官网表单"
  ├─ industry_score (20)      ├─ INDUSTRY HIGH   20/20  "IT行业"
  ├─ level_score (20)         ├─ CONTACT  HIGH   20/20  "全联系方式"
  ├─ contact_score (20)       └─ ACTIVITY HIGH   20/20  "3天内跟进"
  ├─ activity_score (20)
  ├─ llm_score (95)  ← 可选   crm_score_rule_config
  ├─ llm_reason              ├─ SOURCE   weight=20 enabled=1
  ├─ total_score=98           ├─ INDUSTRY weight=20 enabled=1
  ├─ grade=A                  ├─ LEVEL    weight=20 enabled=1
  └─ version=1                ├─ CONTACT  weight=20 enabled=1
                              └─ ACTIVITY weight=20 enabled=1

┌────────────────────────────────────────────────────────────┐
│                  crm_customer(客户,含公海)               │
│  id │ owner_user_id │ level │ contract_start │ ...          │
└──────────┬──────────────────────────┬──────────────────────┘
           │ owner_user_id IS NULL     │ 1:N (分配留痕)
           │ → 公海客户                 ▼
           │              crm_customer_assign_record
           │              ├─ customer_id
           │              ├─ from_owner(回收前的负责人)
           │              ├─ to_owner(新负责人)
           │              ├─ strategy(用了哪种策略)
           │              └─ reason(为什么分配给他)
           ▼
  Phase 1 回收定时任务 → owner_user_id = NULL
  Phase 1 智能分配 Service → 从公海挑候选人 → CAS 抢占

关键设计决策

为什么公海不是一张表?

两种思路:

思路 A:公海是一张独立表 crm_public_pool,客户丢回公海就是从 crm_customer 删一行、往 crm_public_pool 插一行。好处是查公海客户就是 SELECT * FROM crm_public_pool,简单直观。坏处是数据冗余(客户信息要复制),而且客户被认领回来又要插回 crm_customer——一来一回容易丢字段、容易事务不一致。

思路 B:公海是一种状态 owner_user_id IS NULL。好处是无冗余,所有客户信息一张表管。坏处是查公海要加条件,但加索引就解决了。

芋道原版选了 B,我也跟着 B 走——这个设计是对的,不用改。

为什么评分要 version 字段?

运营调权重、改评分规则后,需要重新评分。但重新评分会覆盖历史记录吗?我不想覆盖——我想看到"这条线索第一次评分 70 分,运营把 ACTIVITY 权重调到 30 后重新评,变成 82 分",这是权重调整的效果验证。

所以 crm_clue_score 用 (clue_id, version) 做序列 key,每次评分 version++,前端查当前分就取同一 clue_id 下 version 最大的一条。

为什么评分明细要带 reason?

销售拿着"为什么这条打了 C 级"来问你,你说"LLM 算的"不是人话,他要的是可解释的理由。reason 字段存自然语言描述,前端点一下就展开,比如:

  • "官网主动提交,意向质量最高"
  • "IT 互联网是我司核心覆盖行业"
  • "未填写手机号,无法联系"
  • "45 天未跟进,线索有流失风险"

这就是 06 章标题里说的"可解释优先"。

接下来

03 章我会走查芋道原版的公海机制——它是怎么把客户丢回公海的?怎么认领的?有哪些我觉得可以改进的地方?走查完就开始 Phase 1 的策略模式实现。