大家好,我是你们的 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) 后,系统会:
- 根据线索信息自动创建一个客户记录
- 如果线索有联系人信息,同时创建联系人
- 线索标记为"已转化",后续不再出现在线索列表中
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)------采购、销售、库存、财务的一体化管理
觉得有用的话,点个赞支持一下呗~ 有问题欢迎评论区讨论!