☰
从零自建轻量CRM:数据模型、跟进提醒与团队落地实践
2026/10/20 6:49:40 网站建设 项目流程

DeskcommCRM 这个名字,是我给自己团队做的一个内部客户管理系统起的。起因很朴素:那段时间我每天要问三遍“这个客户最近聊到哪了”,而答案分散在微信聊天记录、Excel 表格和个人通讯录里。做销售和客服的同事也觉得烦——所有关于客户的消息都像蒲公英一样散在各处,真要交接的时候,根本拼不出完整的画面。

后来我花了几个周末,把 DeskcommCRM 从零搭了起来。它不是一个能对接所有第三方平台的庞然大物,而是一个把客户档案、沟通记录、跟进提醒这三件事做到极致的轻量工具。这篇文章我把它拆开来讲:数据模型怎么设计、沟通记录怎么沉淀、提醒机制怎么避免沦为骚扰、权限和备份怎么在小团队里落地。如果你也在考虑自建一个内部系统,或者正准备选一款轻量 CRM,这里面的取舍逻辑应该能帮你少走不少弯路。

1. 为什么会有 DeskcommCRM:被会话碎片化逼出来的内部工具

1.1 信息散落是原罪

我最早判断一个客户管理系统值不值得做,不是看它功能多不多,而是看一个具体场景:一个客户从首次咨询到最终成交,中间经历了哪些沟通,历史记录能不能十分钟内拼出来。

在自建 DeskcommCRM 之前,我们的真实情况是这样的:销售 A 把客户加了微信,聊了需求之后顺手记在手机备忘录里;客服 B 接手技术支持,把问题贴在钉钉群里;主管要了解项目进度,只能挨个问人。最致命的是有人请假或离职的时候,客户资料跟着个人账号一起“消失”了,新接手的人只能重新聊一遍。

这不是某一个人的问题,而是没有统一载体带来的必然结果。只要客户信息不集中在一个可信的地方,任何一个环节的沟通都是“说过了就没了”。所以我在设计 DeskcommCRM 时,把“记录客户完整时间线”放在了第一位,而不是先做那些花哨的销售漏斗报表。

1.2 为什么自己写而不是买现成的

肯定有人会问,市面上成熟的 SaaS CRM 一大把,功能比我自建的全多了,为什么还要自己动手?当时我的判断有两点。

第一,小团队用大系统,配置成本往往比功能收益更大。动辄要设置字段权限、审批流、多级部门,我们团队十几个人,根本没有那么复杂的组织架构,花两周配置还不如花两周写个趁手的工具。第二,客户数据是最核心的资产,放在自己服务器上,至少在数据所有权和二次开发上没有任何顾虑。DeskcommCRM 从一开始就定位在“内部工具”,不对外销售,也不需要支持多租户,这让我在设计上能砍掉大量复杂度,只保留真正天天用到的东西。如果你也是一个小团队的技术负责人,我建议先想清楚这一点,免得被“功能全”绑架。

2. 客户与跟进记录的数据建模:从字段设计看系统骨架

2.1 核心表结构:客户、联系人、跟进记录怎么落库

DeskcommCRM 的数据库我用了 MySQL,最核心的是三张表:customers、contacts、followups。

customers 表存客户主体,也就是“公司/组织”这一层,字段包括公司名称、所属行业、客户来源、状态、负责人的 user_id、自定义字段、创建时间、更新时间。contacts 表存联系人,一个人可以对应多个联系人,比如采购经理和财务负责人,字段包括姓名、电话、邮箱、职务、是否主要联系人。followups 表存每一次沟通跟进记录,字段包括关联客户、关联联系人、跟进人、跟进类型(电话/微信/面谈/邮件)、沟通内容、下次跟进时间、提醒状态。

这三张表分开设计的原因很简单:客户和联系人是一对多关系,如果强行合并成一张大表,同一个客户在不同联系人时会出现大量重复冗余数据,后面查询和维护都会很难受。跟进记录单独成表,是因为每个客户的跟进动作会非常多,必须能高效查询,并且天然按时间排序,不能塞进客户表的文本字段里。

2.2 客户状态机:给生命周期立规矩

客户状态我一开始没有做约束,结果就乱了。有人把“已成交”标成“合作中”,有人把“没回音”删掉,还有人把一个客户的多个联系人改了不同状态。后来我引入了明确的状态机,只允许按固定路径流转:新线索 → 联系中 → 有意向 → 已成交,或者从任意状态进入“已流失”。

这个设计看起来死板,但好处非常明显。第一,状态可追溯,每次流转都会写入 status_logs 表,记录从哪个状态到哪个状态、操作人是谁、什么时间、为什么改。第二,避免了随意修改对统计报表的污染。只要状态机统一了,后续算转化率、算跟进效率,数据才是可信的。

