☰
自研CRM系统实战复盘:从数据模型到性能优化的完整路径
2026/10/19 9:49:51 网站建设 项目流程

做To B业务第六年,团队过了50人之后,最让我没法睡好觉的不是产品迭代,而是客户资料散落在每个人的Excel、微信聊天记录和邮箱里。某销售离职,带走的不光是多年积累的人脉,还有过去两年所有的跟进记录;另一个销售跟进过的客户,被同事再次打电话骚扰;管理层问"这个月的商机预测",几个人给的数字完全对不上。后来我们评估完一圈现成的CRM工具,决定自己动手做一个贴合销售流程的内部系统,这就是DeskcommCRM的由来。从立项到第一版上线用了四个月,这篇文章算是我把它从0搭到百人团队稳定使用后的完整复盘。

1. 选型结论先亮出来:为什么DeskcommCRM走自建而不是买现成的

1.1 市售CRM产品的三类代表,我们为什么都放弃了

在决定自建之前,我花了两周时间认真评估了三类产品。国际级大厂的产品确实功能全面,销售漏斗、客户分群、自动化流程无所不包,但问题也同样明显:订阅费用按坐席规模指数上涨,实施周期动辄半年起,还要专门养一个实施顾问团队配合,对一家百人以下的公司来说,投入产出比太低。我们当时的商务团队总共不到三十人,为了用上其中20%的功能去支付100%的费用和等待周期,这笔账怎么算都不划算。

国内SaaS型CRM胜在轻量易用,大部分两周内就能跑起来,价格也友好得多。但真用起来就会发现,自定义能力被限制得很死:字段类型有限、流程编排只能走官方模板、数据导出再做深度分析非常费劲。最关键的还是客户数据的归属权问题——所有数据都在对方服务器上,万一服务商调整策略或者经营不善,数据迁移会是一场噩梦。我试过把关键客户数据导出,结果连带联系人、跟进历史、附件关联关系这种完整结构都很难还原。"能导出但导不全"这种状态,对数据敏感的业务来说是硬伤。

除了商用SaaS,还看过开源方案,像SuiteCRM、Odoo的CRM模块都是备选。这条路最大的诱惑是零授权费和可改源码的自由度。但深入研究后发现问题同样不少:老牌开源CRM底层架构大多是PHP系,代码风格停留在上一个时代,前端交互体验陈旧;Odoo则是强业务绑定,学它的配置语言本身就需要不低的成本。最麻烦的是,这些系统的数据模型是"定死"的,客户表就是客户表,想按我们行业特有的业务属性打散重来,改造工作量接近重写。

1.2 自建CRM的真实成本账,以及我们认为划算的原因

决定自建之前,我认真做了一笔账。先算外部成本:SaaS按60个账号每年许可加定制费大概在十几万上下,国际大厂则要翻两三倍;再算内部成本:一个后端加一个前端主导开发四个月,日常再投入约20%的人力持续迭代。从金额上看两年内并不算便宜,但把时间轴拉到三年以上,自建的成本优势就出来了,更别提数据完全在自己手里这个隐性收益。

真正让团队坚定信心的,是我们业务的特殊性。产品销售流程长,客户从首次接触到成交,至少要经历需求挖掘、方案确认、报价审批、法务审核四个阶段,每个阶段涉及的角色和需要留存的字段都不一样,市售产品的标准销售阶段根本装不下实际流程。自建意味着流程自己定义,字段随时能加,今天业务提出要加一个"客户预算区间",明天就能上线,不用等厂商排期。

维度国际大厂国内SaaS开源方案自研
功能覆盖面全面覆盖主流有基础模块按需定制
数据归属权完全在厂商侧在厂商侧自主完全自主
初始成本极高中等低中高
上线周期半年以上两周左右一个月左右三个月以上
日常维护全托管托管自行维护自行维护
二次开发自由度低低高最高

这张表做完之后,结论已经很明显:有研发团队、业务模式又有特殊性的公司,自建是长期来看最舒服的一条路。

1.3 DeskcommCRM的定位取舍:先把哪三件事做好

项目启动前,我们把范围刻意收得很窄,只承诺做好三件事。第一,全量客户数据统一沉淀,无论客户线索来自官网、展会还是老客户转介绍,都必须进入一套统一的客户档案;第二,销售过程可追溯,客户从线索到成交的每一步都留下动态记录,管理者随时能知道某个商机卡在哪个阶段;第三,业务流程可自定义,销售团队可以根据实际打法调整字段、阶段和权限,不用每次麻烦开发。

