【若依项目-产品经理视角】RuoYi-Vue-Pro 源码拆解:CRM 客户关系模块深度解析——从线索到回款,一套完整的 B2B 销售闭环是怎么搭的?

大家好,我是你们的 RuoYi-Vue-Pro 源码拆解系列的腻害兔。前几期我们把框架层、系统管理、工作流 BPM、支付模块都拆了个遍,今天终于来到了业务模块的重头戏------yudao-module-crm 客户关系管理模块。这个模块是芋道所有业务模块中 API 最多(24 个 Controller、100+ 个接口)、统计表最丰富(7 个统计 Controller)、权限模型最复杂的模块之一。废话不多说,直接上干货!


一、今日模块概览

一句话总结:CRM 模块实现了一套完整的 B2B 销售管理闭环------从线索获取、客户管理、商机跟进、合同签订到回款催收,配合可配置的销售漏斗、公海池机制和团队级数据权限,让中小企业不用花几十万买 Salesforce,就能跑起来一套像样的销售管理体系。

如果你做过 ToB 产品,你一定知道 CRM 的核心价值:它不是一个"记录客户信息的表格",而是一个驱动销售行为的管理工具。谁跟了哪个客户?商机到了哪个阶段?合同审批通过了没?这个月回款多少?谁的销售排名最高?这些问题,CRM 模块全部能回答。


二、技术选型分析:CRM 模块的技术栈有什么讲究?

2.1 整体依赖结构

先看看 CRM 模块依赖了什么:

依赖 用途 选型分析
yudao-module-system 用户、角色、部门、操作日志 必选,CRM 需要知道"谁在操作"
yudao-module-bpm 合同/回款审批流程 关键选型------审批流走 BPM 引擎而非硬编码
yudao-spring-boot-starter-biz-tenant 多租户 CRM 天然是 SaaS 场景,多租户必备
yudao-spring-boot-starter-excel 客户导入导出 B2B 场景常见需求:从 Excel 批量导入客户
yudao-spring-boot-starter-biz-ip IP 地址解析 用于客户区域分析统计

2.2 为什么审批走 BPM 而不是自己写?

这是 CRM 模块最值得讨论的技术决策。合同审批和回款审批,很多项目会选择自己写一个简单的状态机:草稿 → 审批中 → 已通过 → 已驳回。但芋道选择了接入 BPM 模块(底层是 Flowable 引擎),原因有三:

第一,审批流程是多变的。 今天是一级审批,明天老板说"超过 10 万的合同要两级审批",后天又说"加一个会签环节"。如果自己写状态机,每次改流程都要改代码、发版本。用 BPM 引擎,管理员在流程设计器上拖拖拽拽就能改。

第二,审批记录要可追溯。 BPM 引擎自带完整的审批历史记录------谁在什么时间审批了什么、写了什么意见、花了多长时间。这些如果自己实现,工作量不小。

第三,解耦。 CRM 模块只需要调用 submitContract(id) 提交审批,然后在回调方法 updateContractAuditStatus() 里处理审批结果。中间谁审批、怎么审批、加签还是转签,CRM 完全不关心。

java 复制代码
// 合同提交审批------只需一行调用 BPM 接口
public void submitContract(Long id, Long userId) {
    // 1. 校验合同状态必须是草稿
    CrmContractDO contract = validateContract(id);
    // 2. 创建 BPM 流程实例,流程 key 是 "crm-contract-audit"
    String processInstanceId = bpmProcessInstanceService.createProcessInstance(...);
    // 3. 更新合同审批状态为"审批中"
    contract.setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus());
}

// BPM 审批完成后的回调
public void updateContractAuditStatus(String processInstanceId, Integer auditStatus) {
    // 把 BPM 的审批结果转成 CRM 的审批状态
    CrmContractDO contract = getContractByProcessInstanceId(processInstanceId);
    contract.setAuditStatus(auditStatus);
}