2.3 自定义字段与索引的取舍

行业不同,客户信息差别很大,有的关注公司规模,有的关注结算方式。我不想为了几个特殊字段频繁改表,所以做了 JSON 类型的 custom_fields,存那些不常用但偶尔要记的信息。查询的时候,MySQL 可以直接用 JSON_EXTRACT 取特定字段,数据量不大时性能完全够用。

但自定义字段不能滥用,我特意做了限制:管理员最多新增二十个自定义字段,而且字段名不能乱起,得有统一规范。至于索引,我一开始只在主键上建立了索引,结果查询一多就慢。后来在 followups.customer_id、followups.next_follow_up_at、customers.status、customers.owner_id 上都加了普通索引,效果立刻不一样。核心原则是:凡是要在 WHERE 和 ORDER BY 里用到的字段,都要提前规划好索引,不要等慢了再加。

3. 让沟通记录真正被用起来:时间线、搜索与摘要设计

3.1 录入要快到没感觉:快速记录入口

很多系统记录做不好,不是功能缺陷,而是“录入成本太高”。打开页面、找客户、点新增、填一堆表单,这一套流程下来,销售早就不耐烦了。

DeskcommCRM 做了两件事降低录入摩擦。第一,支持全局搜索框直接输入客户名,回车就到客户详情页。第二,客户详情页顶部就是一个固定的大输入框,默认选中最近跟进类型,按 Ctrl+Enter 就能保存,全程不需要鼠标。刚开始有人不习惯快捷键,后来用过几次就回不去了。实测下来,单条跟进记录的录入时间能控制在十秒以内,这一步直接决定了团队会不会每天坚持用。

3.2 时间线:把碎片拼成故事

每个客户详情页里,我把所有动作按时间倒序铺开,形成一条完整时间线。包括每一次电话沟通、微信交流记录、面谈纪要、合同金额变更、状态流转,甚至包括谁在什么时间查看过这个客户。这样做的好处是,接手人不需要翻零散记录,打开页面就知道这个客户从哪里来、经历过什么、现在卡在哪个环节。

有一次一个销售去客户公司拜访,临出门前看了时间线,发现这个客户三年前就联系过一次,当时对方嫌报价高没合作。他现场就说“我知道你们之前顾虑什么,现在的方案已经变了”,一下就拉近了距离。这条时间线,就是 DeskcommCRM 对“客户记忆”的建模。

3.3 搜索与自动摘要:让记录变得可追溯

数据录进去了,如果搜不到,等于白录。DeskcommCRM 的全局搜索我用的是 MySQL LIKE 配合全文索引,小数据量下表现稳定,多关键词用布尔模式。搜索结果会做关键词高亮,点击结果直接跳转到对应客户时间线。

自动摘要这块我没有一开始就上大模型,而是用了一套非常朴实的规则引擎:从跟进记录里抽取电话号码、金额数字、日期时间,自动生成标签,比如“询价 20 万”“约了 3 月 15 日演示”。虽然不如 AI 灵活,但零成本、可解释、速度快。等数据量到一定规模,再让 LLM 接口跑一遍做语义汇总也不迟。先解决有没有,再解决好不好的问题。

4. 跟进提醒:从“定时跑批”到“真正不打扰”的迭代过程

4.1 提醒怎么发:从定时任务说起

跟进提醒是整个 DeskcommCRM 里被人感知最明显、也最容易做崩的功能。思路其实很简单:followups 表里会存一个 next_follow_up_at 字段,系统每分钟跑一次定时任务,找出“到达或超过下次跟进时间”并且“还没有完成”的记录,发给对应的负责人。

通知渠道我做了三级:站内信、邮件、企业微信机器人 webhook。站内信是最基本的,所有人打开系统就能看到;邮件用于发送每日提醒汇总;企业微信机器人则做即时通知。配置都很轻,没有用消息队列,小团队先把流程跑通最重要。

4.2 重复提醒、时区、状态联动:三个绕不开的坑

这个部分我踩的坑最多,列出来给大家参考。

第一个是重复提醒。定时任务每分钟跑一次,如果一条记录一直没被处理,每分钟提醒一次能把人逼疯。我的方案是给 followups 表加 reminder_status 字段,默认 pending,提醒发出后立刻更新为 notified,保证同一任务不会重复通知。如果处理人选择“延期”,才重新置回 pending,并更新 next_follow_up_at。

第二个是时区。有同事出差到国外,系统按服务器本地时间提醒,他那边经常半夜被通知。后来我把提醒时间统一存储为 UTC,展示时按用户设置的时区转换,并且支持设置“仅工作日提醒”“仅 9:00—21:00 提醒”。这个需求看似小,不做就是持续的抱怨。

