☰
自研CRM实战:从客户表设计到通讯工作台整合
2026/9/26 21:01:34 网站建设 项目流程

1. 为什么我会去做一个叫 DeskcommCRM 的系统

先说背景。上一家公司做的是客服外包业务,客服团队常年要切五六个软件:接电话用一套,看在线聊天用一套,查客户订单用一套,记录跟进日志又得打开 Excel 或同事自建的共享表格。每天下班前,客服主管要花一个小时汇总“今天聊了几个客户、成交了几单、哪些客户得明天跟进”,数据来源全靠人工从不同窗口复制粘贴。混乱到什么程度?同一个客户早上在聊天工具里问过报价,下午打电话来,接电话的人根本不知道他是谁,又从头问一遍。客户烦,客服累,主管拿不到准确的数据。

后来我们决定不再买现成的 SaaS CRM,也不是因为钱,而是业务确实特殊——客服团队同时承担销售线索初筛、售前咨询、售后跟进三件事,标准 CRM 的“销售管道”模型和“工单”模型都覆盖不全。我和另一个后端同事花了大概四个月,从零做了一套内部系统,名字就叫 DeskcommCRM。思路很简单:把“客户通讯工作台”和“客户关系管理”揉到同一个界面里,让客服在一个页面上完成“接待—识别—记录—跟进”的全流程。

这篇文章不打算做全面系统讲解,重点写我在设计和实现过程中真正被卡住的几个环节,以及同样的需求换一种方案会有什么不同的代价。如果你也在做类似“通讯 + 业务系统”的整合,或者正在纠结自研 CRM 的功能边界,这篇应该对你有用。

2. 不带业务视角的客户表设计,三个月后一定返工

2.1 一开始我只设计了“客户基础信息”,结果被骂了

第一个版本我参照网上常见的客户表设计,字段就是姓名、手机号、邮箱、公司、备注,当时觉得够了。结果上线第三天,客服就跑来提需求:“客户是通过哪个渠道来的?是百度推广、朋友介绍、还是有赞店铺,这个得标清楚,月底要算各渠道的转化。”

我当时直接在客户表加了channel字段,值是字符串。又过了一周,需求变成:“同一个客户既在微信咨询过,又在电话里沟通过,这两个来源怎么算?我们要看最后成交算哪个渠道。”字符串字段根本没法回答这个问题。

于是返工。把“客户”和“客户来源”拆成两个概念:客户本身是主体,一条客服记录属于一次“会话”,一次“会话”挂一个渠道。每个客户可能有多条会话记录,每一条都有自己的渠道归属。这样才能算清楚“首次来源”“最近来源”“成交来源”三个指标。

2.2 客户识别:手机号不是唯一标识,别偷懒

客服领域最头疼的是去重。DeskcommCRM 接入了电话、在线聊天、邮件三个入口,同一个客户可能用不同手机号留过资料,甚至同一部手机号码在不同渠道里的昵称还不一样。

第一次我天真地拿手机号做unique key,结果导入历史数据的时候直接崩了:同一个人在公司官网留了座机,在微信留了手机号,两条记录被判定成两个客户。后面做合并功能时才知道,真实场景里“哪些记录属于同一个客户”永远不是百分百确定的事,只能通过“标识字段 + 置信度”来合并。

我的做法是维护一张customer_identity表:

  • 主键是identity_id
  • 关联字段:customer_id、identity_type(phone / email / wechat_openid / qq / 企业微信id)、identity_value、confidence(0~1)
  • 客户主记录只存customer_id,不直接存手机号

匹配规则是队列式的:新会话进来时,按顺序尝试精确匹配手机号 → 精确匹配邮箱 → 匹配微信 unionid → 匹配“手机号后四位 + 姓名”。匹配到任何一个,就归并到同一个customer_id下面。匹配不到就新建。confidence我暂时只用了 0 和 1,因为再细分下去算法成本太高,对客服场景帮助有限。

注意:匹配规则的顺序很关键。手机号和邮箱应该始终优先用精确匹配,其次是“平台内唯一 ID”(比如微信 openid)。模糊匹配(后四位 + 姓名)只能作为兜底,因为它误伤率高,同一个手机尾号的人很多。

2.3 客户状态字段不能只有一个“状态”

一开始我做了status字段:潜在客户、意向客户、成交客户、流失客户。客服实际操作时又反馈:“昨天刚标记为意向客户,今天老板让我查一下这个客户是哪个销售负责的?还有他上次跟进是什么时候?”这些信息如果塞在status一个字段里,什么都查不了。