被砍掉的功能清单反而更值得说:复杂的自动化流程编排、BI报表、移动端App全放到了后续迭代。做CRM最忌讳一上来就追求大而全,一个团队的时间和精力有限,功能堆得越多,上线时间越不可控,一线使用者越容易被劝退。我们定了三条阶段的验收标准:第一版让销售"用得爽",第二版让管理者"看得清",第三版才谈降本增效和智能化。

2. 数据模型是一切的根基:客户、线索、商机怎么组织才不会乱

2.1 五张核心表的关系,以及线索转客户的流转逻辑

DeskcommCRM第一个版本的数据模型只保留了五张核心业务表:线索(lead)、客户(client)、联系人(contact)、商机(opportunity)、跟进记录(activity)。很多人一开始舍不得删那些看上去很有用的表,但实际跑起来之后发现,模型越精简,理解和维护成本越低。后面加字段、加功能都容易,但表结构一旦复杂了,连做个跨模块统计都要绕半天。

设计时最重要的一条原则是"线索与客户严格分离"。线索泛指一切潜在客户来源,可能只是展会名片、官网留言、朋友介绍,信息不完整,也没经过验证;客户则是指已建立联系、有完整主体信息并进入跟进流程的实体。线索需要经过电话确认、背景调查后,通过"转客户"操作进入正式客户池,同时线索上所有备注自动带过去,不丢历史。这个机制保证了客户数据的干净,避免海量垃圾线索污染正式库。

商机表则是销售预测和漏斗分析的核心。一条商机必须引用一个客户,金额、预计成交日期、阶段三要素必填。阶段流转一旦开始,每次变更都会自动生成一条跟进记录,这些历史会用于后面的预计成交日期修正算法——根据历史商机各阶段停留天数,自动计算当前商机的预计成交概率。

2.2 宽表加JSONB的字段存储方案,而不是钻表取数

字段存储是CRM系统里一个特别容易被低估的决策点。我们最终用的是"宽表结构加JSONB扩展字段"的组合方案。以客户表为例,基础字段用独立列存储,如名称、电话、所有者、创建时间等,这些字段是系统需要高频检索和排序的;而业务自定义的扩展信息统一放进一个JSONB字段,比如"客户预算""决策链关系""使用的竞品版本"这类低频查询的字段。

之所以不用传统的EAV(实体-属性-值)表结构,是因为在数据量上来之后,EAV每个属性一行,查询一个实体要关联十几张临时表,性能很难接受。JSONB方案让每个客户一行记录,扩展字段天然集中,配合PostgreSQL对JSONB支持的GIN索引,即使要按扩展字段筛选,查询效率也远高于EAV。代价是灵活性和强约束不可兼得,没法保证某个扩展字段在数据完整性上必须存在,所以我们只对基础字段做数据库级约束,扩展字段交给应用层逻辑去校验。

这里放一个客户表的核心建表语句,可以直观看到结构设计:

CREATE TABLE client ( id BIGSERIAL PRIMARY KEY, name VARCHAR(200) NOT NULL, owner_id BIGINT NOT NULL REFERENCES "user"(id), source VARCHAR(50) NOT NULL DEFAULT 'manual', level VARCHAR(20) NOT NULL DEFAULT 'B', phone VARCHAR(30), industry VARCHAR(100), province VARCHAR(50), city VARCHAR(50), field_data JSONB NOT NULL DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ ); CREATE INDEX idx_client_owner ON client(owner_id) WHERE deleted_at IS NULL; CREATE INDEX idx_client_field_data_gin ON client USING GIN (field_data);

两张索引各有分工:owner索引是支撑列表页和个人客户查询的,JSONB的GIN索引是为了应对业务自己添加字段后的筛选场景。实际运行中客户表数据量到几百万行之后,这类查询依然能在百毫秒内完成,前提是别对JSONB字段做"非前缀"的模糊搜索,那是GIN索引的盲区。

2.3 跟进记录的不可变设计,以及客户时间轴

跟进记录表是DeskcommCRM使用频率最高的表,每一个电话、每一条微信消息、每一次拜访记录都会写入。这个表我坚持用了不可变设计——记录一旦创建,不允许修改,只能追加,删除也必须是管理员权限的软删除。原因是跟进记录的"原始性"对销售管理很重要,如果允许销售随手改掉之前写的跟进内容,很多过程审计就失去了意义。

提示:跟进记录表设计成不可变,意味着后来发现的错误记录也不能直接编辑,只能追加一条更正说明。这是用操作体验换审计可信度的取舍,需要团队提前达成共识。

