自研桌面CRM实践:本地优先架构与SQLite数据管理

📅 发布时间:2026/10/19 16:32:19
自研桌面CRM实践:本地优先架构与SQLite数据管理
想起来做DeskcommCRM这个项目起因很朴素团队里那套通用云CRM用了一年多销售们每天早上第一件事不是看客户而是先补录昨天忘填的字段。客户跟进记录断断续续销售主管想看一眼本周重点客户的推进情况得让四个销售分别截图发到群里。这套系统不是没有客户管理能力而是太重、太绕、太不符合一线销售的实际操作习惯。DeskcommCRM这个名字拆开看就是Desk桌面 Comm通信 CRM。意思是说这不是一个浏览器里打开的网页系统而是一个真正跑在桌面端的客户关系管理工具并且把沟通记录作为第一优先级。它能解决的核心问题非常具体客户信息散落在聊天记录、邮件、通话记录和个人Excel里销售每天要用好几个工具来回切换才能凑齐一个客户的全貌。DeskcommCRM把客户档案、跟进过程、任务提醒、沟通记录归档和基础统计做进一个桌面应用里让销售打开电脑就能看到今天该干什么、上周和这个客户聊到哪、下一步动作是什么。如果你也是小团队负责人、独立销售或者开发人员正在纠结要不要自研内部CRM或者想把现有CRM替换成更贴合业务的东西这篇内容应该能给你一些参考。1. 项目背景与需求定位为什么自研一个CRM1.1 从痛点出发云CRM到底哪里不好用在决定自研之前我们把团队对CRM的不满做了整整一个月的收集最终筛出高频痛点录入成本高、跟进断层、提醒无用、数据封死在系统里拿不出来。录入成本高是很多人忽视的隐性成本。通用CRM的客户表单动辄二三十个字段公司名、行业、规模、地区、来源渠道、客户级别、意向产品、预算额度、预计成交时间每个都标着必填。销售在电话结束后还要花五分钟填表一天打三十通电话光录入就占掉两个半小时。这个成本最终会变成“随便填填”和“明天再补”然后客户信息就彻底失真。跟进断层则是更致命的问题。大部分CRM把客户、联系人、跟进记录拆成三块独立的页面销售要看客户全貌就得跳三次界面。真正高效的CRM应该让销售在一条时间线上看到这个客户什么时候第一次接触、中间停顿了多少天、谁在哪个节点跟进过、客户当时提了什么条件。通用CRM做不到这一点因为它们的设计目标是“管理客户数据”而不是“辅助销售推进关系”。提醒无用这个痛点本质上是任务机制设计太死板。系统让你设置“下次跟进日期”但销售根本不记得去设置或者设置了也总是被其他事情挤掉。DeskcommCRM的做法后来完全不同我们直接把任务提醒和沟通记录绑定打完一通电话、发完一封邮件系统自动询问“是否设置下一步跟进”这个交互在桌面端做起来非常顺手。数据封死的问题表现在销售想按月汇总自己的客户情况发现导出功能只能导出表格不能导出完整的客户时间线主管想调整字段结构发现要提需求单、排期、等版本。这些限制都指向一个结论——无论是采购SaaS还是用开源系统改底层业务逻辑和交互习惯都改不动不如自己写一套真正可控的CRM。1.2 明确边界DeskcommCRM只做四件事自研最怕的就是需求失控越做越像大而全的ERP。我们在项目启动会上定了一条规矩只要不属于以下四件事的需求全部放进“以后再说”清单包括工单模块、财务模块、AI销售助手、移动端APP。第一件事是客户360度档案。一个客户下挂联系人列表、所属标签、行业分类、来源渠道、关联商机以及最重要的时间轴——所有历史沟通记录、跟进结果、客户状态变更都按时间倒序排在同一个界面上。任何一个人打开这个客户页三秒钟之内就能判断出客户当前处于什么阶段。第二件事是跟进流水与任务提醒。每次与客户的沟通都要产生一条带结果标记的跟进记录比如“已报价”“约访成功”“客户意向降低”“暂缓跟进”然后自动或手动生成下一步动作。桌面端通过系统通知弹窗提醒不依赖打开浏览器签到。第三件事是通信归档。把电话录音文件路径、邮件正文副本、IM聊天截图统一挂载到客户档案下。这一步是DeskcommCRM最独特的定位也是名字里Comm的由来。我们不追求自动接入微信或企业微信的API因为小团队的合规风险和开发成本太高而是采用“手动粘贴拖拽附件”的轻量方式让归档动作两秒内完成。第四件事是团队统计与共享。主管能看到每个人的跟进数量、转化情况、待办完成率业务组可以共享客户池和标签体系。统计不需要做成华丽的BI大屏几个关键数字就够了今日新增客户、本周跟进次数、商机转化率、平均响应时长。把边界划定之后大概判断出第一版需要三个月的业余时间能够完成实际用了两个半月后面有大段的时间反而花在了打磨细节和踩坑上。2. 整体架构与核心模块设计2.1 架构选型过程与最终方案技术选型这个问题团队内部争论了将近两周。最初有两个倾向一个是纯Web应用加浏览器访问开发快、维护方便另一个是Electron桌面应用加本地数据库体验好、数据可控。后来我们做了一个折中观察实验让三个销售分别用Web版和桌面原型各工作两天统计他们的操作路径结果是桌面版在“新建客户→记录跟进→设置提醒”这条核心链路上平均耗时比Web版少了40%。原因在于桌面端可以常驻后台全局快捷键呼出不需要在浏览器标签页之间来回切换。最终架构定为主进程加渲染进程的桌面应用壳本地使用嵌入式数据库存储业务数据通过轻量级后端服务在局域网内做团队数据同步。底层技术栈选型如下桌面框架Electron理由是小团队熟悉JavaScript生态IPC通信做本地能力扩展非常成熟跨平台打包方便。前端框架Vue 3搭配Element Plus表单和桌面布局组件现成够用开发效率高。本地数据库SQLite零配置、单文件、事务支持完整对单机上千客户、十几万跟进记录的数据量没有任何压力。同步服务Node.js写一个轻量HTTP服务基于本地文件存储同步日志局域网内用轮询或WebSocket做增量同步。搜索方案SQLite的FTS5全文索引覆盖客户名、公司名、跟进记录正文中文分词用内置的unicode61加自定义词典后续会细讲。这个架构的好处非常明显销售在笔记本上录入的所有数据先写本地再异步同步到服务端断网时完全不耽误工作。对于经常跑外拜访的销售来说这种本地优先的体验比云CRM强太多。同时SQLite单文件备份就是整个数据库备份每天早上自动把数据库文件复制一份到共享网盘和移动硬盘容灾成本几乎为零。2.2 核心模块划分与数据流按功能域拆分DeskcommCRM一共分成六个核心模块身份与权限、客户档案、跟进流水、任务与提醒、通信归档、统计报表。模块之间不直接互相引用而是通过一个统一的数据访问层进行读写避免循环依赖。数据流的主链路是这样的销售在客户页面点击“新增跟进”填写内容并选择结果类型系统先把记录写入本地SQLite然后提交到同步队列由后台线程在空闲时POST到局域网同步服务。服务端校验权限后将新数据追加进同步日志表并广播给其他在线客户端。其他客户端收到事件后更新本地对应客户的时间轴。整个流程在局域网环境下延迟通常在几百毫秒以内体验上接近于实时刷新。这里有一个设计取舍值得展开为什么不在服务端统一保存所有数据客户端只做展示原因在于我们对稳定性的要求高于一致性。销售拜访客户时经常在信号不好的办公楼、地下车库、客户公司的会议室里如果必须联网才能查客户信息体验会非常糟糕。我们宁可接受几秒到几十秒的同步延迟也要求所有操作必须本地可用。数据一致性问题通过版本号和更新时间戳解决后面单独讲。权限模块设计成三级角色管理员、主管、销售。管理员能改字段配置、管理用户、删除数据主管能查看所辖小组的客户和报表销售只能看自己的客户和共享池中的客户。没有做复杂的按数据范围授权这是刻意简化因为小团队的组织架构通常不超过两层。3. 关键功能实现与实操细节3.1 客户档案与重复检测客户档案是CRM的心脏但核心不在于字段多而在于传达信息的方式。DeskcommCRM的客户详情页采用三段式布局顶部是客户名称、所属阶段、标签、所有者、最后跟进时间这几个关键摘要中间左侧是联系人列表和基本信息中间主区域是时间轴右侧是可折叠字段区和附件区。字段配置在管理员面板里可以进行自定义但受限于类型数量——我们只允许使用文本、数字、日期、单选、多选和自定义标签六种类型。这个限制是刻意设置的因为在规划时发现一旦开放关联表、计算公式和复杂校验字段设计的复杂度会以指数级上升最终导致录入门槛再次变高。重复客户检测是客户档案模块里最实用也是实现最巧妙的部分。团队里销售各自录入客户经常会重复创建同一家公司的不同联系人。我们的策略不是做全局查重拦截那样误报太多反而惹人烦而是在销售点击保存时做轻量相似度匹配。核心算法用编辑距离加关键词权重规则是这样的如果客户名称完全一致直接拦截提示“已存在同名客户”。如果客户名称相似度大于80%弹窗提示“是否与已有客户合并”列出候选列表供销售确认。相似度低于80%但大于50%的情况只在保存后以较弱的提示方式展示“你可能认识来自同一公司的某联系人”让销售自行判断。相似度计算没有用复杂的向量模型而是先用Jaccard相似度对客户名称做粗筛再用编辑距离计算精确值。实际测试下来对中文公司名这种场景准确率够用处理两三万条客户数据的耗时在100毫秒以内不会有可感知的卡顿。3.2 跟进记录与任务提醒跟进记录的输入体验决定了销售愿不愿意用。DeskcommCRM把这个功能做成了整条产品线的核心交互在任何页面按快捷键CtrlShiftN直接弹出快速记录浮窗浮窗会自动带上当前正在查看的客户ID销售不需要切换到客户详情页就能记录。浮窗设计参考了记账软件的快手录入思路第一行是客户名称自动补全第二行是跟进方式选择电话、微信、邮件、面谈第三行是跟进结果类型第四行是大文本框填详情。最重要的是浮窗底部有一个醒目的“设置下次跟进”开关默认开启弹出后可以选择明天、三天后、一周后、自定义时间并且可以附带一条提醒附注。这个开关默认开启强迫销售必须主动关掉才能跳过设置这样任务提醒的覆盖率从云CRM时期的30%不到提升到了接近90%。任务提醒的调度完全在本地实现用的是定时器扫描任务表里到期且未完成的任务一旦命中就弹出系统通知。为了不让销售被通知轰炸做了分级策略到期当天早上9点弹一次逾期后每三天提醒一次逾期超过七天的任务自动汇总为邮件周报。提醒的文案会根据任务类型生成不同前缀比如“该联系XX公司的王总了上次沟通是12月3日当时约定了本周确认报价”。这个提醒逻辑最关键的参数是时区与日期的处理。我们要求所有时间字段统一用ISO 8601存储展示时再根据本地时区转换。虽然团队都在同一时区但统一格式带来的好处是后续如果接入了外部日历或出差到其他时区不需要改动存储结构。3.3 通信集成与邮件/IM归档通信归档模块是DeskcommCRM区别于其他CRM的亮点也是“Comm”的实体体现。但这里要坦白说我们并没有实现自动抓取微信、钉钉消息的SDK集成因为对于小团队来说合规风险、开发成本和平台API的不确定性都不值得冒。我们采用了一个更轻量但非常实用的方案——“手动归档模板化”。具体做法是这样的邮件类沟通销售把邮件原文复制或拖拽附件到客户详情页的通信归档区系统自动提取发件人、收件人、主题和时间生成本地备份记录。IM类沟通销售在聊天软件截图后直接粘贴CtrlV到归档区系统保存图片并自动打上当前时间和聊天对象标签。电话类沟通如果是用手机打的销售可以上传录音文件也可以只填一段手动摘要。为了鼓励销售归档系统做了一个小机制每归档一条通信客户时间轴上就出现一个带有沟通方式图标的节点时间轴视觉上会形成明显的沟通密度分布。销售和主管一眼就能看出哪些客户最近被冷落了。我们还接了一个不起眼但非常受欢迎的功能在客户详情页的通信区拖入一个文件后自动识别文件类型并生成对应的ICO图标音频文件可以直接在应用内播放图片文件可以预览这样销售不用额外打开文件夹确认内容。这个模块我踩过最大的坑是图片粘贴时的剪贴板格式处理。Windows系统复制截图后剪贴板里可能同时存在位图、HTML、文件等多种格式靠Electron的paste事件拿到的数据在不同版本、不同来源下格式不一致。最后用了一套兼容写法优先读file格式没有则读HTML中的img标签的base64数据再退一步才读raw图片数据并统一压缩到最大宽1920像素以控制数据库体积。3.4 报表统计与团队协作报表模块是管理层最喜欢的部分但我们刻意控制住没做自助式BI而是固定几个核心看板每个看板只展示7个以内的指标。这样做的理由很实际自助BI意味着要花大量时间做数据维度建模和前端组件抽象而且如果主管能力和数据素养跟不上自由度太高反而造成不会用。目前上线的固定看板包括今日概览今日新增客户数、今日跟进条数、到期任务数、逾期任务数、今日成交金额。个人周报本周新增客户、跟进次数、转化率、平均首次响应时长从首次建立意向到第一次有效跟进的时间间隔。团队漏斗按客户状态阶段统计数量从初识、意向确认、方案报价、商务谈判、成交到暂缓每个阶段的客户数一目了然。响应速度分析统计每个销售在收到新客户分配后的首次跟进延迟中位数这是反映销售主动性的关键指标。团队协作层面做了客户池机制。管理员可以建立一个共享客户池池中的客户没有固定所有者销售可以从池中认领认领后自动进入自己的客户列表。认领有效期是7天如果7天内没有任何跟进动作客户自动退回池中并通知原认领人。这个机制最初是为了解决公海客户无人跟的问题后来发现它意外地催生了团队内部的良性竞争因为大家都想认领最有价值的客户不跟进就会被系统收回。4. 技术实现的关键决策与编码要点4.1 数据模型设计单文件数据库怎么承载关联数据数据模型这块我们没有做花哨的微服务或图数据库整个业务就靠SQLite的十来张表撑起来。核心表结构设计上是这样的客户表customers存储客户基本属性包括id、name、industry、source、status、owner_id、created_at、updated_at、custom_fieldsJSON列存自定义字段数据。联系人表contacts一条记录对应一个客户下的一个人包含姓名、职位、电话、邮箱、微信。跟进记录表activities是业务核心每行记录一次跟进动作字段有id、customer_id、contact_id、type、content、result、next_action_date、creator_id、created_at。任务表tasks保存待办任务把跟进记录生成的下一步动作存在这里也支持手动创建独立任务。通信归档表communications保存邮件、截图、录音文件路径或元数据存储上直接保存文件路径文件实体放到数据库文件同级的attachments目录数据库里只存相对路径。用户表和角色表用来做认证与权限配置。这里有个设计细节值得说说custom_fields用JSON列而不是动态建列因为SQLite的ALTER TABLE操作能力有限频繁加列容易导致内存碎片和性能下降。把自定义字段整体存成JSON查询时用SQLite的JSON1扩展做条件提取实际验证下来只需要维护一个Structure表记录每个字段的定义和显示顺序业务数据表完全不动非常适合字段经常微调的CRM场景。数据量估算方面按50个销售、每人平均负责500个客户计算总共25000个客户每个客户平均30条跟进记录也就是75万条activities。SQLite对这种体量的读写完全够用定期做VACUUM之后单文件大小可以控制在500MB以内。备份策略也是直接复制文件。4.2 同步机制与冲突处理本地优先不意味着不管一致性本地优先的架构最怕的就是两台电脑同时对同一个客户发起了修改然后产生数据冲突。DeskcommCRM的同步机制做了一套务实的三层策略增删改操作全部携带version字段同步时以version大的为准但不会覆盖对方的本地副本数据而是把冲突数据保留一份到“待审更新”表由管理员决定最终版本。具体流程是这样的客户端A修改客户记录后本地version加1把整条记录连同version POST到同步服务端。服务端收到后如果发现客户端B之前也修改过同一条记录且B的version更新就判定为冲突。服务端不会自动丢弃A的修改而是把A的修改放入conflicts表并把两个版本的差异对比存入其中。管理员在控制台看到冲突列表后可以逐条选择采用哪个版本或手工合并。这个方案最大的好处是自动冲突解决永远不丢数据最坏情况只是管理员需要花一点时间人工介入。实际运行半年下来冲突发生的频率非常低大概一百次同步才会遇到一次而且大多是销售人员同时修改了客户备注字段。真正常见的冲突其实是“删除”与“修改”的碰撞我们的规则是删除优先因为删除本身是谨慎操作发生概率更低。同步服务端还做了一道幂等校验每一条同步记录都带着全局唯一的event_id服务端按event_id去重防止客户端网络重试导致同一操作执行两次。这个设计在客户网络质量不稳定的场景下非常救命很多自研系统栽就栽在重复请求导致的数据重复上。4.3 全文搜索与性能优化中文分词是最大的坑搜索功能是DeskcommCRM每天被使用频率最高的功能之一也是技术实现上最容易翻车的地方。一开始老实用SQLite的FTS5建全文索引但跑起来后发现中文搜索的效果惨不忍睹。默认的分词器会把“深圳市腾讯计算机系统有限公司”切成一堆单个汉字搜“腾讯”时匹配结果里混进大量包含“腾”或“讯”的无关记录。后来反复试了几种方案最终采用“内置中文词库自定义拼音映射外置粗切”的组合策略。具体来说数据库里维护一张关键词表里面预置了行业常见词、公司名后缀词和团队自定义词。每次创建或更新客户时后台线程会把客户名称和跟进正文拆成词元连同原文本一起写入FTS5索引表。搜索时用户输入的自然语言先经过同一个分词器拆词再按FTS5的标准BM25算法排序返回。性能优化方面有几千条客户数据时FTS5索引的查询都是毫秒级但真正的问题出现在模糊查询上。很多销售习惯输入“最近一个月跟进过的客户”这涉及到日期范围内的关联子查询。我们优化了activities表的索引设计在customer_id、next_action_date、created_at上各建了一个复合索引让时间条件作为第一排序键查询速度快了三倍不止。这里分享一个性能优化的实际数据最初没有做预聚合时团队漏斗看板每次打开都要实时扫描整个activities表统计两万多个客户的跟进状态耗时约2.3秒。后来改成每天晚上定时跑任务把统计结果写入summary表看板直接读预聚合结果首屏时间降到300毫秒以内。小团队自己做系统这种“用空间换时间”的优化方式是最划算的。5. 上线以来的常见问题与排查实录5.1 典型问题速查表系统自上线以来各种大小问题没少出这里整理一个速查表供自研类项目管理参考现象根因解决方案数据库文件越来越大附件直接存库删除记录未清理文件附件迁移到磁盘目录数据库只存路径删除记录时级联删文件同步偶尔丢数据同步服务未做event_id去重增加全局唯一事件ID和幂等写入逻辑搜索结果乱匹配FTS5默认分词不适合中文自建词库和分词器覆盖录入与查询两侧通知有时不弹出Electron通知需要用户授权未处理授权失败启动时检查系统通知权限引导用户开启增加有声提醒降级方案导出表格乱码CSV未带UTF-8 BOM导出时显式加BOM头多人同时录入卡顿SQLite写锁竞争开启WAL模式缩短单事务处理时间客户页图片加载慢大量截图文件未压缩粘贴时统一压缩懒加载时间轴图片这个表里每一行都是踩过的坑其中SQLite写锁竞争尤其值得展开。我们一开始没启用WAL模式几个销售同时保存跟进记录时界面经常出现“database is locked”的错误提示。切换到WAL模式之后读操作不再阻塞写操作同类问题基本没有再出现。SQLite官方文档虽然推荐大数据并发场景用WAL但实际项目里很多人压根不知道或者忘了执行这条PRAGMA语句。5.2 一个典型的排查案例任务提醒静默失效有一段时间销售反馈“任务提醒好像不太准有人收得到有人收不到”。最初怀疑是Electron通知权限问题检查了一圈发现权限都正常。后来逐步排查到任务调度模块发现代码里有一个低级bug定时扫描任务表时SQL查询条件用的是“今天到期且未完成”但时间判断用的是客户端本地日期而有的销售为了赶工把系统时间手动调快了几个小时导致任务日期穿越。这种“用户自己改了系统时间”的场景在任何客户端应用里都可能遇到。解决办法是在服务端维护一个标准时间接口客户端启动时校准本地时间偏移量扫描任务时把偏移量纳入计算。另外提醒逻辑里加了冗余判断就算时间被系统调乱只要任务状态还是“未完成”且超过三天没有触发也会在下次应用启动时补一次提醒。这样兜底方案出来后任务提醒的可靠性才算真正达标后续再没出现类似反馈。这个案例给团队的启示是客户端应用不能假设用户的系统时间一定是准确的凡是涉及时间调度和日期比较的功能都要考虑时钟偏移。我们后来把所有的time字段统一改为数据库生成的时间戳应用只做展示转换不再依赖本机时间做业务逻辑判断才算彻底解决。6. 写在最后的一点体会DeskcommCRM从立项、开发到上线最大的收获不是技术方案的完美而是真正理解了“工具要被使用者接纳设计必须贴合操作场景”。很多CRM项目死在功能太多、操作太重而我们的原则从始至终都是用20%的功能覆盖80%的日常需求把核心链路打磨到秒级完成。每次销售在办公室吐槽其他软件不好用时我都会暗自庆幸当初顶住了“加一个自选报表字段”之类的需求压力。如果你也在考虑做类似的项目我建议先花两周时间记录团队的真实工作流搞清楚大家在哪些环节最费力而不是先列功能清单。一个纽扣大小的优化比如按快捷键快速记录跟进可能比一百个花哨功能都管用。这个项目后续我还在做插件化重构准备把报表模块拆成可配置的插件让数据看板可以按团队需求自由组合。做内部工具的好处就是可以一直打磨但前提是主干不能歪核心链路不能断。希望这篇记录对你有帮助尤其是那些正在被通用CRM折磨得想拍桌子的人。