☰
从通信集成到数据建模:自研CRM的实战复盘
2026/10/19 14:00:46 网站建设 项目流程

如果你所在的公司曾经买过两套CRM都没能坚持用下来,那你应该能理解我们决定自研DeskcommCRM时,身边同事的表情。大多数CRM产品的出发点其实是管理者的报表需求,而不是一线销售的工作效率。销售不愿意录数据,主管天天催,最后系统里只剩一堆过期的商机和没人看的跟进记录。

DeskcommCRM这个名字拆开看其实就是两件事:Desk(桌面办公场景)+ Comm(通信集成),它不是一个传统意义上"管客户信息"的数据库,而是一个把通话、邮件、企业微信消息和客户资料全部收拢到一个桌面操作台的客户关系管理平台。这篇文章我打算把整个项目的立项判断、技术选型、数据建模、通信集成的实现路径,以及上线九个月之后踩到的真实问题都写清楚。如果你正在纠结自研CRM还是继续买现成的,或者已经决定自研但不确定从哪里下手,这篇文章应该能帮你省掉不少弯路。

1. 从"买了用不起来"到自研:DeskcommCRM立项时的三个判断

1.1 市面CRM为什么落不了地

先说我观察到的普遍现象。很多团队买完CRM之后,热情期不超过三个月。原因是销售真正的工作流根本不在CRM里,他们每天在微信上跟客户聊、用手机打电话、在邮箱里发报价,然后每天下班前再花半小时把沟通记录"翻译"进系统。这种二次录入本质上是在给销售增加工作量,而不是在帮他们干活,所以录入率低是必然的。

还有一层问题在管理层:老板想看的是漏斗、预测、回款,而销售在意的是"这个客户上周说过什么""上次报价是多少""他到底有没有看过我的方案"。这两个诉求如果被强行塞进同一个页面,结果就是页面无比臃肿,点一个按钮要跳转四次。DeskcommCRM立项前我们做了一轮内部调研,发现一线销售和生产主管对"好用"的定义完全不同,一个要少点几下鼠标,一个要能看出谁在怠工,而市面产品往往想同时讨好两边,最后两边都不满意。

1.2 立项时锁定的两个关键词:Desk和Comm

所以我们在产品定义阶段就把范围收敛,只做两个核心词。Desk指的是坐席桌面,销售、售前、客服每天大部分工作时间都在电脑前处理客户消息和跟进事项,这个系统必须首先是一个高效的操作台,而不是一个录入窗口。Comm则是指通信能力,客户触点不应该是需要人工转录的内容,而是应该自动流进系统成为数据资产。

目标产品形态最后敲定为三栏式工作台:左边是客户列表和筛选器,中间是客户详情与时间线,右边是通信面板,可以一键拨号、回复邮件、查看企业微信聊天记录。所有跟这个客户有关的动静都集中在一个屏幕上,销售不需要切换窗口,系统就能把通话记录、往来邮件、消息记录自动归档到对应的客户名下。

1.3 给项目划定的边界

立项时我们也很清楚什么不做。不做复杂的审批流引擎,不做多仓库多币种的ERP联动,不做营销自动化的各种花哨邮件触达。那个阶段团队的精力只够服务一条核心链路:客户进来、跟进记录沉淀、商机推进、成交转化。其他功能宁可后面再补,也不在一期堆进去。

这个边界划得非常重要,因为自研项目最大的风险不是技术难,而是范围失控。如果一开始就想着做一个全家桶,大概率半年后还在写需求文档。

2. 技术底座:自研但不过度设计

2.1 为什么不用现成开源CRM二开

这个问题几乎每个听到自研的人都会问。我确实认真调研过几款开源方案,包括老牌的SugarCRM社区版、EspoCRM,以及用PHP写的SuiteCRM。最终放弃的原因很统一:二开成本被严重低估。

首先是数据模型改不动。开源CRM的数据库表结构是围绕标准销售流程设计的,而我们想要的是"客户+联系人+商机+事件流"这种以时间线为核心的模型,改起来基本等于动根基。其次是升级冲突,一旦改了核心表结构,上游更新就再也合不回来了,等于把自己锁死在一个旧版本上。更现实的问题是界面交互普遍陈旧,现在的销售团队早就习惯了企微、飞书这类工具的交互体验,旧式表单录入很难让他们接受。

当然,自研的前提是团队确实有后端和前端的完整能力,如果团队连一个能独立扛起全栈的人都没有,那还是老老实实买现成的,没必要为了技术情怀给自己挖坑。

