拓冰建站拓冰建站
首页 / 资讯中心 / 正文

三周自建轻量级CRM:从线索到工单的落地实践

先说个背景。做销售的团队都知道客户资料到处散落在Excel、微信聊天记录、纸质名片里跟进到哪一步全凭个人记忆这种状态撑到几十个客户还行一旦过了两三百条线索基本就开始乱了。我接手DeskcommCRM这个项目的时候团队面临的就是这个局面线索来源杂、跟进记录断档、工单处理靠吼所以当时的目标非常明确——做一个轻量、够用、能落地的客户管理系统不追求大而全而是先把客户从线索到成交再到售后服务的主链路打通。整个项目从设计到跑起来大概用了三周目前已经稳定运行了四个多月今天把这套系统的搭法、踩过的坑、以及几个关键模块的实现细节完整记录下来给同样在做CRM选型或自建的朋友一个参考。1. 项目整体设计与思路拆解1.1 为什么选择自建轻量级CRM而不是买现成的当时第一个摆在面前的问题不是说要不要上CRM而是买SaaS还是自己搭。市面上主流的CRM产品我也考察过功能确实全面从线索管理到客户画像再到销售预测全都有。但问题也很明显一是价格不便宜按坐席收费团队三四十号人一年下来是笔不小的开销二是很多功能的颗粒度和我们实际业务流程对不上比如我们的售后工单要绑定具体的客户联系人还要关联到产品序列号这个逻辑在通用CRM里配置起来非常费劲三是数据都放在别人服务器上导出和二次开发都受限制。所以综合考虑之后决定自己搭一个轻量级CRM。核心思路就一句话——只做业务主链路上必不可少的功能其余一律砍掉。我们的主链路很清晰线索进来官网表单、渠道投放、老客户转介绍销售跟进记录沟通内容、更新客户阶段、设置下次跟进时间客户沉淀成交后自动转为客户建立客户档案售后工单客户提单、客服处理、结果回写客户档案这套流程理顺之后系统设计就变得非常简单本质就是一张大宽表加上几个状态机的流转。1.2 技术选型背后的取舍逻辑技术栈用的是我比较熟悉的一套组合前端Vue3 Element Plus后端Java Spring Boot数据库MySQL缓存用Redis部署在阿里云一台4核8G的ECS上。选Java而不是Node.js或者Python主要是考虑到后续可能会有更复杂的数据统计需求Java生态里这块的工具链更成熟。前端选Vue3没有太多纠结因为团队里前端工程师对这个最熟而且Element Plus的表格和表单组件用来做管理后台非常顺手。数据库表结构设计是整个项目里最值得花时间的部分我花了一天半专门理这个。核心表一共就六张用户表、线索表、客户表、跟进记录表、工单表、工单回复表。很多人做CRM喜欢把线索和客户分成两套完全独立的表字段各设计一套但我没有这么做而是用一张表加一个状态字段record_type来区分线索和客户。这么做的好处是减少了跨表查询客户从线索转化过来的时候不需要执行Insert只需要Update一个状态字段数据的连贯性也更好。Redis主要用来做两件事一是登录会话管理二是跟进提醒的延迟队列。前者不用多说后者展开说一下——每个销售都可以给客户设置“下次跟进时间”这个时间到了系统要在工作台提醒。实现这个功能最简单的方案是起一个定时任务每分钟扫一次数据库但这样对数据库压力比较大。我采用的是把未来24小时内需要提醒的跟进任务缓存到Redis的有序集合里用时间戳作为分数定时任务只需要从Redis里取分数小于当前时间的元素即可只有Redis取不到的时候才去数据库兜底查一次。1.3 业务权限模型的简化处理权限这一块很多CRM会把角色划分得非常细什么销售主管、销售专员、客服主管、客服专员、运营、管理员每个角色配一套菜单权限和数据权限。我刚开始也这么设计了但做完一版发现太复杂了单单权限中间表就有七八张后来果断做减法。最终保留的模型是这样的角色只有三种——管理员、销售、客服。权限控制分两层菜单权限控制能看左侧哪些菜单在登录的时候一次性查出来放到Redis里数据权限控制能看到哪些数据这个不靠按钮级别的细粒度权限而是靠数据表里的owner_id字段比如说销售只能看自己负责的线索和客户管理员看全部客服只能看被分派给自己的工单。因为业务规则足够简单所以直接在SQL查询层面加条件过滤就行不需要引入ShardingSphere这类数据权限框架。提示自建系统最怕权限模型一开始设计得过重。先跑通主流程再根据使用反馈加权限维度这个顺序不要反。2. 核心功能模块的实操要点2.1 线索导入与去重机制的实现细节线索进来的入口有三个官网表单直接写入、销售手动新增、Excel批量导入。其中Excel批量导入是最容易出问题的地方我花了比较多精力处理。批量导入的技术实现本身不复杂后端用EasyExcel解析上传的文件逐行校验格式后写入数据库。真正的难点在于去重策略。刚开始团队定的规则是按手机号去重但实际跑下来发现误伤很严重——同一个客户的手机号可能在系统里已经存在但是对应的需求描述完全不同直接合并会把两条独立的需求搞混。后来我把去重策略改成了两段式第一段手机号完全相同的直接标记为“重复线索”不进销售私海而是进入一个待确认列表由管理员处理第二段手机号不同但公司名和联系人姓名都相同的标记为“疑似重复”允许销售在列表里看到但需要手动确认合并。这样既避免了误伤又留了人工判断的空间。导入这块还有一个容易踩的坑是编码问题。Excel文件虽然有.xlsx后缀但里面的内容可能来自各种渠道有的系统导出的CSV文件是GBK编码的用UTF-8去读全是乱码。我在前端上传之前加了一个文件编码嗅探逻辑读取文件的前几个字节判断BOM如果是GBK编码就在前端转换成UTF-8再上传这个问题就彻底根治了。2.2 客户详情页设计的核心逻辑客户详情页是整个系统里最有价值的一个页面因为销售每天的大部分时间都花在这个页面上。我设计这个页面的原则是所有和这个客户相关的信息都在一个页面里展示完不让销售跳来跳去。页面从上到下依次是客户基本信息卡片名称、行业、规模、来源渠道、负责人关键字段区客户阶段、预计金额、下次跟进时间跟进记录时间线按照时间倒序展示所有沟通记录关联工单列表如果这个客户提过工单直接展示在下方。其中跟进记录时间线是这个页面的灵魂。每次跟进的内容是什么、下次计划做什么、有没有新的联系人加入全都以时间线的形式记录下来。销售每天的工作流变成打开工作台看今日待跟进列表 - 点进某个客户 - 看上次跟进记录 - 添加新的跟进记录 - 更新下次跟进时间。整个闭环非常顺畅。技术实现上客户详情页的数据是分三个接口返回的基础信息一个、跟进记录一个、关联工单一个前端并行请求。没有做服务端聚合因为这三个数据的变化频率不一样分开查询更灵活比如跟进记录可以用分页加载而基础信息只需要查一次。2.3 工单流转规则的状态机设计工单模块相对独立但也比较复杂因为涉及状态的流转和响应时效的考核。我设计的工单状态机一共有五个状态待分配、处理中、待客户确认、已解决、已关闭。流转规则如下客户提交工单后状态默认是“待分配”系统根据工单的产品分类自动匹配到对应客服的待办池客服认领或管理员手动分派后状态变为“处理中”客服处理完提交解决方案状态变为“待客户确认”客户确认无问题状态变为“已解决”超过7天没有新回复的“已解决”工单定时任务自动置为“已关闭”每个状态的变更都会记录一条操作日志方便后续追溯。这里我特别注意了一个细节——状态变更操作全部放在一个事务里比如从“处理中”变为“待客户确认”的时候不仅要改工单的状态字段还要记录操作日志、通知客户发邮件或者站内信、更新客服的处理中工单数量这几步任何一个失败都要回滚绝不能出现工单状态改了但客户没收到通知的情况。关于SLA服务级别协议报警我用了一个比较实用的办法。每张工单在分配的时候会根据优先级设置一个“首次响应时限”比如普通工单4小时紧急工单1小时。系统每分钟跑一次定时任务扫描所有处理中且首次响应时间为空的工单如果当前时间减去工单创建时间超过了响应时限就向对应的客服和管理员发送一条站内信和邮件提醒。响应时间这个字段是在客服第一次添加工单回复的时候写入的所以不会重复触发报警。3. 数据流转与自动化能力的落地3.1 自定义字段体系的搭建过程自建的CRM如果字段写死了后面一定会后悔因为业务需求经常变。所以我在设计的时候就预留了自定义字段的能力实现方式不算复杂但很实用。具体的做法是在客户表旁边加两张表一张是custom_field_def存储字段定义比如字段名称、字段类型文本/数字/单选/多选/日期、是否必填另一张是custom_field_value存储字段值主键是记录ID加字段ID的联合主键。查询的时候前端先请求客户基础数据再请求自定义字段的定义和值动态渲染到页面上。这套方案上线之后很快就派上了用场。有一次市场部门说要增加一个“客户预算范围”的字段用作销售筛选高意向客户如果当时没有自定义字段机制就得改表结构、改前端表单、改详情页展示至少得折腾一天。有了这套机制市场人员在管理后台自己就能配置好前端页面自动就显示出来了前后用了不到十分钟。当然这个方案也有它的代价自定义字段没法参与复杂的SQL排序和聚合如果要按自定义字段筛选需要先查出匹配的ID列表再回表查询。对于字段总量不大我们控制在20个以内、数据量不大10万条以下的场景性能完全不是问题。如果以后数据量上去了可以考虑用MySQL的JSON字段替代分表方案但至少现在没有必要。3.2 跟进提醒的自动化实现方式跟进提醒这个功能看起来不起眼其实对销售的执行力影响特别大。系统上线之前销售经常忘跟进靠的是主管口头催系统上线之后每天上午九点半销售的工作台就会自动弹出当天需要跟进的客户列表这个体验上的升级是很明显的。实现方式在前面提过核心是Redis的有序集合。具体来说每次创建或更新跟进记录的时候后端会把下次跟进时间作为分数写入Rediszadd remind_queue 下次跟进时间戳 userId:customerId。然后一个后台任务每隔五分钟执行一次zrangebyscore remind_queue -inf 当前时间戳取出所有到期任务按用户维度合并之后推送站内信提醒同时从有序集合里删除这些已处理的任务。这里有一个坑需要注意如果利用Redis有序集合来做延迟队列任务到期后一定要先删除再处理不能先处理再删除。因为如果先处理业务逻辑比如推送消息然后才删除Redis里的记录过程中服务一旦崩溃重启后这个任务会重复执行客户就会收到两条重复的跟进提醒。反过来先删除再处理最多丢失一次提醒相比之下可接受得多。3.3 客户阶段转化与数据看板的统计口径数据看板是最后做的模块虽然技术上不复杂但在统计口径上踩了不少坑。项目刚启动的时候我用一个简单的SQL统计各阶段的客户数量SELECT stage, COUNT(*) FROM customers GROUP BY stage。结果跑出来自己都吓了一跳各阶段客户数量加起来比客户总数多了不少。排查之后发现问题出在客户阶段的历史变动没被处理——同一个客户上周在“初步沟通”阶段这周变成了“方案报价”但看板统计的是当前阶段所以两周的数据对不上。最终我采用了客户阶段变更流水表的方案每次客户阶段变更时在流水表里插入一条记录包含客户ID、变更前阶段、变更后阶段、变更时间。看板统计的时候按时间范围去流水表里查每个阶段有哪些客户再按当前的最新状态归类。这样做之后数据口径就对上了而且还可以顺便统计每个阶段的转化时长、各来源渠道的转化率这些数据对管理者来说特别有参考价值。看板的实现本身没什么特殊的就是几张报表页面今日新增线索数、今日跟进次数、各阶段客户分布、本周成交金额、工单响应时长统计。前端用ECharts画图后端提供对应的聚合查询接口缓存策略上是五分钟刷一次避免频繁查数据库。4. 实际操作中的部署记录与优化调整4.1 服务器配置与初期部署方案服务器用的是阿里云的ECS配置是4核8G系统盘40G数据盘100G。这个配置对于日活一百人左右的内部管理系统来说可以说是比较宽裕了。实际运行了四个月CPU使用率基本都在20%以下内存使用率在50%左右完全没有性能压力。如果再算上后续的数据增长和可能的并发提高这台机器撑个两年应该没什么问题。部署方式用的是最传统的方案后端打包成Jar包用systemd注册成系统服务前端构建之后用Nginx托管静态文件同时Nginx做了反向代理把/api开头的请求转发到后端的8080端口。HTTPS证书用的是阿里云的免费证书配置好自动续期之后基本不用管。数据库用的阿里云RDS MySQL 5.7而不是自己装的MySQL虽然是付费服务但省去了备份、高可用、监控这一堆事情。对于一个小团队来说把运维成本降到最低才是正经事自己折腾主从复制和高可用方案投入产出比太低。备份方面RDS自带自动备份我额外设置了一个每天凌晨两点的全量备份保留最近7天。另外每周末手动导出一份SQL文件存到OSS上作为跨平台的异地备份。这套备份策略在四个月里没真的用到过但数据库这种基础设施宁可多备不能漏备。4.2 连接池、索引与慢查询的初期调优系统刚上线的那一两周数据库方面确实发现一些性能问题。最明显的是客户列表页销售翻页翻到后面几页的时候接口响应时间从一两百毫秒涨到了一秒以上。用EXPLAIN分析了一下慢查询日志发现问题出在客户表的大范围查询上。客户列表页的筛选条件比较多包括负责人、客户阶段、来源渠道、最近跟进时间这些字段只有部分建了单列索引组合查询的时候MySQL只能用到其中一个索引其他条件只能全表扫描过滤数据量一旦上去查询自然就慢了。解决办法是给最常用的几个查询组合创建联合索引。比如“负责人 客户阶段”建一个联合索引“创建时间 来源渠道”建一个联合索引。建完之后列表页的响应时间降到了两百毫秒以内效果立竿见影。连接池方面用的是HikariCP配置的最大连接数是20最小空闲连接数是5。这个参数在同规格的机器上跑一百个并发以内的请求完全够用如果并发上去了优先考虑加机器而不是调大连接数因为数据库的连接数并不是越大越好连接太多反而会把RDS的资源耗尽。提示上线初期要养成定期看慢查询日志的习惯。MySQL默认的慢查询阈值是10秒建议在配置里调低到1秒这样能更早发现潜在的SQL性能问题。4.3 搜索功能的演进从LIKE到全文索引系统里的全局搜索功能一开始做得比较粗糙就是客户名称和联系人姓名用LIKE %关键词%模糊匹配。客户量在几千的时候没觉得有什么问题等到了两三万条搜索接口的响应时间明显变慢了有时候要三四秒才能出结果。中间想过接Elasticsearch来做搜索但考虑到数据量并不大引入一套分布式搜索引擎有点杀鸡用牛刀而且要额外维护一套集群投入产出比不划算。后来翻了一下MySQL的文档发现5.7版本之后自带全文索引功能支持中文分词虽然默认的分词器对中文支持一般但可以用ngram解析器就试着用了这个方案。把客户名称、联系人姓名、联系手机号三个字段建成全文索引然后用MATCH...AGAINST语法执行搜索响应时间从三四秒降到了两三百毫秒效果非常明显。当然前提是数据量在可控范围内如果以后数据量突破了百万级别再考虑引入Elasticsearch也不迟。5. 常见问题与排查技巧实录5.1 登录会话丢失与Redis配置问题系统上线后遇到的第一个大问题就是用户反映登录状态经常丢失有时候上午登录的下午点开页面就被踢回登录页了。排查了半天才发现不是代码问题而是Redis的内存淘汰策略导致的。我部署的Redis实例默认的maxmemory-policy是noeviction也就是内存满了之后不淘汰任何数据新的写入直接报错。而系统用Redis存了登录会话、权限信息、跟进提醒队列等一系列数据内存很快就满了。内存满了之后新的会话写入失败用户就被踢出登录状态。这个问题的解决办法是给Redis设置一个合理的最大内存上限和淘汰策略比如maxmemory 2gb淘汰策略用allkeys-lru。同时给登录会话设置一个合理的过期时间我用的24小时再加上滑动续期这样即使淘汰策略有偏差用户也不会频繁掉线。5.2 批量导入数据时的字段映射错误有一次市场部门导入了两千多条线索导入完成后抽查数据发现很多线索的“客户规模”字段是乱的有的显示“50-100人”有的显示“100-500人”跟原始Excel里的内容完全对不上。排查之后发现是Excel模板的列顺序和前端解析的列映射不一致。市场人员用Excel重新整理模板的时候不小心插入了一列导致后面所有列的偏移了一位。前端EasyExcel按索引解析的时候把“客户规模”这一列的内容解析到了“所在行业”字段上。这个问题单纯靠代码层面很难完全避免因为前端没法判断Excel里的每一列到底是不是用户想要的字段。最后我做了两个优化一是导入之前增加了一个预览步骤把解析后的前十条数据显示在页面上让用户确认字段映射正确后再正式导入二是前端解析Excel的时候使用表头名称进行匹配而不是用列索引这样即使列顺序变了也能正确识别。5.3 工单重复创建与幂等性处理工单模块上线后有一个客户反馈说同一个问题提了两次单我们的系统里也确实出现了两张几乎一模一样产品相同、问题描述相同的工单。排查原因是客户在网络波动的情况下点击了两次“提交”按钮前端没有做防重复提交的限制两次请求都到达了后端各自创建了一条工单记录。这在弱网环境下是个高频问题尤其是客户那边用的系统网络本身就不太稳定。解决方法是做接口的幂等性处理前端提交工单时生成一个唯一的请求IDUUID后端在创建工单之前先查一下这个请求ID是否已经存在存在则直接返回已有的工单信息不存在再创建。Redis里存储请求ID加过期时间比如10分钟这样既解决了重复提交的问题又不会给Redis带来长期存储负担。同时前端也在按钮点击后立即置灰双保险。5.4 数据看板统计数字对不上的排查思路看板模块做出来之后管理员反馈说“本周成交金额”这个数字跟财务那边统计的金额对不上系统里显示的是580万财务那边是620万。这个问题的根源在于成交金额的判定标准不统一。系统里“成交客户”是指客户阶段被修改为“已成交”的客户但在这个阶段变更之前客户可能已经付了全款也可能只付了定金。财务那边的标准是看实际回款到账比销售阶段变更的时间通常要滞后几天所以两边统计口径天然就对不上。解决这个问题的思路是增加一个独立的“成交记录”实体销售在客户进入“已成交”阶段的时候需要单独录入一条成交记录包含成交金额、成交日期、回款方式等字段。看板里的“本周成交金额”统计的是成交日期在本周的记录而不是客户阶段变更的日期这样跟财务的口径就一致了。提示自建系统里的统计口径问题特别容易忽略。凡是涉及金额、次数、时长的统计一定要提前明确唯一的判定标准并且把这个标准固化到代码和文档里否则后面反复扯皮。6. 项目上线后的使用反馈与后续规划系统上线第四周的时候我做了一个简单的小范围问卷调研问了一线销售和客服几个关键问题日常工作效率有没有提升、哪些功能用得最多、还有哪些痛点没解决。反馈比较集中的有几个点一是客户详情页的跟进时间线确实是每天用最多的功能销售觉得比之前翻Excel和聊天记录找上下文要高效得多二是工单模块的SLA提醒很有用客服不会漏掉紧急工单了三是大家普遍希望增加移动端的支持因为销售在外面拜访客户的时候不方便开电脑。移动端的支持我目前已经纳入规划了但不会单独开发App而是做一套适配手机浏览器的H5版本只保留最核心的两个功能查看今日待跟进列表和快速记录跟进内容。考虑到我们用的前端框架是Vue3可以直接复用大部分代码逻辑工程量不会太大。另外一个方向是客户公海和私海的结合。目前线索分配还是管理员手动分配的销售把线索跟丢或者一直不动的情况没有机制来兜底。后续可以做一个简单的“僵尸线索回收”逻辑线索超过7天没有跟进记录自动回到公海池其他销售可以领取继续跟进这样能有效提高线索利用率。最后再分享一个做这类系统的心得自建CRM最大的优势不是省钱而是所有功能都能贴着团队的实际业务流程走没有那些用不上的复杂功能压在头顶上。但前提是你要非常克制脑子里时刻有一根弦——这个功能真的需要吗能不能用更简单的方式替代我见过很多自建系统死在功能膨胀上做着做着就变成了一个比商用软件还复杂的怪物最后没人愿意用。保持简单保持贴近真实业务是这个项目走到现在最核心的一条经验。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门