划重点:这种"提交审批 + 回调通知"的模式,是业务模块与 BPM 引擎集成的标准姿势。如果你自己的项目也需要审批流,可以直接复用这个模式。

2.3 数据权限:为什么不用框架层的 @DataPermission?

框架层有一个基于 SQL 拦截的数据权限方案(starter-biz-data-permission),按部门过滤数据。但 CRM 模块没有用它,而是自己实现了一套基于 crm_permission 表的权限体系。为什么?

因为 CRM 的权限需求比"按部门看数据"复杂得多:

  • 同一个客户,可以有多个人协作------负责人、协作人(只读)、协作人(读写)
  • 不同实体(客户、商机、合同)的权限要独立管理
  • 需要支持**"下属负责的数据"**这种场景
  • 需要支持公海池------没有负责人的数据,所有人都能看

这种需求,用 SQL 拦截是做不到的。芋道选择了多态权限表 + AOP 注解的方案,后面详细展开。


三、需求溯源推演:CRM 模块的产品需求是怎么来的?

让我尝试还原 CRM 模块最初的产品需求------

3.1 核心用户画像

CRM 模块至少服务三类用户:

一线销售 :关心"今天该联系谁"、"这个客户跟到哪一步了"、"我的业绩排名多少"。他们需要的是工作台 和跟进提醒。

销售主管 :关心"团队每个人跟了多少客户"、"商机漏斗健不健康"、"这个月能回多少款"。他们需要的是团队看板 和统计分析。

管理员/老板 :关心"客户资产不能跟着销售跑"、"离职了客户怎么办"、"合同审批流程要规范"。他们需要的是公海池 、审批流 和客户锁定。

3.2 需求演进路径

V1.0------能管客户就行

最初的需求可能就是:录入客户信息、录入联系人、记录跟进情况、签合同、收回款。标准的 CRUD 五件套。

V2.0------客户不能跟着销售跑

老板发现:销售离职了,客户信息全在他手机里,公司一个客户都带不走。于是有了公海池机制 ------超过 N 天不跟进的客户,自动回收到公海,其他销售可以领取。同时有了客户锁定------老板锁定的客户,销售不能自己放公海。

V3.0------团队协作

大客户不是一个人能搞定的,需要售前、方案、交付多人协作。于是有了团队权限------一个客户可以加多个协作者,分只读和读写两种权限。

V4.0------销售过程管理

老板不只想看结果(签了多少合同),还想看过程(商机在哪个阶段、转化率怎么样)。于是有了可配置的销售漏斗------每个部门可以设置不同的阶段模板,每个阶段有对应的赢单概率。

V5.0------数据驱动决策

最后,当数据积累到一定程度,老板要看报表了------客户画像分布、销售排行榜、业绩完成率、漏斗转化率、成交周期分析......这就是为什么 CRM 模块有 7 个统计 Controller,覆盖了几乎所有 B2B 销售分析维度。


四、竞品对标分析:开源 CRM 横向对比

对比维度 芋道 CRM RuoYi 原版 JeecgBoot CRM 悟空 CRM 八百客
线索管理 ✅ 线索录入 + 转客户 ❌ ✅ ✅ ✅
客户公海池 ✅ 自动回收 + 手动领取 ❌ ❌ ✅ ✅
商机漏斗 ✅ 可配置阶段 + 赢单概率 ❌ 基础 ✅ ✅
合同审批 ✅ BPM 流程引擎 ❌ 简单审批 ✅ ✅
回款管理 ✅ 回款计划 + BPM 审批 ❌ ❌ ✅ ✅
团队权限 ✅ 负责人/只读/读写三级 ❌ ❌ ✅ ✅
统计分析 ✅ 7 个维度 30+ 报表 ❌ 基础 ✅ ✅
业绩目标 ✅ 月度目标 + 完成率 ❌ ❌ ✅ ✅
多租户 ✅ 原生支持 ❌ ❌ ❌ ✅(SaaS版)
开源免费 ✅ Apache 2.0 ✅ ✅ ⚠️ 社区版有限 ❌
与后台管理一体化 ✅ 同一套代码 ✅ ✅ ❌ 独立系统 ❌