2.2 技术栈与部署形态

DeskcommCRM的后端我们用了Node.js + TypeScript + NestJS,选它的核心原因有两个:团队对这个技术栈最熟,以及NestJS的模块化结构非常适合按业务域来划分代码,比如客户模块、通信模块、统计模块各占一块,互不干扰。

数据库用的是PostgreSQL,这是我认为最适合做业务系统的选择。它的事务能力、JSONB类型、行级安全性都能同时满足业务建模和后续权限扩展的需求。前端是React + Electron的桌面应用壳,配合自建的REST API和WebSocket通道。通信网关方面,电话能力用FreeSWITCH接SIP中继,软电话走WebRTC,邮件走IMAP/SMTP协议栈,企业微信走官方API和会话存档接口。

部署上我们放在了公司自己的私有云环境,没有用公有云SaaS化。原因是客户数据敏感性比较高,公司内部要求所有业务数据必须落在自有基础设施上,这也是很多中小型公司选择自研而不是采购SaaS的一个重要考量。

2.3 一个关键取舍:桌面端还是纯Web端

浏览器确实省事,但通话功能成了绕不过去的坎。浏览器里的WebRTC虽然也能打电话,但在设备管理、录音文件获取、本地音频回环这些层面限制很多,尤其当你还要对接实体话机或者复杂的音频外设时,纯Web方案很难做到稳定。

最终采用Electron桌面壳之后,桌面端和Web端的差异就体现出来了:本地SIP注册、音频设备枚举、录音文件直接落盘、来电时系统级弹窗提醒,这些能力都变得很自然。下面是当时做的对比:

维度纯Web端Electron桌面端
通话能力受浏览器限制,需要额外服务转发本地SIP注册,走WebRTC直连,延迟更低
设备管理无法精确控制音频输入输出设备可以枚举设备并记忆用户选择
录音落盘需要服务器转存,流程长本地录制后同步,可靠性更高
离线能力基本没有可以缓存客户列表,弱网可用
包体与内存轻量包体大,内存占用高,需做优化

Electron的缺点也真实存在,安装包动辄一两百兆,内存占用比浏览器高不少。我们的缓解办法是把不需要常驻的模块改成懒加载,并定期释放缓存,实测下来中端办公电脑跑起来不算吃力。

3. 数据底盘:客户、联系人、商机的建模细节

3.1 企业和联系人分开建模

这是很多初创CRM最不重视的地方。早期我们打算一张客户表解决所有问题,后来发现如果客户是公司,你想记对接人、决策人、财务联系人,一张表根本塞不下。最终拆成了两张主表:customers(客户企业)和 contacts(联系人),联系人和客户是多对一的关系。

这种拆分看起来简单,实际好处极大。销售电话打进来时,来电号码先查contacts表,命中后直接定位到客户企业,客户详情页和该联系人的历史沟通记录一次性都带出来。如果号码没有在联系人表里,还可以通过"这一企业下的其他联系人"来帮助判断来电者身份,比单表设计灵活很多。

3.2 商机阶段不是简单的下拉框

商机管理我们一开始也做成了下拉框,但跑了两周发现一个问题:销售选阶段非常随意,主管根本看不出哪些商机真正在推进。后来把阶段字段升级成了有约束力的状态机模型,定义如下:

阶段含义进入条件预期停留时间
1-初步接触已建立联系创建商机5天
2-需求确认已明确需求和预算有会议纪要或需求文档7天
3-方案报价方案已发,进入商务报价单已创建10天
4-商务谈判价格和条款谈判中有二次报价记录14天
5-赢单/输单闭环合同审批通过或明确放弃-

每个阶段匹配了预期停留时间,系统每天凌晨跑定时任务,发现有商机在某个阶段停留超过预期天数,就自动给负责人和主管推送一条提醒。这个机制上线之后,商机变动的及时性明显提升,因为销售自己也被系统盯着,偷懒不推进会被领导看见。

3.3 时间线模型:所有客户接触记录的统一容器

时间线是DeskcommCRM最核心的数据结构。我们设计了一张统一的events表,所有跟客户的交互都往这里面写:电话、邮件、企微消息、会议纪要、跟进笔记、报价记录,都作为一条事件记录存在,关联到对应的客户和联系人身上。

这样做的好处是前端渲染简单,客户详情页只要按时间倒序拉取events表就可以了,不需要为每种业务类型单独写一套列表组件。搜索也方便,全局搜索只要索引events表的content字段,客户相关的历史消息全都能被检索出来。统计也好做,按事件类型分组聚合就能得出沟通频次、响应时长这些管理指标。