每次跟进完成后,系统会把相关内容追加到客户详情页的时间轴上。时间轴按事件类型分组展示,包括跟进记录、商机阶段变更、报价单生成、合同上传、字段变更。这样做的好处是销售打开客户卡片,三分钟内能完整回顾整个交往历史,不用来回翻不同模块,这一点在老客户回访和新销售接手时特别省心。

3. 权限和协作是CRM的生死线:数据可以共享,但不能失控

3.1 四级权限模型:角色、数据范围、字段、操作

CRM里权限做得不好,会造成两种极端:要么数据完全公开导致销售互相抢单,要么权限过死导致老板看不到一线情况。DeskcommCRM把权限切成四个维度来设计。

第一层是角色权限,按岗位分配菜单和功能按钮权限。系统内置了销售、销售主管、运营、管理员四种角色,销售只能访问工作台和自己的客户,销售主管能看到本团队数据,运营负责公海池和线索分配,管理员拥有全部权限。第二层是数据范围权限,这是核心中的核心,用一条"数据归属链路"解决——每条客户记录都有所有者字段,销售可见"自己名下"的客户,销售主管在"自己名下"之外还能看"本部门"客户,管理层可以看到"总部"或"全部"。数据范围支持叠加,比如销售主管同时被授权查看两个事业部,那他能访问的数据范围就是两个事业部客户的并集。

第三层是字段级权限。客户信息里有些字段比较敏感,比如客户的采购预算、上次报价折扣、法务风险备注,这些字段在列表页对销售隐藏,只有销售主管和以上角色才能看到,甚至导出Excel时也会自动脱敏。实现上不复杂,配置层维护一个字段可见性规则表,查询时根据当前用户角色动态拼接要返回的字段集合。第四层是操作权限,控制的是"能看"之外的"能做",比如普通销售不能删除客户记录,不能把客户转给别的部门,不能修改已审批通过的报价金额。

3.2 公海池回收机制:让沉睡客户流动起来

客户数据最怕什么?怕躺在某个销售的Excel里永远不活动。DeskcommCRM从上线第一天就设计了公海池机制:每个客户记录上维护一个last_followed_at字段,记录最近一次有效跟进时间;系统每天晚上跑定时任务,如果某客户属于某个销售名下,但过去14天内没有任何跟进记录,这条客户数据就自动回收进公海池,并给销售和其主管各发一条站内通知。

公海池里的客户对所有销售可见,但领取有约束——每次最多领取20个,领取后进入7天保护期,保护期内其他销售不能抢。如果7天内还是没有有效跟进,再次回收。这套机制上线后,沉睡客户被重新激活的比例非常可观,最直观的变化是过去那些"想起来才跟进"的客户,现在都有明确的跟进节奏。我记得有一个躺在XLSX里两年的老线索,进公海池第一天就被一名新销售领走了,两周后居然成交了一笔不小的合同。

3.3 协作留痕的设计细节:@人、共享、操作审计

协作方面做得比较轻,但每一处都留痕。跟进记录支持@同事,被@的人会在工作台收到待办提醒;客户详情页提供"共享"按钮,销售可以把客户临时共享给其他同事查看或协助跟进,共享关系里带一个过期时间,到期自动收回。这种临时共享适合跨部门协作,比如销售把客户共享给售前工程师看技术需求,售前完成后权限自动收回,避免长期开放。

操作审计单独建了一张大表,记录的粒度到"谁在什么时间对哪个客户做了什么操作",包括查看关键页面、修改敏感字段、导出列表、回收/领取客户。这张表不做软删除,永远追加,定期归档。日常没什么人专门去看,但一旦出现客户归属争议、流程违规,审计日志是唯一能还原现场的依据。我自己遇到两次客户归属纠纷,都是靠审计日志查清楚了原始操作记录,所以这块设计一定别省,别等出问题再补。

4. 业务人员自己就能改流程:自定义字段和表单引擎的落地

4.1 元数据驱动的设计思路

CRM系统上线第一周,业务部门就提了十几个字段需求,如果每个字段都走一次开发排期,需求和开发都会崩溃。所以DeskcommCRM的第二阶段重点做了一件事:把字段配置能力交给业务人员。

做法是元数据驱动。系统维护一张自定义字段元数据表,每一条记录定义一个新字段的属性,包括所属实体、字段名、显示名、类型、是否必填、下拉选项等。页面渲染时,前端根据元数据动态生成表单控件;后端存储时,统一写入对应表的field_data JSONB字段;校验逻辑和导出列配置也全部由元数据驱动。这样业务人员打开"字段设置"页面,点几个按钮,一个全新字段就能在半小时内上线。

4.2 字段类型与前端渲染的对应关系