芋道 CRM 的核心优势

1. 一体化架构。 芋道 CRM 不是一个独立系统,而是和系统管理、工作流、支付等模块在同一个代码库里。这意味着:CRM 的合同审批可以直接调用 BPM 引擎,CRM 的回款可以对接支付模块,CRM 的客户数据可以跟 ERP 的订单数据打通。这种模块间的数据流通,是独立 CRM 系统做不到的。

2. 多租户原生支持。 悟空 CRM、八百客等独立 CRM 虽然也有 SaaS 版,但它们是独立部署的。芋道 CRM 天然跑在多租户架构上,一套部署就能服务多个企业的 CRM 需求。

3. 可配置的销售漏斗。 很多开源 CRM 的商机阶段是硬编码的,芋道允许管理员按部门配置不同的阶段模板,每个阶段有独立的赢单概率,直接驱动漏斗统计。

不足

1. 缺少客户自助门户。 B2B 场景中,客户自己查看订单、提交工单的需求很常见,芋道 CRM 目前只有管理端,没有客户端。

2. 缺少邮件/日历集成。 成熟的 CRM 通常支持邮件收发(自动关联到客户跟进记录)和日历同步(安排拜访计划),芋道 CRM 目前缺少这块。

3. 移动端能力偏弱。 虽然前端有 UniApp 版本,但 CRM 的核心使用场景------外出拜访、现场签到、拍照记录------在移动端体验上还有提升空间。


五、核心业务流程:从线索到回款的完整链路

CRM 模块的核心业务流程可以用一张图说清楚:

5.1 线索转化(transformClue)

线索转客户是 CRM 的第一个关键动作。调用 clueService.transformClue(id) 后,系统会:

  1. 根据线索信息自动创建一个客户记录
  2. 如果线索有联系人信息,同时创建联系人
  3. 线索标记为"已转化",后续不再出现在线索列表中

5.2 商机阶段推进

商机不是简单的"进行中/已结束"二元状态,而是一个可配置的多阶段流水线。管理员可以为不同部门配置不同的阶段模板:

java 复制代码
// 阶段类型(模板):如"标准销售流程"
CrmBusinessStatusTypeDO {
    name: "标准销售流程",
    deptIds: [1, 2, 3]  // 适用于哪些部门
}

// 阶段定义:每个阶段有名称和赢单概率
CrmBusinessStatusDO {
    typeId: 1,        // 属于哪个模板
    name: "需求分析",
    percent: 30,      // 赢单概率 30%
    sort: 2           // 排序第 2
}

销售推进商机时,只能按阶段顺序推进(或者跳阶段),每个阶段对应一个赢单概率,这些概率数据最终汇聚成漏斗统计报表。

5.3 公海池自动回收

这是 CRM 模块最有"管理价值"的功能。公海池的设计思路是:客户是公司的资产,不是销售的私有财产。

java 复制代码
// 定时任务:自动把超期未跟进的客户回收到公海
@TenantJob  // 多租户定时任务,每个租户独立执行
public void autoPutCustomerPool() {
    // 1. 读取公海池配置
    CrmCustomerPoolConfigDO config = poolConfigService.getCustomerPoolConfig();
    // 2. 查找超过 N 天未跟进的客户
    List<CrmCustomerDO> customers = customerService.getPutPoolRemindCustomerList(config);
    // 3. 逐个放入公海
    for (CrmCustomerDO customer : customers) {
        customerService.putCustomerPool(customer.getId());
    }
}

回收后的客户,ownerUserId 被清空,任何人都可以从公海领取。领取时会校验:客户是否被锁定?领取人的客户数量是否超过上限?


六、数据模型解读:CRM 的 21 张表是怎么关联的?

CRM 模块的数据模型以客户为中心,所有其他实体都围绕客户展开:

6.1 核心表设计亮点

1. 多态权限表 crm_permission