最终改成了三个独立维度:

  • stage(客户所处销售阶段:新线索、联系中、需求明确、报价中、成交、流失)
  • owner_id(当前负责人,可能同时存在“首次接待人”和“当前跟进人”)
  • last_follow_up_at(最后跟进时间,用独立字段存,而不是靠翻聊天记录看)

这个设计看起来很朴素,但真正用起来才发现价值:每周一早上跑一次last_follow_up_at > 7天的列表,就是“需要回访的客户清单”,完全不需要额外建报表。

3. 把通讯入口收进工作台,是 DeskcommCRM 最重要的一步

3.1 不是做“呼叫中心”,而是做“通讯动作聚合”

最初有同事建议直接接入云呼叫中心 SDK,自动录音、自动弹屏。我评估了一下,如果要做到完善的电话托管、排队、IVR 语音导航,工作量至少多出一个月,而且呼叫中心必须由具备资质的话务通道转发,成本很高,对内部系统来说名大于实。

DeskcommCRM 的做法是“软电话 + 点击外呼”:

  • 客服电脑插话机或使用 USB 话务耳机,通过 WebRTC 调起浏览器电话
  • 系统内部存的是call_log表,记录通话时间、时长、对方号码、录音文件外链、通话结果(未接通/接通/意向明确/已加微信)
  • 客服在系统里点“拨打”按钮,系统调用话务网关 API 发起外呼,通话结束后强制填写“通话小结”才能关弹窗

这么做避免了“必须做全套呼叫中心”的陷阱,同时保留了最关键的一环:通话数据自动归档到客户档案。哪怕只是接通了五秒,通话记录也在,不会出现“打电话给客户但系统里没留下任何痕迹”的情况。

3.2 在线聊天记录的统一收口

在线咨询入口也类似。我们没有自研 IM,而是直接接入了企业微信客服和企业微信会话存档。客服在 DeskcommCRM 里看到的是一个统一收件箱,里面同时汇总了企业微信私聊、客户群、公众号消息,点击任意一条消息,右侧侧边栏同步显示客户资料和订单记录。

这一块的难点不在接入,在“消息与客户档案的关联”。企业微信消息会提供external_userid(外部联系人 ID),但同一个客户可能同时加了客服的企业微信、进过客户群、又在公众号留过言。我用前文提到的customer_identity来统一关联:凡是外部联系人 ID 相同的情况直接绑定,凡是没有绑定过的就展示为“未知访客”,由客服手动认领。

3.3 所谓“桌面化”,就是把高频操作放到一个页面里

“Deskcomm”这个名字后半截指的就是这个逻辑。传统 CRM 列表在左,详情在右,查看聊天记录要再点一个新页面。DeskcommCRM 的工作台是三栏布局:

  • 左栏:会话/工单列表,按紧急度排序
  • 中栏:当前会话的聊天内容
  • 右栏:客户档案、历史来访记录、待办任务、订单信息

这个布局一点都不新颖,但真的解决了一个关键问题:客服不用来回切换窗口,整个接待过程中的所有信息都在一屏之内,鼠标移动次数少了,单次会话时长明显下降。上线两周后我统计过,客服处理一个咨询的平均耗时从 4 分 20 秒降到了 2 分 50 秒,一屏式信息展示的贡献很大。

4. 工单系统和 CRM 的边界:什么时候该“分出去”,什么时候该“并进来”

4.1 工单不是大而全的,而是“异常处理”的载体

CRM 管的是普通商机推进流程——联系客户、发方案、报价、签单。但实际运营中总有“异常流程”:客户投诉、售后维修、技术问题转交、跨部门协作。这些不该在“客户进度”里硬塞,否则看数据的人分不清“这个客户走到哪一步了”和“这个客户有什么待处理问题”。

DeskcommCRM 的工单模块只做四件事:

  1. 创建:任何客服可以为一个客户创建工单,选择工单类型(投诉/售后/技术/其他)
  2. 流转:主管可以指派给某个处理人或某个小组
  3. 时效:记录每张工单的创建时间、响应时间、解决时间,超时自动标红
  4. 关联:工单必须关联一个customer_id,也可以关联一个订单 ID

不做 SLA 计算,不做多级审批流,不做工单模板扩展。不是说这些功能不好,而是我们团队只有两个人,做太多配置化功能会陷入“功能森林”,最后反而没人用。一个系统能坚持用下去,核心是“知道不该做什么”。

4.2 一个典型工单流转示例