第三个是状态联动。客户成交之后,之前挂在客户名下的未完成跟进提醒必须自动取消,否则会产生大量无效提醒。我的实现方式是在状态流转到“已成交”或“已流失”时,触发一个事务:更新客户状态,同时把该客户下所有 reminder_status = pending 的记录标记为 cancelled,并写一条审计日志。如果不加这一步,提醒模块就会变成一个天天喊错话的喇叭。

4.3 “今日待跟进”:给提醒做减法

除了即时提醒,我在首页放了一个“今日待跟进”板块,只显示今天和已经超期的跟进任务。SQL 大概是这样:

SELECT c.company_name, f.content, f.next_follow_up_at FROM followups f JOIN customers c ON c.id = f.customer_id WHERE f.owner_id = :current_user_id AND f.next_follow_up_at <= NOW() + INTERVAL 1 DAY AND f.reminder_status = 'pending' ORDER BY f.next_follow_up_at ASC;

加上 owner_id 过滤之后,每个人进来第一眼只看到自己的事,不会淹没在团队所有客户里。这个设计刻意做了减法,因为提醒不是越多越好,而是要刚好在“还记得但快忘了”的时候出现一次。

5. 权限与数据保障:小团队也不能回避的一课

5.1 角色与数据权限的最小可用实现

DeskcommCRM 的角色分了四档:管理员、主管、普通成员、只读访客。权限矩阵大致如下:

操作管理员主管普通成员只读访客
查看全部客户是本部门仅自己的+显式共享仅被授权的报表页
新增/编辑跟进是是是否
删除客户/记录是本部门范围内自己的否
导出数据是是自己的否
成员与角色管理是否否否

数据行权限方面,所有客户表都带 owner_id 字段,查询时统一在 ORM 层拼接过滤条件,避免某个接口漏掉权限判断。共享客户则单独建 share_customers 表,存客户 ID 和被共享人 ID,查询时做一次 LEFT JOIN。这套模型并不复杂,但能解决小团队 90% 的权限场景,剩下的交给管理员手动处理就好。

5.2 审计日志与敏感操作

客户数据是公司资产,所以谁删了数据、谁导出了数据、谁批量修改了状态,这些敏感操作必须留痕。我建了一张 audit_logs 表,记录操作人、操作类型、操作对象、操作前后值、IP、时间。导出功能强制弹窗提醒,并且把导出记录写进审计日志。

这里有个实际体验:有次一个主管把一批客户负责人统一改错成另一个同事,还好有审计日志,几分钟内就定位到变更记录,回滚掉了。如果没有日志,这种错误只能靠人肉回忆,代价非常大。

5.3 备份、恢复与密钥管理

数据安全里,最不能省的就是备份和恢复演练。DeskcommCRM 的备份策略是:每天凌晨 2 点用 mysqldump 全量备份,压缩后传到对象存储,保留 30 天,同时每周做一次增量备份。命令大概是:

mysqldump -u backup -p deskcomm_crm --single-transaction --routines --triggers | gzip > /backup/deskcomm_$(date +%F).sql.gz

比备份更重要的是恢复演练,我给自己定的规矩是每个月至少在一台临时服务器上完整恢复一次。别到事故真的发生了才发现备份文件是坏的。密钥方面,数据库密码、OSS 密钥、邮件 SMTP 密码全部放在 .env 文件里,用 dotenv 加载,并且 .env 永远不进 Git 仓库。这可是我身边真实发生过的事:有人把云数据库密码写死在代码里推到仓库,第二天数据库就被删了。

6. 部署落地:一台 2C4G 的服务器能跑出什么效果

6.1 技术栈与部署实践

后端我用的 Node.js + Express + MySQL + Redis,前端是 Vue 3,整体非常常规,没什么炫技的成分。选型逻辑是:团队里大家都熟悉 JavaScript 生态,开发效率高,Node 处理这种 CRUD 密集型应用完全够用;MySQL 不用多说,稳定可靠,运维资料一搜一大把。

部署我用 Docker Compose 编排,核心服务三个:app(Node 服务)、db(MySQL 8)、redis(Redis 7,用于缓存首页待办和提醒去重)。一个基础配置大概是:

version: "3.8" services: db: image: mysql:8.0 restart: always environment: MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} MYSQL_DATABASE: deskcomm_crm volumes: - db_data:/var/lib/mysql ports: - "3306:3306" redis: image: redis:7-alpine restart: always volumes: - redis_data:/data app: build: . restart: always depends_on: - db - redis env_file: - .env ports: - "3000:3000" volumes: db_data: redis_data:

前端构建后由 Nginx 托管,同时用 Nginx 反代 /api 到 Node 服务,并开启 HTTPS。我个人强烈建议从第一天就挂上证书,不要用裸 HTTP,浏览器“不安全”的提示会直接影响同事对系统的信任度。

6.2 一台 2C4G 服务器能撑住多少数据

说点具体数据。我们目前是在一台 2 核 4G 的云服务器上跑,客户量一万出头,跟进记录三十万条左右,日常并发只有十几个人,系统响应基本都在百毫秒级别,相当轻松。MySQL 慢查询日志我设置了 1 秒阈值,三个月只出现过两次超时,都是因为后台导出任务全表扫描导致的,后来把导出改成异步执行就解决了。

如果你团队规模也就是几十人以内,不用一开始就上好几台机器,先把容量预留好、做好索引和备份,一台中等配置的服务器足够跑很久。真到了性能瓶颈,再考虑读写分离或者迁移到容器集群也不迟,架构上保持简单反而好迁移。

6.3 升级、监控与回滚

自建系统最怕“跑着好好的没人维护”。我的习惯是每次发布会先备份数据库,然后 docker compose pull + up -d。如果 app 启动后健康检查不通过,Nginx 会自动把流量切到旧版本容器,实现最基本的回滚。这个健康检查我用的是 /health 接口,返回 app 版本号和数据库连接状态。

监控方面没有上复杂的 Prometheus 全家桶,而是用 Node 进程里挂一个简单的统计接口,每分钟输出请求数、错误率、平均响应时间,再用 shell 脚本定期检查。写报警机器人到企业微信,接口挂了或者磁盘超过 85% 就会收到通知。小团队的运维,简单直接能落地最重要。

7. 使用率比功能更重要:DeskcommCRM 的团队推广经验

7.1 让团队真正用起来的推广方法

系统做出来没人用,是比系统崩溃更常见的事故。我的经验是,先找两三个愿意反馈的“种子用户”,不要一上来就全团队强制推行。种子用户用顺手之后,他们的案例会自然传播给其他同事。

DeskcommCRM 录入做了快捷键和全局搜索,但真正的关键不是手感,而是习惯。我主动把每个同事的客户数据从 Excel 导进系统,然后帮他们把历史沟通记录补录了一遍。虽然补录花了大量时间,但这让所有人第二天打开系统时就看到自己的完整客户全景,而不是空荡荡的表格。人都是需要奖励反馈的,数据越全,大家越愿意继续录。我不定“每天录入十条”这种 KPI,而是每周看一次登录率和有跟进动作的人数占比,把这个当作系统健康度指标。

7.2 数据迁移与历史包袱

从 Excel 迁数据是个躲不开的活。DeskcommCRM 提供了一个标准导入模板:公司名称、联系人、电话、邮箱、状态、负责人、备注。导入时做两步处理:一是手机号去重,同一个手机号对应多个客户时自动合并为同一客户下的多个联系人;二是状态映射,Excel 里各种不规范的写法,比如“已合作”“合作中”“VIP”,统一映射到状态机定义的那几个合法状态上。

合并客户是我特别想提醒的一点。刚开始去重只按手机号,后来发现有个人名相同但公司不同的情况,乱合并会污染数据。所以合并逻辑要同时校验公司名称和手机号,拿不准的人工确认,绝不自动处理。这个原则在迁移这种不可逆操作里非常重要。

7.3 后续扩展:哪些功能值得做

DeskcommCRM 目前还在迭代,我列了几个“真正值得做”的方向,排在优先级的从高到低是这样:

  • 数据看板:老板和主管需要一眼看到本周新增线索数、跟进次数、成交转化率,纯 SQL 聚合就能出结果。
  • 回收站与恢复:防止误删客户数据,逻辑删除 + 30 天保留期,是安全感的来源。
  • 公海池:无人负责的客户自动回归公共池,轮流分给销售,这一步能明显提升线索利用率。
  • API 与第三方集成:比如把呼叫中心的通话记录自动拉进时间线,或者对接发票系统自动带出合同金额。

那些看起来特别酷但实际使用频率很低的功能,比如语音自动识别客户意向、OCR 扫描名片入库,我暂时都压着没做。原因很简单,在核心流程没有完全稳定的前提下,每加一个“高科技”功能都会带来新的维护成本,而它带来的实际增量往往被高估了。做内部工具最重要的标准只有一个:明天还有人愿意打开它。如果一个系统只是功能全,但没有任何人主动录入新信息,那它从落地那天起就已经开始腐烂了。

DeskcommCRM 这个项目,最让我欣慰的不是技术多复杂,而是它真的改变了团队的工作习惯。有个销售后来跟我说了一句:“现在谁要是没往系统里录跟进,我都觉得这个单子不靠谱。”我听完觉得,这就够了。

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

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

立即咨询