当然,统一模型也有代价,每种事件类型会有自己独特的字段,比如电话有通话时长,邮件有主题和附件,工单有状态。解决办法是主表只存公共字段,扩展字段放附属的JSONB列,查询的时候按需解析。PostgreSQL的JSONB性能足够支撑这种用法,不用过度纠结表结构。

4. 通信集成:DeskcommCRM的灵魂模块

4.1 通信层抽象:所有外部触点都收敛成"会话记录"

如果DeskcommCRM只是一个客户资料库,那它跟Excel没什么本质区别。通信集成才是让系统真正"活"起来的关键。我们的做法是抽象出一个统一的消息记录接口,不管消息来自电话、邮件还是企业微信,最终都归一化成同一种数据结构,写入message_records表。

这个接口大约长这样:

interface MessageRecord { id: string; customerId: string; contactId?: string; direction: 'inbound' | 'outbound'; channel: 'call' | 'email' | 'wechat'; content: string; mediaUrl?: string[]; startedAt: Date; duration?: number; rawPayload?: Record<string, unknown>; }

这个抽象非常重要。它让上层业务完全不需要关心消息来自哪个渠道,通话记录、邮件、消息全部平等地出现在时间线上,展示、搜索、统计都只需要处理一张表。新接一个渠道时,只需要写一个适配器,把外部数据翻译成MessageRecord结构,剩下的功能全部复用。

4.2 通话模块:FreeSWITCH与来电弹屏的落地

电话这块我们用了FreeSWITCH配合SIP中继,前端通过SIP.js做WebRTC软电话。核心功能有三个:点击拨号、来电弹屏、录音归档。

点击拨号的逻辑是:销售在客户页点击电话号码,系统调FreeSWITCH的API发起外呼,FreeSWITCH先呼坐席分机,坐席接听后系统再呼客户号码,这种模式叫做"先呼坐席再呼客户",好处是客户看不到坐席的真实手机号,而且支持录音。

来电弹屏的逻辑是:来电接入FreeSWITCH后,系统拿到主叫号码,在contacts表里查这个号码对应的联系人和客户,然后把客户信息直接推送到所有在线坐席的桌面端弹窗上。销售还没接电话,就已经知道这个客户叫什么、上次聊到什么、有没有未处理的报价。

这里有个很实在的坑:SIP信令的超时时间设置。最开始我们用的是默认值60秒,结果有些客户电话响了很久没人接,系统里一直处于"呼叫中"状态,坐席不知道要挂断还是继续等。后来把无应答超时改成了25秒,同时加了音频提示,体验才正常。

4.3 邮件模块:IMAP同步与自动关联

邮件模块我们走的是IMAP协议。每个坐席登录系统时绑定自己的企业邮箱账号,系统通过IMAP定时拉取收件箱新邮件。拉下来之后要做三件事:去重、解析、关联客户。

去重是个容易被忽视的坑。同一封邮件如果既有收件箱又进了某个文件夹,IMAP的UID会不一样,容易造成重复入库。我们后来加了一个规则,用message-id + received日期做唯一索引,才彻底解决重复问题。

关联客户的策略是反向匹配:先提取发件人和收件人的邮箱地址,去contacts表里查这些邮箱有没有绑定过联系人,如果找到了就自动把邮件挂到该联系人名下。如果找不到,就把邮件放进"待分配"池子,销售看到后可以手动归属,归属之后系统会记住这个外部邮箱发来的后续邮件自动进该客户的收件箱。

发送邮件的部分相对简单,我们做了一个模板库,销售可以选一个模板,然后自动带入客户称呼、公司名这些变量。不要小看模板功能,它直接提升了报价邮件和跟进邮件的规范化程度。

4.4 企业微信/企微消息记录:把外部IM也收进来

很多销售跟客户沟通都用企业微信,这部分聊天记录如果进不了CRM,系统的时间线就是残缺的。我们接入了企业微信的会话存档和外部联系人能力。特别注意这里不是爬聊天记录,而是通过官方API在客户和员工都同意的前提下获取合规的会话内容。

企微消息进系统后同样转换成MessageRecord,channel标记成wechat。为了减少噪音,我们还设置了一个开关:可以把某些闲聊群组排除在归档范围之外,避免时间线被无关消息刷屏。

4.5 统一收件箱的交互细节