一张表存储所有 CRM 实体的权限,通过 biz_type 字段区分实体类型:

sql 复制代码
-- crm_permission 表结构
CREATE TABLE crm_permission (
    id          BIGINT PRIMARY KEY,
    biz_type    INT NOT NULL,        -- 实体类型:1=线索 2=客户 3=联系人 4=商机...
    biz_id      BIGINT NOT NULL,     -- 实体 ID
    user_id     BIGINT NOT NULL,     -- 用户 ID
    level       INT NOT NULL,        -- 权限级别:1=负责人 2=只读 3=读写
    -- 联合索引:查询某实体上的所有权限
    INDEX idx_biz (biz_type, biz_id),
    -- 联合索引:查询某用户有权限的所有实体
    INDEX idx_user_biz (user_id, biz_type)
);

设计亮点:这种多态权限设计,避免了为每种实体建一张权限表(customer_permission、business_permission、contract_permission...),一张表搞定所有实体的权限管理。配合 CrmBizTypeEnum 枚举,扩展性极强------新增一种 CRM 实体,只需要加一个枚举值,不需要建新表。

2. 商机阶段模板 crm_business_status_type + crm_business_status

两级设计:类型是模板(按部门分配),阶段是模板下的具体步骤。这样不同部门可以有不同的销售流程,互不干扰。

3. 公海池配置 crm_customer_pool_config

一张单行配置表,控制公海池的所有行为:

java 复制代码
CrmCustomerPoolConfigDO {
    enabled: true,            // 是否启用公海池
    contactExpireDays: 30,    // 未跟进超过 30 天自动回收
    dealExpireDays: 90,       // 未成交超过 90 天自动回收
    notifyEnabled: true,      // 回收前是否通知
    notifyDays: 3             // 提前 3 天通知
}

4. 负责人变更记录 crm_owner_record

每次客户负责人变更都记录在案,支持按日期和用户维度统计公海池的"放入/领取"次数,为管理决策提供数据支撑。


七、产品设计亮点与槽点

让人眼前一亮的设计

1. @CrmPermission 注解 + AOP 切面------声明式数据权限

java 复制代码
// Controller 方法上直接声明权限要求
@PutMapping("/transfer")
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, 
               bizId = "#transferReqVO.id", 
               level = CrmPermissionLevelEnum.OWNER)
public CommonResult<Boolean> transferCustomer(@Valid @RequestBody CrmCustomerTransferReqVO transferReqVO) {
    // 只有这个客户的"负责人"才能执行转移操作
    customerService.transferCustomer(transferReqVO, getLoginUserId());
}

这个注解的设计非常精巧:

  • bizType 指定实体类型(客户/商机/合同...)
  • bizId 用 SpEL 表达式动态取值
  • level 指定最低权限要求(负责人/读写/只读)
  • AOP 切面自动校验,不通过直接抛异常

更妙的是它的降级逻辑:如果你自己没有权限,但它会检查你的下属有没有权限------主管可以看下属的数据。

2. 级联操作------转移客户时连带转移关联数据

java 复制代码
// 转移客户时,可以选择同时转移关联的联系人、商机、合同
CrmCustomerTransferReqVO {
    Long id;                    // 客户 ID
    Long newOwnerUserId;        // 新负责人
    Integer oldOwnerPermissionLevel;  // 原负责人降级为(null=移除)
    List<CrmBizTypeEnum> toBizTypes;  // 要级联转移的关联实体类型
}

这个设计很贴合实际业务场景:销售 A 离职了,他名下的客户、联系人、商机、合同全部移交给销售 B。不需要一个一个去转移,一次操作搞定。

3. 场景化数据视图------三种视角看同一份数据

CRM 的列表查询支持三种场景切换:

  • 我负责的:只看自己是负责人的数据
  • 我参与的:只看自己是团队成员(只读/读写)的数据
  • 下属负责的:看下属负责的数据(主管视角)

这三种视图不是三个不同的接口,而是同一个分页接口通过 CrmSceneTypeEnum 参数动态拼接查询条件。前端只需要一个 Tab 切换,后端自动处理权限过滤。