字段类型没有做得很花哨,但也足够覆盖日常需求:单行文本、多行文本、数字、日期、单选下拉、多选下拉、人员选择、关联实体、布尔值。每种类型在元数据里定义后,前后端共用一套类型清单,避免出现"后端存的是字符串、前端渲染成日期"这种错位。

单选和多选字段的选项列表单独存在options字段里,改选项有版本记录。这里有一个细节经验:选项一旦被历史数据引用,不能直接物理删除,只能停用。否则会出现历史数据归档后选项对不上号的情况。我们吃过一次亏,某个"客户来源"选项被删除了,结果三个月前的统计报表里这个维度直接变成了空值,排查了好久才明白是选项删除导致的关联丢失。

4.3 表单校验规则和配置发布机制

自定义字段的校验规则也做了可视化配置。比如"客户预算"限定数字类型且最小值0,"预计成交日期"不能早于创建日期,"客户名称"设置唯一性校验防止重复建档。这些校验规则一部分配置在数据库约束层,一部分落在应用层。数据库约束层负责最基础的类型和唯一性,应用层负责面向业务的可视化规则。分层的原因是数据库约束性能好但表达能力有限,应用层灵活但可能被绕过,两者搭配能兼顾性能与灵活。

配置发布上做了一个小创新:字段配置有"草稿"和"已发布"两种状态。业务人员在草稿状态下可以随便改、随便测试,调整满意后再点击发布,发布动作会写入一条配置变更记录。这样即使某个字段配置导致页面报错,也能通过回滚配置快速恢复。上线半年后,这条机制救了我们不下三次,有一次业务配置了一个必填字段,结果导致历史数据在编辑时全部弹校验错误,回滚配置后十分钟恢复。

5. 搜索和列表性能:数据过百万后,重点优化的三处

5.1 千篇一律的LIKE查询为什么不能用了

DeskcommCRM上线半年后,客户表加相关记录数据量突破两百万行,列表页开始出现明显卡顿。问题根因就是最初的搜索实现太天真——所有模糊搜索都用了LIKE '%keyword%'。这种写法在数据量小的时候看不出问题,数据量上来后会触发全表扫描,性能急剧下降,尤其当业务人员在列表页频繁尝试各种搜索条件时,数据库CPU动不动就飙到90%以上。

优化方案第一步是区分"必须模糊搜索"和"可以精确匹配"两类场景。客户名称、手机号这类查询改成前缀匹配加复合索引,查询条件从WHERE name LIKE '%张%'改成WHERE name LIKE '张%',配合(owner_id, name)的复合索引,响应时间从秒级降到几十毫秒。这个做法能覆盖绝大多数真实场景,毕竟销售搜客户的时候,打几个字往往就是从名字开头敲起的,很少有人只输入中间一个字。

5.2 PostgreSQL全文检索加中文分词的实践

对于确实需要全文搜索的备注、跟进内容、地址字段,我们引入了PostgreSQL全文检索能力。英文分词PostgreSQL内置就能做,中文分词需要额外安装分词插件,像基于jieba的pg_jieba、基于SCWS的zhparser都是成熟方案。落地时把跟进记录、客户备注这类长文本字段建立了tsvector类型的生成列,配合GIN索引,搜索响应稳定在两百毫秒内。

这里有个容易踩的坑:全文检索适合"搜文档"的场景,但对"按客户名精确找到一条记录"并不友好,因为分词会把"张三咨询采购"切成多个词条,查出来的结果太多太杂。所以搜索框的设计原则是:先走精确字段匹配,再走全文检索,精确匹配没有结果时才触发全文检索,避免让用户面对大量无意义结果。这个优先级关系要在一开始就设计好,不要等用户抱怨了再改。

5.3 列表页的分页、排序与缓存策略

列表页的另一个性能瓶颈是排序与分页。CRM列表页最常见的操作是翻页、按更新时间排序、按客户级别筛选。数据量大时,ORDER BY updated_at DESC LIMIT 20 OFFSET 1000这类深分页查询会让数据库越来越慢,因为OFFSET越大意味着要扫描越多的行。

我们的做法是引入"游标分页":记住当前列表最后一条记录的updated_at和id,下一页查询用WHERE (updated_at, id) < (:last_updated, :last_id) ORDER BY updated_at DESC, id DESC LIMIT 20,跳过了OFFSET带来的无谓扫描。这个改动让列表翻页稳定在百毫秒以内。另外,列表页的热点查询做了Redis缓存,同一用户同一条件的查询结果缓存30秒,销售反复操作时明显感觉更流畅。缓存时间不要设太长,否则客户数据更新了列表里看不到最新状态,反而制造新问题。