举个例子,客户在微信里说“我上个月买的机器坏了,想报修”。客服在聊天里看到后,点击客户名旁边的“创建工单”。

  • 类型:售后
  • 优先级:中
  • 描述:客户反馈机器故障,附上客户描述原文
  • 指派:直接指定给小张(售后工程师)

小张在 DeskcommCRM 里看到待处理工单列表,点开工单,左侧能看到这个客户的历史全部通讯记录和购买记录。他在工单里回复“已联系客户,确认是电源板故障,安排补发电源板”,系统自动给客户发一条微信通知。工单状态从“处理中”改为“待客户确认”。

第三天客户反馈换新后正常,小张把工单关闭。整个过程中,数据天然沉淀在客户档案里,不需要任何额外录入。

4.3 工单数据不要和会话数据混在一张表

我踩过一个坑:最初想省事,把所有“客户联系动作”统一存到interaction表,然后靠type字段区分是聊天、电话还是工单。结果type字段越来越多,查询越来越慢,每次统计都要写一堆CASE WHEN。后来老老实实拆表:

  • call_log:通话记录
  • message_log:在线聊天记录
  • ticket:工单

三张表通过customer_id关联,统计报表时用 JOIN 或子查询。这样做的代价是代码稍微啰嗦一点,但每张表的字段都简洁清晰,维护成本低很多。

5. 跟进记录的自动化和 UI 细节,比业务逻辑更影响日活

5.1 强制“跟进小结”和“下次跟进时间”

很多系统把跟进记录当成可选项,客服忙起来就不填,最后报表是空的。DeskcommCRM 做了一个不近人情的设定:每次通话结束或聊天会话关闭时,弹窗必须填写两个字段——跟进小结、下次跟进时间,否则不能关窗口。

这个设计来自我们客服主管的一句话:“你们系统能不能帮我盯着每个人,别让客户在他们手里断掉?”强制弹窗保证了“每个客户被处理过之后一定留下痕迹”。一开始有客服抱怨麻烦,坚持了两周以后,大家习惯了,因为好处很明显——下次再碰到这个客户,翻记录就知道上次聊到哪,不用重新问一遍。

5.2 跟进任务自动生成

“下次跟进时间”填完之后,系统自动创建一条待办任务,放到负责人的“今日待办”列表里。到期当天早上 9 点推送提醒,超期未完成则任务标红,主管后台能看到超期任务数。

不用额外做复杂的 CRM 工作流引擎,一张follow_up_task表就能搞定:

  • task_id
  • customer_id
  • owner_id
  • due_at
  • status(pending / done / skipped)
  • related_type(call / message / ticket)
  • related_id

每天定时任务把due_at <= 当天 23:59:59 且 status = pending的任务推给负责人,就这么简单。做复杂工作流引擎的团队,往往真正用起来的还是这种“看一眼就知道该干嘛”的待办。

5.3 客户详情页的“时间线”必须按倒序

这个细节很小,但影响很大。客户详情页我一开始是按时间正序展示所有互动记录(通信、工单、跟进、订单变更)。客服真实操作时发现,正序找“上次聊了什么”很难受,因为最近的信息沉在最底部。

改成倒序之后,客服打开客户详情页的第一眼看到的就是最近发生的事情。就这么一个小改动,客服反馈“明显好用了”。类似这种交互细节,在系统设计时就该优先考虑一线使用者的真实动作,而不是按数据生成的逻辑来排列。

6. 数据报表:先搞定“日活看得懂”,再谈“老板看得爽”

6.1 一屏日报:每人每天多少通电话、多少条消息、处理了多少工单

DeskcommCRM 的报表模块没有从一开始就做大屏可视化,而是先做了一个最朴素的“日报表”页面,默认展示:

  • 今日每人外呼次数、接通次数、通话总时长
  • 今日每人接待消息数、响应平均时长
  • 今日工单新增数、关闭数、超时数
  • 今日新增客户数、跟进到期数

这个页面用列表形式展示,不用图表。因为客服主管实际需要的就这些数字,不需要炫酷的折线图。上线后主管每天早上打开 DeskcommCRM 看一眼,就知道昨天团队整体情况,省去了原来手工汇总的一小时。

6.2 转化数据怎么算:界定好“一次客户旅程”

要想算“从线索到成交的转化率”,先得定义“一次客户旅程”的起始和结束。这是所有 CRM 里最容易扯皮的地方——是首次咨询算起点?还是建了客户档案算起点?成交是首付款日算?还是合同签订日算?

