如果你在一个几十人的团队里同时管着销售、客服和售后你大概率经历过这种早晨销售说客户早就答应签约了客服说这个客户上周刚投诉过两次实施说工单里写得很清楚还需要产品部确认而你想看任何一个真实数据都得让人从三套Excel里各导一份再手动合并。后来我们内部做了一个叫 DeskcommCRM 的系统把客户、商机、工单、跟进记录全部串到一条线上这篇文章就是我在这个项目里从选型、建模、开发到被业务团队“骂”着改出来的完整复盘。DeskcommCRM 不是什么惊天动地的产品本质就是一套以“客户沟通与工作台”为核心场景的轻量 CRM。它解决的痛点是客户信息沉淀在销售个人微信/Excel里服务过程沉淀在客服工单里实施进度沉淀在项目群里决策层想看全局却只能靠问。它的做法是重做数据模型把原本割裂的销售链路和服务链路拧在一起让每一个客户ID底下的所有沟通记录、承诺、投诉、合同状态都自动归档。如果你正准备自研CRM或者正在为团队选型而纠结这篇内容值得你花十分钟看完——我把踩过的坑、走过的弯路、最终沉淀下来的设计思路都写出来了。1. 为什么我们选择了自研DeskcommCRM而不是买现成的先说结论买现成的CRM不一定错但我们当时的情况是预算有限、业务流程又比较“非主流”市面上的产品要么太重要么太轻最后算下来两年总拥有成本比自研还高而且关键的数据联动需求完全没法满足。1.1 市面CRM的两头难太重和太轻通用型CRMSalesforce、微软Dynamics、国内的销售易、纷享销客这类强在销售流程管理但弱点也很明显实施周期长、业务字段要重新梳理、后期按用户数收license费用加一个审批流或者自定义报表往往要额外付费。对几十人规模的团队来说这套成本吃不消。而轻量级的在线表格加自动化工具虽然便宜但业务一旦复杂起来就崩了客户并发查重做不了、跨部门权限说不清、工单和商机的关联更是无从谈起。我当时做了一个测算按三年计用某主流付费CRM三个部门共60个账号加上实施顾问费、自定义开发费、年维护费总投入接近40万。而自研DeskcommCRM一个后端全职、一个半兼职前端加产品加上云资源六个月的核心版本总成本不到15万而且后续迭代不依赖厂商排期。当然这个账的前提是团队里有人真懂业务建模不是找个实习生用低代码平台拖几个页面就叫自研。1.2 我们的业务到底“畸形”在哪DeskcommCRM这个名字拆开看Desk代表工作台Comm代表Communication沟通核心场景就是“坐席/销售人员在同一个工作台里完成所有对内对外沟通的记录与协作”。普通CRM强调的是Pipeline销售管道但我们公司是“售前咨询—销售签约—实施交付—售后客服”全部在一个链条里走完的。这里的核心难点在于售前顾问需要查历史工单才能判断一个客户是否值得继续投入但是传统CRM里根本没有工单售后工程师需要知道销售当初承诺过什么但销售在CRM里填的跟进记录和客服在工单里记的内容常常对不上管理层想看的不是简简单单的销售漏斗而是“从线索到回款再到服务完结”的整体流转效率这需要销售数据和工单数据在同一套模型里被拉通。自研的价值就在这里可以针对自己的业务重新定义“客户”的边界。DeskcommCRM里客户不再只是一个“公司名联系人电话”的静态档案而是一个带状态、带时间线、带所有关联业务的动态实体每次销售跟进、每次客服响应、每次合同变更都会自动写入这个客户的档案时间线。1.3 自研的边界控制MVP只做三件事自研项目最容易死的不是技术而是范围蔓延今天我们想加一个AI商机推荐明天想加一个在线客服一个月后整个项目已经变成了一个四不像。我在立项时跟团队和老板反复对齐MVP阶段DeskcommCRM只做三件事数据统一把散落在Excel、个人通讯录、纸质工单里的客户信息收拢到同一个库里流程串联线索转客户、客户转商机、商机关联工单每一步都留痕可追溯关键提醒公海回收提醒、SLA超时提醒、商机阶段长时间未推进提醒。这三件事不涉及复杂营销自动化也不做小程序端就是用一个浏览器能访问的桌面Web工作台让每个人每天打开DeskcommCRM就能干完和客户相关的所有事。事实证明这个边界控制是整个项目后来没有烂尾的最重要原因。2. DeskcommCRM的数据模型与核心模块设计很多自研系统死在第一步就是表结构随手一建。CRM这种系统数据模型就是地基地基歪了后面所有代码都是负资产。DeskcommCRM的单据对象不算多但每个对象的字段设计都经过了反复推敲。2.1 核心实体关系与字段设计原则整个系统围绕五个核心对象线索(Lead)、客户(Account)、联系人(Contact)、商机(Opportunity)、工单(Ticket)。外加两个辅助对象跟进记录(Activity)、操作日志(AuditLog)。设计原则只有一条能不加字段就不加字段。CRM使用者大部分都不是技术出身字段越少录入意愿越高数据质量越好。原始模型的字段数量控制如下对象初始字段数量核心字段举例说明线索22来源渠道、公司名、联系电话、意向等级录入门槛最低可重复不强制查重客户28客户全称、行业、规模、客户状态、负责人强查重一客一档所有业务挂靠点联系人16姓名、手机号、邮箱、职位、微信遵循手机号归一化存储商机26商机名称、金额、阶段、预计签单日期金额不做复杂币种转换VIP客户单独标记工单24工单类型、优先级、SLA到期时间、关联商机工单状态机是系统最复杂的部分每个表还预留了一个extra_json字段用PostgreSQL的JSONB类型存储未来可能的扩展属性这样业务部门随时提“我想再加一个字段”开发人员不用立刻改表结构通过配置中心加一个元数据描述即可。2.2 线索查重与客户唯一性策略线索允许重复客户不允许重复。这个原则一定要在一开始就定死。实操中两个销售拿到的可能是同一个客户但不同的联系人电话如果一上来就严格按“公司名完全相同”判断重复那同一个客户会被拆成三个档。DeskcommCRM采用三层查重策略第一层入库实时校验。手机号归一化处理后做精确匹配邮箱转小写后精确匹配这两类能挡住60%的重复。归一化函数看起来简单但必须单独抽出来写单元测试我见过太多系统死于“86”“-”“空格”这些脏数据。第二层公司名相似度匹配。用PostgreSQL的trigram索引pg_trgm扩展对客户全称做相似度打分similarity 0.65判定为疑似重复进入待审列表。这个阈值不是拍脑袋定的我用线上历史数据做了回测0.65能在召回率和误报率之间取得相对平衡。第三层人工合并机制。对于判定为疑似重复的客户系统不会强制合并而是推送给客户管理员逐一确认。合并时默认保留所有关联记录联系人按手机号去重工单和商机全部挂到保留客户下。这套“自动查重人工兜底”的策略避免了业务部门因为合并错误而彻底抵制系统。2.3 归属规则与公海池机制客户归属默认是“谁创建归谁”但要防止“占着茅坑不拉屎”的客户。DeskcommCRM的公海池规则是商机阶段超过30天未更新、或者客户状态连续60天没有跟进记录的客户自动回收到公海池。回收前7天会给负责人发站内提醒和邮件提醒。这个机制上线第一周就回收了37个僵尸客户有一半被其他销售重新激活后转化出商机。公海池的设计有几个细节坑需要提醒回收时间必须以“最后一次跟进活动”为准不能以“客户更新时间”为准因为很多销售会通过改客户基本资料来“假装跟进”回收前提醒必须明确说明剩余天数并且要能一键“认领并更新跟进计划”否则销售觉得系统是在故意找麻烦。2.4 工单状态机与销售SLA联动工单是DeskcommCRM里最体现“沟通型CRM”价值的模块。我们设计的工单状态机是新建 → 受理中 → 处理中 → 待客户确认 → 已解决 → 已关闭另外还有两个特殊状态“已退回”和“待补料”。客户重新打开已关闭工单时系统会自动生成一条工单变更历史并提醒原处理人。状态流转不是随便点的每个状态变更都要填写处理说明处理说明写入AuditLog和时间线供全员查阅。真正让业务团队觉得系统有用了是SLA联动。工单优先级分为P0/P1/P2/P3对应的首次响应时限分别是15分钟/30分钟/2小时/24小时。DeskcommCRM里配置了一个定时任务每5分钟扫一次超时工单超时后直接在系统里给处理人弹通知同时创建一个“SLA超时待办”这个待办会出现在管理后台的看板里领导不用追着问也知道哪张工单已经红牌了。这里没有做复杂的SLA计算引擎就是基础的到期时间判断但实际价值已经远远超过了实现成本。3. 技术选型与关键实现细节为什么这样组合技术选型没有最好的只有最合适的。DeskcommCRM的实际技术栈是后端Spring Boot 2.7 PostgreSQL 14 Redis 6 前端Vue 3 Element Plus部署在阿里云的两台4C8G ECS上一台跑应用一台只读从库用于报表和导出。这个组合放到今天看不算惊艳但胜在稳定、招人容易、出了问题社区方案多。3.1 为什么用PostgreSQL而不是MySQLCRM系统最大的特征就是“业务字段容易变、查询条件组合多”。MySQL在5.7时代对JSON的支持弱得让人头疼而PostgreSQL的JSONB配合GIN索引可以直接对extra_json里的字段做条件查询这个能力太适合CRM的“未知扩展需求”了。另外PostgreSQL自带的全文检索tsvector和trigram模糊匹配帮我们省掉了一开始接入Elasticsearch的复杂度。早期客户搜索框要实现“输入关键词同时匹配公司名、联系人和工单内容”如果用MySQL光建搜索逻辑就得写很多代码而PostgreSQL直接对几个核心字段建一个表达式索引就能实现跨表联合搜索数据量在百万级以下完全够用。3.2 数据权限不只是行级权限是“能看到谁的数据”CRM里权限做不好业务团队一定造反。DeskcommCRM的权限模型分为三层角色权限控制的是“能不能进这个菜单、能不能删除记录”比如销售总监可以查看所有客户导出普通销售只能查看自己名下客户。数据范围控制的是“列表查询时能看哪些人的记录”所以普通销售登录系统后客户列表默认只能看到归属自己的数据销售组长能看到本组数据管理员能看到全部。共享规则是很容易被忽略却很重要的设计。DeskcommCRM支持将客户共享给指定用户或团队比如合同客户可以共享给实施工程师。实现时加了一张customer_share表查询SQL的过滤条件核心是WHERE c.owner_user_id :currentUserId OR c.owner_dept_id :currentDeptId OR EXISTS ( SELECT 1 FROM customer_share cs WHERE cs.customer_id c.id AND (cs.share_user_id :currentUserId OR cs.share_dept_id :currentDeptId) ) AND (:isAdmin true OR ...)这里有个性能隐患如果客户表很大且share表数据多EXISTS子查询会导致索引失效。实际优化是把共享关系冗余到一张customer_visible_map表里在客户分享、部门调整时异步刷新这张映射表查询时直接JOIN它而不是EXISTS。这个优化让客户列表从200ms降到了50ms以内。3.3 权限校验放在Service层而不是SQL拼接层开发CRM系统时最短视的做法是直接在Mapper里写死WHERE条件。后来我们统一做了一个PermissionFilter切面通过注解声明数据权限范围由Service层根据当前用户动态拼装条件再传给仓储层。这样一个核心逻辑不会因为SQL拼接混乱而漏掉权限条件也方便写单元测试时mock当前用户。3.4 客户查重的核心算法与代码实现手机号归一化函数单独写成数据库函数方便在应用和SQL里复用CREATE OR REPLACE FUNCTION normalize_phone(phone TEXT) RETURNS TEXT AS $$ SELECT regexp_replace( regexp_replace(phone, \?86, , g), [^0-9], , g ) $$ LANGUAGE sql IMMUTABLE STRICT;注意India等国家的国际号码如果不小心被“86”规则误伤所以只有开头是86或86且非0开头11位时才剥掉国号。做好归一化之后再建一个表达式索引CREATE UNIQUE INDEX idx_customer_phone_normalized ON customer (normalize_phone(primary_phone)) WHERE primary_phone IS NOT NULL;这个唯一索引是防并发的底裤。因为查重永远存在时间窗口两个销售同时提交一个同样的手机号两个事务各自查询都“没有重复”然后各自插入没有唯一索引兜底必然出现脏数据。当时我们就是因为这个索引漏建生产环境出现了一对重复客户后来补上索引后这个坑就没再犯过。3.5 仪表盘数据不是实时聚合而是定时写指标表销售漏斗和工单时效报表如果每次打开都去聚合几千张工单和商机记录数据库迟早会被拖垮。DeskcommCRM的做法是写了一套简单的指标聚合任务每5分钟跑一次凌晨2点全量重建一次把计算结果写入独立的metric_daily和metric_live表展示端只查这两张表。5分钟延迟对内部管理看板完全够用但数据库的压力却降了一个量级。这里还要注意一个细节指标表用任务跑出来后改历史数据不会自动重算所以每次工单状态变更或者商机阶段变更时除了业务表更新还要往Redis里写一个“待刷新指标”的标记定时任务扫描到这个标记才重算对应维度的数据。否则业务人员修改一条历史工单后看板数据错误就会对系统失去信任。4. 落地过程中踩过的三个大坑与完整排查链路自研系统真正烧钱的时间不是写代码而是上线后业务部门不用、用了出问题、出问题没人认。这里分享三个我们真实踩过的坑每个坑都附上排查思路和最终解法。4.1 坑一团队录数据意愿低“冷启动”差点让项目黄掉现象上线第一周系统里新增客户只有30个而销售手里个人Excel里的客户有800多个。销售觉得录数据占用签单时间客服觉得工单系统是增加工作量整个系统几乎没有真实业务数据流动。排查和转机我们最开始试图通过管理命令强行要求大家录入结果销售应付了事录进去的数据质量很差。后来我们做了三件事扭转了这个局面第一把历史客户Excel批量导入系统而不是让销售手动录导入后的客户自动分配给原登记人这一步直接让数据量从30涨到800第二DeskcommCRM接入了企业邮箱销售和客户的往来邮件通过规则自动归档到客户档案里销售不用手动登记系统“自动”越来越完整第三管理层明确将客户跟进计划和商机阶段更新纳入例会汇报依据所有人看到用系统的人能在周会上展示数据而不用系统的人答不上来客户情况自然就跟着用了。4.2 坑二并发创建客户产生的重复脏数据现象上线一个月后运营发现客户列表里出现好几对“几乎相同”的记录名字一样、电话一样分别挂在两个不同销售名下。这引起了一个很大的信任危机因为领导认为系统查重根本没做。排查链路先查日志发现两条记录创建时间相差不到1秒明显是并发插入。再查数据库事务隔离级别默认是READ COMMITTED在这个隔离级别下两个事务同时查询都不存在对方未提交的数据所以都认为“无重复”。最终定位根因是缺少唯一约束、查重和插入之间不是原子操作。解决措施其实有两层入库前统一走一个lockCustomerCreation方法以归一化手机号为key获取Redis分布式锁数据库层建唯一表达式索引作为最终的硬性保障。同时每天凌晨跑一个离线去重脚本把漏网之鱼扫出来放进待处理列表。这三层之后重复脏数据几乎归零。4.3 坑三导出大量数据把主库连接池打爆现象月底CRM管理员想导出一份一年内所有客户和工单的统计明细点击“导出”后系统卡了大概几秒钟随后销售反馈系统所有页面都变慢了后台监控显示数据库连接池满。排查链路查看慢SQL日志把PostgreSQL的log_min_duration_statement设置为500ms后开启慢查询日志发现导出动作生成了一条超级复杂的大SQL关联了客户、联系人、商机、工单、跟进记录、附件表一次性全量查出来查询时长8秒但在内存里转JSON和写Excel时又触发了几十条分页查询每一条查询都占用一个连接直接把连接池拖死了。解决措施把导出功能彻底和在线查询隔离。导出改走只读从库并且限制单次导出最大行数为5万行超出这个量就拆成EXCEL多Sheet分批生成。同时导出变成异步任务用户点击导出后返回“生成中”的提示后台任务生成完毕后在“我的下载”里提供下载链接。前端再也不会因为导出而长时间挂起等待。另外给PostgreSQL配置了语句超时参数和连接池的最大等待时间避免类似问题再次拖垮全部业务查询。5. 上线之后的运营推广与系统的第二次生命很多自研系统死在“上线”这个节点以为部署完就万事大吉。实际上一套CRM系统要真正发挥价值至少需要运营3-6个月。5.1 从“领导让用”到“我自己想用”DeskcommCRM上线初期业务团队的使用状态是“每天被逼着打开一次”。为了刷新率达标销售会随手点几个页面再关掉。转折点是我们在系统里加了一个“每日工作台”的首页登录后第一眼看到的是今天待跟进的客户预约、超时未处理的工单、即将进入公海池的客户预警这些信息完全是围绕“用户今天要做的事”生成的。这个改动让系统从“台账工具”变成了“工作指挥中心”。销售不用再自己记着哪个客户该回访了——打开DeskcommCRM就知道客服不用每天翻Excel查SLA时限——系统会主动提醒。真正好用的B端系统不是功能多而是能让使用者在打开系统的那一刻获得“今天干什么”的答案。5.2 实际效果数据的变化使用满三个月后我们拉了一组对比数据线索到商机的转化率提升了约18%平均首次响应时长从4小时降到45分钟客户信息完备度联系方式和非空必填字段占比从导入初期的55%提升到91%。工单SLA超时率从32%降到了7%。这些数字并不夸张但足以让管理层认可系统价值。5.3 DeskcommCRM的下一个版本规划经过这次实践我开始觉得“自研CRM”最难的并不是技术实现而是要持续抵抗“大而全”的诱惑。DeskcommCRM现在的定位逐渐从“销售管理工具”演变成了“客户成功平台”下一步我们计划增加轻量的低代码字段配置通过JSON Schema让管理员自己加字段和表单以及开放一套只读API方便其他内部系统拉取客户和工单数据做数据仓库分析。如果你也在考虑做一个内部CRM系统我的建议是第一MVP一定要小先解决“数据统一”这一个核心痛点其他功能往后放第二查重和权限这两个设计必须一开始就做对后期改造成本极高第三上线后的运营投入至少要和开发投入一样大没有人用的系统再华丽也是零。DeskcommCRM现在还在每天被业务团队使用着也还在不断迭代这个过程里最有价值的不是代码而是我们逐渐理解了一个道理CRM的本质不是“管客户”而是“让每一个和客户打交道的人更省心”。
阅读完成 · 觉得有帮助?