5.4 运营后台的报表查询和主库读写分离

销售日常查询优化好之后,运营后台的统计报表又成了新的瓶颈。这类查询条件复杂、扫描数据量大、执行时间长,如果和线上业务共用一个数据库连接池,很容易拖垮正常业务。方案是给PostgreSQL做主从读写分离,主库承担日常事务,从库挂一个只读副本专门跑报表和数据分析,并对所有报表查询做了超时限制。

这里科普一个细节:不推荐在业务初期就做读写分离,会额外增加数据同步延迟的复杂度。我们当时的路径是:先做索引优化,再上缓存,最后才考虑读写分离。前两步做完,线上核心链路的压力已经缓解了一大半;从库投入使用后,运营同事可以放心跑大查询,两者互不干扰。如果你也遇到类似瓶颈,建议按这个顺序排查,上来就搞读写分离容易过度设计。

6. 上线前后的那些坑:数据迁移、销售抵触与习惯养成

6.1 历史数据迁移:Excel里的脏数据清理三原则

系统开发完成之后,最棘手的工作不是写功能,而是把散落在Excel、旧系统里的历史客户数据搬进新系统。第一次迁移失败的原因很简单:原始数据字段缺失严重、重复记录大量存在、电话号码格式五花八门。比如同一个客户在两张表里分别叫"某某科技有限公司"和"某某科技公司",系统硬导入后客户列表立刻出现两行重复数据。

后来定下了三条清理原则。第一,迁移前必须做一次字段完整性检查,缺失率超过30%的字段直接降级为备注文本,不强行映射成结构化字段;第二,以"客户名称归一化+联系方式"为联合字段做全量去重,归一化的意思是把"科技有限公司"这类固定后缀统一处理,抵消简称和全称的差异;重复记录不丢弃,统一合并归属到最近更新的一条,历史记录全部追加到合并后的时间轴;第三,导入流程预留一个"待确认区",所有无法自动判定归属的客户先进这个区,由运营人工确认后再入库。这三条原则让第二次迁移的数据质量大幅提升,销售在正式使用后基本没有因为"找不到原来那个客户"而投诉过。

6.2 销售为什么会抵触新系统,以及我们怎么降低门槛

再好的系统,如果一线销售不用,也是一堆代码。上线早期最大的阻力不是技术而是心态:销售觉得每天维护跟进记录是额外负担,觉得系统在盯着自己干活。我们做了几件事来降低这个门槛。第一是把记录成本降为零的操作设计——跟进记录做成快速录入,最多点三次鼠标完成一条记录,电话界面直接点号码就能拨号,通话结束后弹窗问"要生成跟进记录吗",点一下就写入。

第二是把动作绑定业务关键节点,比如报价单必须关联一个商机才能生成,这就倒逼销售把商机信息先录进系统。另外,Excel导入功能保留了一段时间作为过渡方案,但导入的数据会标记为"未跟进",必须补充一条跟进记录才能算有效客户,这个机制避免销售为了完成任务把一堆僵尸数据倒进来充数。

第三是把数据变成价值反馈给销售。系统每周自动生成本人客户健康度报表,汇总本周新增跟进、即将流失客户、商机预测金额,销售看到这些东西能帮自己提前规避风险,自然会养成使用习惯。上线两个月后,日活率稳定在九成以上。

6.3 我复盘下来的三个最值得做的事(如果重新再来)

如果让我重新再来一次,有三件事我会在第一版就做进去。一是客户归属变更的审批流,上线初期是销售主管手动在后台改归属人,操作链路上自由度太大,导致一段时间内客户归属变动非常频繁,后来加了审批流才收敛。二是手机端适配,第一版只做了桌面端Web,销售在外出差拜访时只能回到电脑前录信息,移动端是第二个月才补上的,但已经错过了不少现场记录的黄金时间。三是一开始就设计好和IM工具的集成,现在很多客户沟通发生在即时通讯工具里,这些对话若能自动沉淀到客户时间轴,价值巨大,而这个模块我们目前还在迭代中。

DeskcommCRM从立项到现在,最让我有成就感的是销售团队慢慢养成了一个习惯:看到一串陌生号码会顺手查一下系统里的历史记录,谈客户之前会先翻时间轴。数据沉淀这件事,一开始靠制度和规则推,后面完全靠价值本身驱动。如果你也被客户资料分散、流程不透明的问题困扰,与其东拼西凑买一堆工具,不如认真评估一下自研或深度定制这条路。四个月的时间投入换一套自己说了算的客户数据体系,我觉得不算亏。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询