我的选择是:起点 = 首次message_log或call_log记录的当天;终点 = 订单表里第一个“已支付”状态的订单创建时间。这个定义可能不适合所有业务,但关键不是“正确”,而是“一致”——所有人都按同一个口径看数,才能对比分析。口径不统一才是报表项目失败的最大原因。

6.3 不要做实时报表,除非你能扛住数据量

我一开始计划用 WebSocket 推送实时看板,数据每隔几秒刷新一次。后来发现客服团队根本不需要“实时”,他们需要的是“第二天早上能看到昨天的准确数据”。所以报表模块只做了离线统计:定时任务在每天凌晨跑一次,把前一天的汇总数据落库,页面直接查统计结果表,查询速度飞快,也不需要单独的报表数据库。

当你团队规模不到几十人时,实时大规模计算完全没必要。CPU 不用白不用,但人力是要花钱的,优先做对使用者真正有价值的功能。

7. 上线之后的三个“真实事故”和对应修补

7.1 事故一:导入历史数据时把两个同名客户合并错了

导入数据来自 Excel,同一客户在表格里出现两次,一次手机号写了 138,一次写了 139(可能是旧号和新号)。我的匹配机制在“手机号精确匹配”阶段没匹配上,然后在“姓名 + 后四位”兜底时误判成同一个人,结果把两人的订单合并了。

修补方案:所有自动合并操作都先进“待确认合并”列表,由主管人工确认后才真正合并。这个列表每天要清一次,但误合并的问题再没发生过。

7.2 事故二:企业微信消息侧的“同一个人”识别错乱

客户在企业微信里同时加了客服 A 和客服 B,两个人各自在 DeskcommCRM 里看到了“不同客户”(因为external_userid是以每个客服为维度的,而不是企业统一维度)。

修补方案:调用企业微信“客户联系”接口,获取“外部联系人详情”,里面有一个unionid字段,只要客户在微信生态内统一绑定过手机号或公众号,就能用来做全局标识。我把unionid也加入customer_identity,external_userid只作为会话关联的辅助键。

7.3 事故三:跟进任务超时提醒太吵,最后没人看

第一版我设了每两小时推送一次催办通知,结果客服直接把通知权限关了,系统里“待办已读率”降到 20%。后来改成每天 9:00、11:30、16:00 三个节点推送,并且只推和自己相关的任务,已读率回升到 85% 以上。

“提醒”这个功能,最关键的其实是克制。过度打扰会让用户习惯性忽略,反而起不到提醒效果。

8. 一些你可能需要的具体实现参考

8.1 表结构简表(客户与互动域)

-- 客户主表 CREATE TABLE customer ( customer_id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100), gender TINYINT, company VARCHAR(255), remark TEXT, stage VARCHAR(50), owner_id BIGINT, last_follow_up_at DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 身份标识表(用于多渠道识别) CREATE TABLE customer_identity ( identity_id BIGINT PRIMARY KEY AUTO_INCREMENT, customer_id BIGINT NOT NULL, identity_type VARCHAR(30), -- phone / email / wechat_openid / unionid identity_value VARCHAR(255), confidence DECIMAL(2,1) DEFAULT 1.0, UNIQUE KEY uniq_type_value (identity_type, identity_value) ); -- 通话记录 CREATE TABLE call_log ( call_id BIGINT PRIMARY KEY AUTO_INCREMENT, customer_id BIGINT, owner_id BIGINT, phone_number VARCHAR(30), direction VARCHAR(10), -- inbound / outbound started_at DATETIME, ended_at DATETIME, duration_seconds INT, call_result VARCHAR(50), -- connected / no_answer / busy / invalid summary TEXT, recording_url VARCHAR(500) ); -- 消息记录 CREATE TABLE message_log ( msg_id BIGINT PRIMARY KEY AUTO_INCREMENT, customer_id BIGINT, owner_id BIGINT, channel VARCHAR(30), -- wecom / public_cloud / web_chat sender_type VARCHAR(10), -- customer / agent content TEXT, sent_at DATETIME, KEY idx_customer_time (customer_id, sent_at) ); -- 工单 CREATE TABLE ticket ( ticket_id BIGINT PRIMARY KEY AUTO_INCREMENT, ticket_no VARCHAR(50), customer_id BIGINT, order_id BIGINT NULL, ticket_type VARCHAR(50), -- complaint / after_sale / technical priority VARCHAR(10), -- low / medium / high status VARCHAR(30), -- open / processing / waiting_customer / closed assignee_id BIGINT, creator_id BIGINT, description TEXT, created_at DATETIME, updated_at DATETIME, closed_at DATETIME );