4. 统计分析的丰富程度

7 个统计 Controller,覆盖 30+ 个统计维度,这在开源 CRM 中是罕见的:

统计维度 具体指标
客户统计 新增/成交客户数(按日期/按人员)、跟进记录统计、公海池统计、成交周期分析
漏斗统计 商机漏斗、阶段分布、转化率、赢单/输单分析
业绩统计 合同数量/金额/回款额(同比/环比)
目标达成 月度目标 vs 实际完成
客户画像 按区域/行业/来源/等级分布
产品统计 产品销售排行、品类分布
排行榜 合同金额/回款金额/客户数/跟进数等 8 个维度

可以改进的地方

1. 缺少跟进提醒的推送能力

目前 CRM 有"今日需联系客户数"、"待处理合同数"等统计接口,但没有看到主动推送通知的逻辑(比如 WebSocket 推送、企业微信通知)。对于 CRM 这种强时效性的系统,主动提醒比被动查看重要得多。

2. 公海池规则可以更灵活

目前公海池只支持"未跟进 N 天"和"未成交 N 天"两种回收规则。实际业务中还有:按客户等级设置不同的回收周期、按行业设置不同的跟进频率要求等。建议把公海池规则做成可配置的规则引擎。

3. 统计查询的性能隐患

统计分析大量使用 SQL 聚合查询(GROUP BY、SUM、COUNT),在数据量大时可能出现性能问题。目前看到的是直接查数据库,没有做预计算或缓存。建议对高频统计做定时预聚合,或者引入 ClickHouse 之类的 OLAP 引擎。


八、发散性思考:CRM 模块还能做什么?

8.1 如果让我重新设计

1. 引入事件驱动架构

目前 CRM 模块内部是同步调用:创建客户 → 创建权限 → 记录日志。如果引入 Spring Event 或 MQ 做事件驱动:

java 复制代码
创建客户事件 → 权限服务监听 → 自动创建负责人权限
                              → 日志服务监听 → 记录操作日志
                              → 通知服务监听 → 给新负责人发欢迎通知

这样新增一个"创建客户后要执行的动作",只需要加一个监听器,不用改原来的代码。

2. 增加客户生命周期自动化

目前客户状态是手动的(未成交/已成交)。可以引入自动化规则:客户创建 30 天未成交 → 自动标记为"高风险";客户成交后 90 天无回款 → 自动触发催收提醒。

3. 打通 IM 模块

芋道已经有 yudao-module-im 即时通讯模块,如果把 CRM 和 IM 打通:客户来访消息自动关联到 CRM 客户记录,销售可以在 CRM 界面直接跟客户聊天,聊天记录自动变成跟进记录。

8.2 技术思路的迁移场景

CRM 模块有几个设计思路,可以迁移到其他业务场景:

  • 多态权限表(biz_type + biz_id + user_id + level)→ 可以迁移到任何需要"细粒度数据权限"的场景,比如项目管理(不同项目不同权限)、文档管理(不同文档不同权限)。
  • 公海池机制 → 可以迁移到"资源池"场景:服务器资源池(闲置服务器自动回收)、停车位管理(超时未使用自动释放)、共享设备管理。
  • BPM 审批集成模式(提交 + 回调)→ 可以迁移到任何需要审批的业务:请假审批、报销审批、采购审批。
  • 可配置的阶段流水线 → 可以迁移到项目管理(任务状态流转)、招聘管理(候选人阶段)、售后工单(处理进度)。

九、关键代码导读

1. CrmPermissionAspect.java

路径:yudao-module-crm/.../framework/permission/CrmPermissionAspect.java

为什么值得读:这是 CRM 数据权限的核心拦截器。读懂它,你就理解了:@CrmPermission 注解是怎么工作的、权限校验的优先级是什么(CRM 管理员 → 自身权限 → 下属权限 → 拒绝)、公海池的特殊处理逻辑。整个 CRM 的安全体系都建立在这个切面之上。

