ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

从0到1自研轻量级CRM:架构设计与实践全解析

2026/9/17 1:40:02 拓冰建站 浏览量
从0到1自研轻量级CRM:架构设计与实践全解析 1. 项目背景与整体定位为什么我会做 DeskcommCRMCRM 这个词在国内已经被说烂了市面上从国际大厂到各种轻量级 SaaS少说也有上百款。我一开始也没想过要自己写一套真正动手是在把市面产品试了一圈之后被一个很现实的问题给卡住了销售团队要用的功能永远只占系统的 20%但企业要付的是 100% 的钱还有一堆用不上的复杂流程在后面拖着。DeskcommCRM 这个名字是我临时起的拆开来看很直白——Desk 代表桌面端Comm 是 Communication合起来就是一套以桌面工作台为核心、把客户沟通记录和业务数据打通的客户管理系统。之所以叫这个名是因为这个项目从一开始就没打算做一个大而全的重型系统而是围绕一个核心痛点来设计销售和客服每天大量的沟通行为散落在各种渠道里电话、微信、邮件、面谈信息没有沉淀下来客户跟进就会断层。在动手之前我把需求理得很清晰核心就三件事。第一是客户信息统一管理把散落在 Excel、微信聊天记录和个人通讯录里的客户资料集中到一个地方至少要让这个客户上次聊到哪了这个问题在 3 秒内能回答上来。第二是跟进过程可追溯谁在什么时候跟进了哪个客户、聊了什么、下一步计划是什么这不仅是管理需要也是销售交接时最基本的底气。第三是数据能反哺决策通过商机阶段、跟进频率、转化周期这些维度让管理者知道团队的业务健康状况而不是只凭感觉拍脑袋。这套系统适合谁来参考我觉得主要是两类人。一类是还在用表格管客户、想自建一套轻量 CRM 的中小团队负责人另一类是自己在做 Side Project 的开发者想了解一个实际业务系统从需求梳理到落地实现的全过程。我会把当时设计时踩过的坑、反复推翻过的方案都写出来这些内容在官方文档里基本看不到。2. 整体架构设计与关键决策拆解2.1 技术选型为什么没有走微服务全家桶这个项目刚开始的时候团队里确实有人提议上 Spring Cloud 那套微服务架构理由也很充分——以后业务大了肯定要拆分。但我在技术选型时非常坚持一点架构的复杂度必须和业务规模匹配否则就是在给自己挖坑。最终确定的技术栈是前端用 Vue 3 加 Element Plus后端用 Python 的 FastAPI数据库选 PostgreSQL缓存用 Redis部署直接 Docker Compose 搞定。这个组合在大多数团队里可能显得有点土但它有一个非常大的优势整套系统任何一个人都能完整看懂出了问题半小时内能定位新成员入职一周就能上手改代码。FastAPI 选型的理由我多说两句。Python 在这个项目里最大的价值不是性能而是开发效率。CRM 这种系统核心是业务逻辑的快速迭代——今天加一个跟进字段明天改一个商机阶段后天调一下报表口径。用 FastAPI 配合 Pydantic数据模型和 API 校验可以一次定义完成响应时间基本都在 50 毫秒以内对这个场景来说完全够了。如果你非要问我为什么不用 Java 或者 Go我的回答很简单团队里最熟什么就用什么技术选型不是选最强的是选最能驾驭的。数据库选了 PostgreSQL而不是 MySQL这一点我后来觉得非常明智。CRM 系统有大量需要关联查询的场景客户、联系人、商机、跟进记录、合同随便一个列表页就是四五张表 join。PostgreSQL 在复杂查询的优化器表现上确实比 MySQL 更稳定JSONB 字段在处理自定义属性时简直就是救命稻草。2.2 核心数据模型的设计思路CRM 的数据模型是整个系统的地基这块我当时画了很久的 ER 图反复调整了三四版才确定下来。最核心的五张业务表分别是customers 客户表、contacts 联系人表、leads 线索表、opportunities 商机表和 activities 跟进记录表。这五张表之间的关系我先用一个不太严谨但很好理解的方式来描述线索是还没验证的潜在客户客户是已经建立联系的公司主体联系人是客户公司里具体沟通的那个人商机是客户身上正在推进的销售机会。一个客户可以有多个联系人一个联系人可以对应多个商机一个商机的所有沟通记录都挂在 activities 表里。这里有一个特别容易踩的坑很多 CRM 会把客户和联系人的概念混成一个表然后通过是否主联系人这种字段来区分。短期看确实减少了表数量但业务稍微复杂一点就受不了。比如一个公司有三个对接人分别负责技术、采购和商务你要是把这三个人塞在同一条记录里那跟进记录到底挂谁头上我在这版设计里把 customers 和 contacts 拆开用 customer_id 做外键关联后续所有模块的开发都轻松很多。activities 表我单独说因为这张表是整个系统最繁忙的表。每条客户沟通记录都要落在这里包括电话通话记录、微信沟通纪要、拜访纪要、邮件往来。这张表的字段设计我花了很多心思字段名类型说明idbigint主键customer_idbigint关联客户 IDcontact_idbigint关联联系人 ID可空activity_typevarchar类型call/meeting/email/wechat/othercontenttext沟通内容摘要next_actionvarchar下次跟进计划next_action_datedate下次跟进日期owner_idbigint跟进人created_attimestamp创建时间设计这张表时我做了一个很关键的决策跟进内容允许非结构化不强求每个字段都填但 next_action 和 next_action_date 必须填。这个约束保证了销售每次记录完沟通后必须想清楚下一步动作而不是写完就丢。后续做今日待跟进功能时直接查这两个字段就能生成每个人的工作清单效率极高。商机阶段我用了一个单独的阶段枚举表初始版本设了五个阶段初步沟通、需求确认、方案报价、商务谈判、成交。加上了赢单率预设用于报表计算销售预测。这个阶段定义一开始被业务负责人质疑太粗但实践证明粗有粗的好处——销售填起来不抗拒数据可信度高。你在一个 CRM 里把商机阶段搞出十几个选项销售根本不会认真选。2.3 权限模型与数据隔离方案权限设计在一个多人的 CRM 系统里是绕不开的而且做不好会出大问题。我见过某团队用 CRM 时销售总监能看到所有销售的客户明细结果两个销售为了抢一个客户吵到老板那里去场面非常难看。DeskcommCRM 的权限模型我最终采用了经典的 RBAC 数据范围两层方案。角色层面分为超级管理员、团队负责人、普通成员和只读访客四种角色每种角色在功能菜单上有不同权限。数据范围层面则分四级全部数据、本部门数据、本人数据和自己创建的客户。这个设计的好处是灵活团队负责人可以看全部门的客户情况来分配资源普通销售只能看到自己的客户避免撞单纠纷。数据范围的实现我是在所有业务查询的 SQL 层自动追加过滤条件而不是在业务代码里到处判断。具体做法是写了一个依赖注入函数从当前登录用户信息中解析出数据权限范围然后作为查询条件强制注入。这样一来哪怕业务代码里漏写了权限判断最终执行 SQL 时也会被兜底拦截安全性高很多。展开说一下本人创建这个数据范围这是给售后和客服角色准备的。销售离职会把自己名下的客户转给别人但那些自己录入的零散客户记录往往成为孤儿数据。新增本人创建这个维度后客服在查找自己之前处理过的客户时会方便很多同时也不会越权看到其他人的跟进详情。2.4 通信集成这个模块是怎么想明白的名字带 Comm 不是白带的。我最初的设想是让系统直接接入企业微信和邮件把往来消息自动同步到客户时间线里彻底消灭信息孤岛。但真的做起来才发现通信接口的坑比想象中多得多尤其是企业微信的接口文档字段之间各种隐式依赖读取消息时稍不小心就把事件拉重了或者拉漏了。后来我把通信这块收敛成一个更务实的方案做半自动集成不追求全自动同步。企业微信的服务端 API 仍然保留用于读取外部联系人列表和基本资料解决客户在公司哪个账号手里这个基础问题。但具体的消息记录我不强行抓取而是在系统里提供快捷登记功能销售跟客户聊完后花十秒钟把本次沟通的关键信息填进去。很多人可能会说这样不还是要手动录入吗我的回答是信息化的核心目标不是消灭一切人工操作而是把有价值的行为数据沉淀下来。强行自动化需要应对各种异常场景通信消息的抓取还会涉及合规风险投入产出比并不划算。而且实测下来让销售用系统自带的通话外呼功能打电话通话记录自动生成再手动补一句摘要整个过程不超过 30 秒团队接受度反而很高。3. 核心功能建设与实操过程3.1 客户 360 度视图让所有信息在一个页面里说清楚客户详情页被视为整个系统的门面。我的目标是打开任意一个客户页面你能在五分钟内了解这个客户与公司的全部互动历史不必再点开五六个子页面去拼凑信息。实现这个功能时我设置了一个三段式的页面布局。顶部是客户核心摘要包括公司名称、行业、规模、来源渠道、所属销售、当前客户状态中间是联系人列表每个人显示姓名、职位、电话、微信以及最近一次联系时间下方是一个按时间倒序排列的完整时间线客户的所有跟进记录、通话记录、商机变动都会按时间轴展示出来。时间线是这个页面的灵魂。从技术实现上我建了一张统一的 timeline_events 表不同类型的业务动作都向这张表写入事件记录展示时按时间排序一次性查询。这样虽然写入时多了一步操作但读取时非常顺畅不用跨表 join 多种类型的数据。用来处理客户最近 20 条动态这类查询普通服务器上一次请求基本都能在 20 毫秒内返回。页面上我还加了一个关键信息速览卡片把客户的风险点直接标出来比如超过 15 天无任何跟进记录的沉睡客户、长时间停留在一个商机阶段未推进的停滞商机。这块需要定期跑一个定时任务去扫描数据打标记我直接用 PostgreSQL 的 pg_cron 插件实现语法就和普通的 SQL 更新语句一样简单。3.2 跟进任务与日程提醒销售执行力的发动机有了客户信息和商机模型后如果缺少任务提醒机制系统就只是一个静态的信息仓库。DeskcommCRM 的跟进任务模块我做了分级设计。第一级是我的待办登录系统后默认展示今日需要跟进的客户列表、逾期未处理的任务、今日到期商机的清单。这个列表的数据来源就是前面提到的 activities 表中的 next_action_date 字段再叠加一个 today_tasks 表用于记录计划和重复性任务。第二级是销售漏斗视图每个人可以在列表里直观看到自己手上处于不同阶段的商机数量和金额点一下就能进入批量操作页面。任务的触发规则我设计成了一套简单的规则引擎支持按条件自动生成任务。举个例子如果一个客户从商机进入方案报价阶段后 5 天内没有新增跟进记录系统会自动生成一条提醒任务分配给负责人。这类规则在管理后台配置规则存成 JSON 格式每次定时任务扫表时统一解析。当初没有引入 Drools 之类的规则引擎原因很简单——CRM 里的规则都是简单的 if-then没必要为了花哨引入一个学习成本高且难排错的重组件。提醒方式我接了邮件和钉钉机器人每天上午九点推送一次当日待办摘要。这里有一个经验分享提醒消息不要过于频繁否则销售会习惯性忽略。最佳实践是每天早上一条汇总消息加上某条任务临期前 2 小时的一条单独提醒这个节奏实测下来最不容易让人产生厌烦情绪。3.3 数据报表与可视化让管理层看到发生了什么和接下来会怎样业务系统跑一段时间后管理层的需求会自然从谁能看到信息升级到数据能告诉我们什么。我在报表模块的实现上没有引入第三方 BI 工具而是直接用 ECharts 在 Vue 前端画图、后端做聚合查询接口。最核心的三个报表页面分别是销售漏斗转化率分析、团队业绩进度追踪和客户来源渠道效果对比。销售漏斗页面可以通过一个堆积柱状图展示整体转化趋势同时通过表格列出每个阶段的商机数量、金额和转化率。团队业绩进度追踪用进度条展示每个销售的季度目标完成率并支持下钻看到其商机和客户明细。做报表最大的坑是数据口径不一致。比如成交金额到底是按合同签订时间算还是按回款时间算新客户是按首次录入时间算还是按首次成交时间算这些口径如果不和数据负责人对齐报表做出来就是两张皮。建议的做法是所有指标口径在代码中以常量或配置文件形式统一定义并在报表页面上增加一个小图标注释点击可以看到口径说明。这样既方便内部核对也让管理层明白数字背后的含义。技术实现上后端用一个通用的 report_query 服务接受前端传入的报表类型、时间范围和维度组合动态拼接 SQL。这个服务一开始做得比较粗糙后来我重构了一次把查询条件和聚合逻辑封装成配置化结构新增一张报表页面基本上只需要写一个配置项和一段前端模板不用再写一遍聚合查询。3.4 客户导入导出从 Excel 迁移到系统的第一道关卡从旧管理方式切换到新系统时最让人头疼的是历史数据迁移。很多团队买了一套 CRM 后用不起来卡就卡在数据搬不进去这一步。我在系统开发时把客户导入导出功能做在了非常靠前的位置而且是完全可用的版本。导入功能支持标准的 Excel 模板下载字段包括客户名称、联系人姓名、电话、微信、行业、来源、备注等十几个常用项。后端解析时我用的库是 pandas 加 openpyxl分三步处理先做数据校验检查必填项是否为空、手机号格式是否合法、客户名称在系统内是否已存在再进入数据清洗把明显错误的格式做归一化比如手机号去空格加区号、日期格式统一最后分批量写入数据库每批 500 条写入过程放在事务里任何一条失败就回滚当前批次并记录失败行号。当时我在导入功能的代码里反复跑了很多次测试发现最容易出问题的往往是 Excel 里那些看不见的字符。比如某客户在 Excel 里名字前多了一个空格导入时就会误判为已存在或者重名。解决方案是导入前的数据清洗把所有字符串字段执行 trim、去全角空格特殊字符统一做转义处理。这一步虽然简单但能帮你省掉非常多售后时间。导出功能就直接了前端点一下按钮后端生成 CSV 返回下载链接不过 Excel 打开 CSV 有时候会出现中文乱码。解决方式是导出的 CSV 文件自动带上 UTF-8-BOM 头Excel 打开就正常了。这个小技巧我当时找了很久的解决思路分享给同样被乱码坑过的人。4. 性能优化与稳定性保障4.1 列表页慢查询优化实录系统上线初期数据量小所有页面响应都很快。但运行了半年之后客户表到了 5 万行activities 表到了 30 万行麻烦就来了客户列表页每次打开要四五秒销售翻完一页等半天整个团队开始抱怨系统卡。我第一反应是去看后端日志里的慢查询记录。因为 FastAPI 的 SQLAlchemy 默认打印所有 SQL把 execute 日志拉到分析工具里很快就定位到最严重的几条客户列表分页时count 全表扫描、关联 contacts 和 opportunities 都是全表扫。这一波排查让我深切体会到大型系统性能问题大多数不是硬件问题而是索引问题。解决思路分三步走。第一步为所有外键列和需要过滤的字段建索引。customers 表的 owner_id、created_atactivities 表的 customer_id、next_action_dateopportunities 表的 customer_id、stage。第二步对列表页的分页查询把 count 查询优化成基于主键的覆盖索引扫描避免二次查表。第三步对 activities 时间线这种高频访问的子列表限制查询行数每次只取最近 50 条加载更多用游标分页。优化完成后客户列表页首次打开时间从 4 秒降到了 300 毫秒左右activities 时间线从 1.2 秒降到了 80 毫秒团队反馈终于像网页应用了。这次优化其实没有任何高深技巧核心就是建立索引意识在开发阶段就预判哪些字段会被用于 where、order by、group by提前建好索引。4.2 缓存策略哪些数据需要 Redis哪些不需要Redis 在这个系统里没有成为缓存万能药——不少系统把所有查询结果都塞 Redis结果缓存一致性维护成本比数据库查询还高。我在 DeskcommCRM 里对缓存的使用非常克制只有三类数据用了 Redis。第一类是用户权限信息登录后将用户的角色和数据权限范围缓存起来30 分钟过期避免每次请求都查权限表。第二类是数据字典类型的数据比如商机阶段列表、客户来源选项、行业分类这类数据几乎不变缓存 24 小时完全没问题。第三类是仪表盘的汇总统计值每次查询结果缓存 5 分钟即使数据新增了一些记录报表展示上有 5 分钟延迟对业务决策来说毫无影响。其他所有实时性要求高的业务数据查询一律直接查 PostgreSQL。CRM 这类系统单机数据库的能力远比大家想象中强适度优化下支撑几百人的团队日常使用完全不成问题。不要为了用 Redis 而用 Redis多一个组件就多一份运维成本和故障风险。4.3 数据备份与容灾方案我见过不止一个团队CRM 跑了一年数据没备份过一次然后一个误操作把客户表清空了整个销售团队等同于失忆。数据备份这事应该在系统部署上线第一天就做好。我采用的备份策略是 PostgreSQL 每日全量备份加 WAL 归档。全量备份用 pg_dump每天凌晨三点执行一次备份文件直接传到阿里云 OSS 上按时间戳保留 30 天。WAL 归档实现时间点恢复属于进阶需求初期如果觉得复杂至少保证每日全量备份是完整可用的。备份恢复演练这件事大家普遍忽略但你一定要试一次。我当时第一次做恢复演练时发现pg_dump 恢复的库在权限和序列自增上会有小坑。比如自增主键的序列不会自动恢复插入新数据时会报主键冲突。后来我写了一个恢复脚本在 pg_restore 完成后自动检查所有表的主键序列并把序列值设为当前最大值加 1。这个细节如果不做一次真实演练几乎不可能发现。4.4 线上部署的几个安全细节部署层面我采用的是 Docker Compose 编排前端 Nginx 容器加后端 API 容器加 PostgreSQL 容器再加 Redis 容器。几个服务放在同一个 Docker 网络里对外只暴露 Nginx 的 80 和 443 端口数据库和 Redis 的端口不映射到宿主机外部无法直接访问。安全方面的几个细节值得特别注意。第一强制启用 HTTPS证书用 Lets Encrypt 自动续期。第二所有 API 接口统一加认证中间件对未携带 Token 的请求返回 401。前置拦截逻辑是白名单路径如登录接口、健康检查接口通过其余全部要求 JWT Token 有效才能进入JWT 过期时间设 24 小时最近活跃用户自动续期。第三管理后台的登录接口加上验证码防止暴力破解。第四文件上传功能严格限制扩展名和文件大小避免被人传了一个 WebShell 上来。这些安全措施写起来简单但实际踩过坑之后才知道重要。我曾在一次临时测试环境忘记关掉远程数据库端口不到两天就被人扫描并尝试暴力破解。安全这东西防的不是什么高级黑客防的就是自动扫描脚本和贪图便利的粗心。5. 实际使用中的高频问题与避坑指南5.1 常见问题速查表系统上线后的这几个月我收集整理了大量用户反馈这里挑出最常出现的问题做个速查表你在自己搭建类似系统时多半也会撞上。问题现象根本原因解决方案客户列表出现重复记录导入时未做重名校验或销售手工录入时不规范导入前增加客户名称相似度校验人工确认合并时间线里出现幽灵跟进记录多个设备同时登录提交时序问题后端对同一客户并行写入加锁用乐观锁版本号今日待办页面数据不准时区设置不一致美国时间和中国时间混用所有时间统一存 UTC展示时按浏览器时区转换报表数字对不上指标口径不统一用配置中心维护口径定义报表页面显示口径说明导出 Excel 后日期变成一串数字Excel 对日期格式识别问题导出时日期字段强制定为 yyyy-mm-dd 格式文本5.2 业务推进中的三个隐藏陷阱第一个陷阱是重录入、轻使用导致的数据质量低下。上线初期很多销售觉得录入客户信息是额外负担结果系统里全是未命名客户和空字段数据报表完全失真。我后来做了一个细节调整把客户来源设为录入阶段的必填项否则保存不了。一开始确实引发一阵抱怨但坚持两周后新数据质量明显改善销售自己看报表时也认可了。要记住没有数据质量CRM 就是空壳。第二个陷阱是权限设置过死导致协作受阻。我有一次把数据范围严格限制在各自名下结果出现同一个客户被销售 A 和销售 B 各自跟踪、互不可见的局面最后客户被两边同时报价客户非常不满。后来在权限模型里加了共享客户功能客户负责人可以把客户主动共享给指定同事双方都可见、可协作但最终主负责人仍是唯一 Owner这就兼顾了协作与责任归属。第三个陷阱是过度追求自动化流程。刚开始设计系统时我计划把所有销售流程全部自动化比如自动分配线索、自动触发提醒、自动调整商机阶段。结果测试时发现自动化逻辑越复杂出错时越难排查而且销售对系统的信任感会因一次错误提醒而下降。建议的原则是自动化只做高频、确确定性强的动作复杂决策还是留给人来做。6. 后续演进与我的个人体会DeskcommCRM 目前已经稳定运行了大半年服务了三个部门、四十多人的团队沉淀了超过十万条客户行为记录。要说做这个项目最大的收获反而不是技术上的成长而是让我彻底明白了一套业务系统能跑起来三分靠代码七分靠业务理解和对人性的把握。后续我计划在几个方向上继续完善。一是把销售预测做得更细基于历史转化率让系统自动算出每个商机的成交概率和预期回款时间帮助管理者提前调配资源。二是接入更多沟通渠道特别是把企业微信的聊天存档功能用好在合规前提下让沟通数据自动沉淀。三是增加客户健康度评分模型把客户的活跃度、跟进频率、需求匹配度综合成一个分数让销售可以按分数排序优先跟进高价值客户。最后分享一个个人经验如果你也在规划做类似的系统千万不要一上来就抄大厂的复杂功能。先把手头最痛的两三个业务场景跑通再逐步加功能。CRM 的本质不是让销售多填表格而是帮他们少填表格、多花时间在真正的客户沟通上。你只要记住这个少即是多的原则就永远不会把系统做成销售团队的负担。挑一个你觉得当前最麻烦的功能开始动手吧做出来你自己会用、你的同事愿意用这套系统就已经成功了一半。