8.2 获取“待跟进且超期”的任务列表

SELECT t.task_id, t.customer_id, c.name AS customer_name, t.due_at, t.status FROM follow_up_task t LEFT JOIN customer c ON c.customer_id = t.customer_id WHERE t.owner_id = ? AND t.status = 'pending' AND t.due_at < DATE_ADD(NOW(), INTERVAL 1 DAY) ORDER BY t.due_at ASC;

8.3 一个简单的“下一次提醒”判断

既然选择了每天定时推送,就可以在每天 9 点、11 点 30 分、16 点三个时间点跑一个批处理脚本,把符合条件的任务汇总推给对应负责人:

scheduled_times = ["09:00", "11:30", "16:00"] def remind_job(): pending_tasks = db.query( "SELECT task_id, owner_id, due_at FROM follow_up_task \ WHERE status = 'pending' AND due_at <= NOW()" ) by_owner = {} for task in pending_tasks: by_owner.setdefault(task.owner_id, []).append(task) for owner_id, tasks in by_owner.items(): send_notification(owner_id, tasks)

这个脚本用简单的 cron 调度即可,不需要引入消息队列或任务编排框架,维护成本低,出了问题定位也容易。

9. 部署与运维时要注意的几个实际问题

9.1 内网部署还是云服务器

如果客服团队少、数据要求高私密,可以直接内网部署。DeskcommCRM 后端用的 Java Spring Boot,前端用的 Vue 3,打包成jar+nginx静态资源即可。内网部署注意服务器时区要统一,否则“今日跟进任务”会出现晚上 23:59 和凌晨 00:00 的边界判断问题。

9.2 录音文件的存储路径

通话录音如果直接存数据库,数据库很快会膨胀。我把录音文件放到对象存储或本机磁盘目录,数据库中只存文件链接。对于中小团队,/data/recordings/2025/06/xx.wav这种目录结构就够了。注意定时清理过期录音(根据公司保存期限要求设定保留天数),否则磁盘很快会被占满。

9.3 备份策略

客户数据是核心资产,但不需要多复杂的备份方案。我用的mysqldump每天凌晨 3 点全量备份一次,保留最近 7 天;每周日凌晨做一次离线上传,保留一份到其他机器。这比高可用集群、读写分离方案更实用,毕竟系统挂了可以重建,数据丢了才真正要命。

10. 最终项目复盘:哪些决定现在回想仍然是对的,哪些该改

如果现在让我重做一遍 DeskcommCRM,有几件事我会坚持,有几件事我会调整:

坚持的部分:

  • 通讯工作台和 CRM 一体化的定位,这是这个系统存在的意义
  • 强制跟进记录和跟进任务机制,它保证了“数据不沉没”
  • 把所有客户互动按时间线倒序展示,简单但特别有效
  • 报表先做朴素列表再做可视化,业务方真正关心的是数字,不是图形

调整的部分:

  • 一开始就应该引入customer_identity表,而不是等合并事故发生了再补
  • 企业微信 unionid 识别应该在需求分析阶段就调研清楚,别等上线后才发现
  • 工单模块可以再轻一点,把“工单状态流转”做成纯字段更新,不搞状态机,更符合小团队实际
  • 提醒推送的频次从一开始就该保守,提醒功能的“疲惫感”是真实存在的

另外,有一些想法我想对正在犹豫“要不要自研 CRM”的朋友说。如果你所在公司的业务流程完全通用——Salesforce 那种标准销售管道就能覆盖——那么买现成的确实比自研便宜。如果你的业务流程有大量“跟客户沟通的过程管理”需求,尤其是客服团队和客户多次交互、多渠道合并、通话记录与客户档案自动关联,那么市面上很多通用 CRM 反而不合适,自研一个轻量系统是值得的。

DeskcommCRM 的核心不是用了什么高深技术,而是精准贴合了“客服需要在一个界面里完成接待 + 记录 + 跟进”这个场景。这个场景在很多行业都有,但很少有一个标准产品能配得那么好。做好一个内部系统,有时候比的不是技术难度,而是对业务现场的理解深度。你是把系统做出来让人用,还是做了个系统等人来填数据,用两周就能见分晓。每次上线前,我都会问自己一句:“这个功能上线后,客服真的愿意每天打开它吗?”如果答案犹豫,那就别上了。

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

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

立即咨询