2. CrmCustomerServiceImpl.java

路径:yudao-module-crm/.../service/customer/CrmCustomerServiceImpl.java

为什么值得读:这是 CRM 最核心的 Service 实现,包含了客户管理的几乎所有关键操作:创建、转移、锁定、放入公海、从公海领取、批量导入、自动回收。特别是 transferCustomer() 和 putCustomerPool() 方法,是理解 CRM 业务流转的最佳入口。

3. CrmBusinessServiceImpl.java

路径:yudao-module-crm/.../service/business/CrmBusinessServiceImpl.java

为什么值得读:商机管理的核心实现,特别是 updateBusinessStatus() 方法------商机阶段推进的校验逻辑(终态不可变、阶段类型一致性校验)。配合 CrmBusinessStatusServiceImpl,可以理解可配置流水线的实现方式。

4. CrmContractServiceImpl.java

路径:yudao-module-crm/.../service/contract/CrmContractServiceImpl.java

为什么值得读:合同管理的核心实现,重点看 submitContract() 和 updateContractAuditStatus() 两个方法------这是 CRM 与 BPM 引擎集成的标准模式。理解了这两个方法,你就知道怎么在自己的业务模块里接入审批流。

5. CrmStatisticsFunnelServiceImpl.java

路径:yudao-module-crm/.../service/statistics/CrmStatisticsFunnelServiceImpl.java

为什么值得读:销售漏斗统计的实现,展示了如何用 SQL 聚合查询实现 BI 报表。特别是赢单概率的计算(商机的阶段概率加权求和)和转化率的统计方式。如果你要做任何类型的统计分析模块,这个文件是很好的参考。


总结

CRM 模块是芋道系统中业务复杂度最高的模块之一。它不是一个简单的 CRUD 系统,而是融合了数据权限(多态权限表 + AOP 注解)、流程审批(BPM 引擎集成)、资源管理(公海池自动回收)、统计分析(7 个维度 30+ 报表)等多个技术难点的综合性业务模块。

如果满分 10 分,我给 CRM 模块打 8 分。扣掉的 2 分在于:缺少邮件/日历集成、移动端体验有待加强、统计查询的性能优化空间较大。但作为一个开源项目内置的 CRM 模块,它的功能完整度和设计质量已经相当出色了。

系列文章导航

  • 上一篇:支付模块 yudao-module-pay
  • 本篇:RuoYi-Vue-Pro 源码拆解:CRM 客户关系模块深度解析------从线索到回款,一套完整的 B2B 销售闭环是怎么搭的?
  • 下一篇预告:ERP 企业资源(yudao-module-erp)------采购、销售、库存、财务的一体化管理

觉得有用的话,点个赞支持一下呗~ 有问题欢迎评论区讨论!

相关推荐
七牛云行业应用2 小时前
DeepSeek Super-agent 与 Grok Bot:开源 Agent 架构和聊天助手有何区别
人工智能·ai·架构·ai编程
Autumn_ing2 小时前
AI Skills 怎么用于原型设计?把需求澄清、页面生成和测试接起来
产品经理·墨刀·skill·墨刀ai客户端·ai原型·原型skill·设计skill
Csvn2 小时前
性能监控
前端
Highcharts.js2 小时前
Highcharts Gantt使用符号的自定义数据标签开发甘特图示例
javascript·甘特图·highcharts·图表开发·实战代码·响应式图表
Csvn3 小时前
构建工具
前端
FRing033 小时前
vue2干货总结3(computed计算属性、watch监听器)
开发语言·前端·vue.js·vue2
Java小卷4 小时前
JuanForm vs FormCreate!Vue3 表单引擎联动配置 PK!
vue.js·低代码
kyriewen5 小时前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员
海的透彻5 小时前
dockerfile如何快速构建容器(java服务)
java·开发语言
IT_陈寒6 小时前
当Java的hashCode遇上Lombok,我的缓存被全清了
前端·人工智能·后端