通信模块做好之后,界面交互反而成了新的战场。所有渠道的消息汇入同一个收件箱之后,我们设计了几个交互细节:新消息在桌面端弹Toast提醒,点击直达对应客户时间线;消息按客户聚合而不是按渠道聚合,同一个客户下的电话、邮件、消息全部混排在一起按时间排序;已读未读状态全局统一,处理过的消息自动标记为已读。

这三个细节看起来简单,但实际上决定了用户愿不愿意天天打开这个系统。

5. 权限模型:让一线安心用,让管理层看得见

5.1 三级客户池:私有、团队共享、公海

客户资源分配是销售团队最敏感的话题。我们当时把客户归属设计成了三级池子:私有客户(只有归属人可见可操作)、团队共享客户(同组成员可见,但编辑和跟进需要权限)、公海客户(无归属人,所有销售都可以认领)。

所有新线索进入系统后默认落在公海,销售主动认领后变为私有。私有客户如果在30天内没有跟进记录,则自动回到公海,由其他同事认领。这个规则听起来会让销售紧张,但实际跑起来之后反而提升了线索利用率,沉睡的客户资源不再被个人私藏。

还要注意公海回收和商机阶段的联动。如果一个私有客户名下存在处于"方案报价"阶段的活跃商机,就不应该被回收,否则容易引起冲突。我们的策略是:有活跃商机的客户不参与自动回收,需要主管手动移出。

5.2 字段级权限与数据导出边界

权限不能只靠"谁的客户谁看"这种粗粒度控制。客户资料里有些字段很敏感,比如客户负责人的手机号、合同金额、备注中的个人信息。我们的方案是给字段打标记,分为全员可见、仅本人和上级可见、仅管理员可见三种级别。前端根据当前登录用户的角色动态渲染字段,后端接口同步做过滤,前端隐藏字段只算第一层保险,后端不下发数据才是真正的权限边界。

数据导出权限也做了限制。普通销售只能导出自己名下客户的列表,导出操作会生成审计日志,记录操作人、时间、导出的数据范围和条数。我们遇到过销售把整个团队客户导出带走的情况,所以导出权限必须从严。

离职继承这块我们做了自动转移:员工账号标记为离职后,名下私有客户自动转移给部门主管,主管可以再批量分配给指定接手人。这个功能看着不起眼,但能省掉大量线下扯皮。

6. 上线之后:一组真实数据和几个要返工的设计

6.1 用了九个月的数据反馈

DeskcommCRM上线九个月,最直观的变化是数据活起来了。团队成员30人左右,每天平均产生600多条客户交互记录,而以前手动录入的时候这个数字长期在50条以下。下面是整理的一些关键指标:

指标上线前(手动录数据)上线后(系统自动采集)
人均每日录入跟进记录不到10条20条左右,且多为自动同步
客户联系响应时间(邮件)8小时以上2小时以内
商机阶段更新及时性滞后2-3周当天
数据完整率(客户电话)约70%95%以上

这组数据说明一个事:当记录不再依赖人工二次录入,而是操作过程中自动产生,数据质量自然而然就上来了。管理者想要的"看得见",其实是这么来的。

6.2 三个做对了的地方

复盘下来有三个决策我觉得是对的。第一个是把通信放在客户详情页旁边而不是单独一个模块,销售不需要来回切换界面,使用频率自然就上来了。第二个是所有沟通记录自动进时间线,销售不再需要为"写跟进记录"这件事买单,系统替他们完成了记录,他们只需要偶尔补充备注。第三个是报表少而精,我们没有做几十个维度的BI看板,核心就保留了商机漏斗、团队活跃度、客户响应时长三个报表,管理者真正盯着的也就这几个数。

6.3 下一步计划返工的两个模块

当然也有要返工的地方。第一是自定义字段能力实在太弱了,客户资料里有些行业特定的字段当时是写死在代码里的,结果每接一个行业就要发一次版本。现在已经在规划一套动态表单引擎,让管理员可以在后台自己添加字段而不改代码。第二是移动端体验。DeskcommCRM目前只有桌面端,销售外出拜访客户时只能用手机浏览器凑合看,体验很差,而且通话模块在移动端也不能用,这个短板已经直接影响了外勤人员的接受度。

如果当时能更早意识到"移动端不是桌面端的缩小版,而是另一个使用场景",可能会把H5轻量版提前排进迭代计划。这也是我给所有准备自研CRM的团队一个最实在的建议:别只盯着电脑前的场景,销售真正签单的时刻往往是在客户现场。

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

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

立即咨询