十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于RuoYi自研CRM实践:从Excel迁移到客户全流程管理

基于RuoYi自研CRM实践:从Excel迁移到客户全流程管理 DeskcommCRM这个项目严格来说不是提前规划的而是被我们的Excel表格和微信聊天记录逼出来的。去年年初我们销售团队还靠一张共享Excel管理客户报价单发了几个跟进到哪一步了上次答应客户周三给方案结果周五才想起来这些问题每天都要在群里问一圈才能凑齐答案。后来实在撑不住了开始折腾CRM选型一连试了五六款免费的、收费的最后还是决定基于RuoYi框架自己搭一套——也就是后来命名为DeskcommCRM的这套系统。它的定位很简单一个长期运行在服务器上、浏览器随时能打开的Web端客户管理系统把客户资料、跟进动作、合同回款、团队权限全部放在同一个环境里。不是为了造轮子而是因为团队真正需要的很多东西现成产品很难改。这篇文章把这个项目的来龙去脉、技术选型、模块设计、部署维护和上线后的坑完整写出来不管你是技术负责人还是想给公司做信息化的小团队应该都能找到点参考。1. 从Excel搬家到CRM为什么最后自己做了DeskcommCRM1.1 免费CRM真正卡住团队的地方试用免费CRM的那段时间我最大的感受是功能看着都有销售就是不用。大多数销售每天的工作节奏是碎片化的他们需要的是我打开一个页面马上知道今天该联系谁、上次聊到哪了、接下来干什么。但市面上的免费CRM往往把界面做得非常重——左边菜单十几个模块开个客户详情要等半天填一条跟进记录要过七八个必填项。销售试了两天就回Excel了理由是Excel更快。还有一个更深层的问题免费产品对客户数据的归属和导出限制很多。有的免费版导不出全部客户数据等于数据被平台绑定了有的免费版有企业账号数上限团队超过二十人就得升级付费版。这些限制在小团队阶段不明显人一多就成了巨大的阻力。免费CRM和自建系统之间的区别本质上是用别人的规则管自己的业务和用自己的规则管自己的业务的区别。1.2 自建与租用的成本边界我知道一定有人问自建CRM不是要养服务器、养开发吗成本怎么算都划不来吧我的判断标准很简单如果你的团队人数在10人以内业务模式稳定直接买现成的SaaS产品是划算的但如果你有特殊业务流程比如客户需要多层审批、合同需要自定义回款计划、数据需要和内部ERP打通自建反而是节省成本的那条路。算一笔账一个能跑起来的CRM系统后端一台2核4G的服务器一年大概两三千费用数据库用云数据库一年一千多开发人力是一次性投入。核心链路做完大概一到两个月。而SaaS产品的企业版通常是每人每年几百到上千元20个人的团队一年就是一两万而且每年都要续费。两三年下来自建的成本就摊平了系统还是自己的想怎么改就怎么改。当然前提是你得有一个懂点后端开发的人哪怕是业余时间搞也行。DeskcommCRM就是在这样的半业余状态下做出来的。1.3 永久在线到底是什么意思永久在线这个说法不是指服务器永不宕机。它的真正含义是整个团队在任何时间、任何设备上打开浏览器就能用不需要安装客户端不需要依赖某个IM工具更不需要在本地方便地导来导去。现在很多小团队还在用Excel 微信群的方式管客户但这种方式有一个天然缺陷客户资料和沟通记录分散在各个人的手机和电脑里离职一个人就带走一批数据。DeskcommCRM把数据集中放在服务器上所有操作都在网页完成数据带不走也丢不了在家、在路上、在客户现场只要有网就能查看客户信息和填写跟进记录。跟那些必须装App才能用的竞品比网页端最大的优势是低门槛一个链接发给新同事浏览器打开登录就能干活不需要等待下载安装包也不需要注册什么手机验证码。2. 技术选型为什么吃着RuoYi的老本来做CRM2.1 基于RuoYi-Vue的起点优势DeskcommCRM的技术底座选了RuoYi-Vue前后端分离版。前端基于Vue3 Element Plus后端基于Spring Boot 3.x认证方案是Spring Security JWT数据库用MySQL 8.0缓存用Redis。为什么选它而不是从零搭一套第一RuoYi把RBAC权限模型做好了。用户、角色、菜单、部门、岗位这些通用体系开箱即用不需要重复造轮子。CRM虽然是业务系统但谁能看哪些数据、谁能操作哪个功能依然是最核心的底座RuoYi在这方面的建模非常成熟。第二代码生成器省了大量CRUD开发时间。建好表结构之后可以直接生成前端页面和后端接口代码。客户列表、跟进记录、合同登记这类标准表单页面生成完再改改业务逻辑就能用效率比手写高一倍以上。第三周边生态完善。RuoYi的文档、示例、问题解决方案非常多遇到问题搜索一下基本都有答案。做内部系统不是搞技术探索稳定、快速、能落地才是第一目标。2.2 DeskcommCRM的核心模块与表设计整个系统的核心链路是线索/客户 → 跟进 → 合同 → 回款这条业务主线。我围绕这条主线把表拆成了几块crm_customer客户主表客户名称、企业性质、所属行业、客户来源、当前负责人、客户状态潜在/跟进中/已成交/流失、是否进入公海、最后跟进时间、下次跟进时间、扩展字段。crm_follow_record跟进记录表客户ID、跟进方式电话/微信/面谈/邮件、跟进内容、下次跟进时间、创建人。crm_contract合同表客户ID、合同编号、合同金额、签约日期、开始/结束日期、关联订单明细。crm_receipt_plan回款计划表合同ID、计划回款日期、计划金额、实际回款日期、实际金额、状态。crm_product产品表和crm_order_detail订单明细表处理报价和合同关联的产品明细。有两个字段我特意做了索引一个是客户表的last_follow_time一个是next_follow_time。后面讲公海自动回收和待办提醒时这两个字段是核心判断条件没有索引的话数据量上来之后性能会非常差。2.3 名字背后的产品定位Desk CommDeskcommCRM这个名字取自Desk桌面办公和CommCommunication沟通。意思是这套系统不只是把客户数据存下来还承担着内部协同的职责——每个人登录后看到的首页就是我今天的待办跟进计划谁的合同快到期了系统会推送提醒客户被回收之前管理员能看到预警。在真正的使用场景里销售往往需要同时操作客户管理和内部沟通两件事。如果客户管理在一个系统、内部沟通在另一个工具来回切换的成本非常高。DeskcommCRM把这两件事放在同一个环境里销售不用频繁切换工具信息的流转效率就上来了。3. 核心业务模块落地从客户池到回款的完整链路3.1 公海/私海与自动回收让客户资产流动起来这是整个CRM里最关键的机制直接决定了客户资源能不能高效流转。我的设计是客户分为私海和公海。私海就是有明确负责人的客户公海则是所有人都可以领取的客户池。新录入的线索默认进入公海销售可以随时认领已分配的客户如果超过30天没有任何跟进记录系统自动收回公海但成交客户不受回收限制因为大客户的成交周期本来就长。这些阈值不写死在代码里而是放在系统参数表中管理员可以在后台自由调整。比如有的团队要求15天不跟进就回收有的团队核心大客户的回收期放宽到60天改个配置就行。自动回收的实现逻辑是每天凌晨1点跑一个定时任务SELECT id FROM crm_customer WHERE last_follow_time DATE_SUB(NOW(), INTERVAL 30 DAY) AND status ! 成交 AND owner_id IS NOT NULL找到需要回收的客户后清空负责人把客户状态改为公海同时在客户状态变更表里写一条日志记录谁在什么时候因为超时未跟进被回收。这个日志很重要避免销售事后扯皮。一个特别注意的细节如果该客户正在走合同审批流程或者存在未完成的报价单自动回收任务要跳过这些客户避免因为系统自动操作打断正在进行的商务流程。3.2 并发领取两个销售抢同一个客户的兜底方案公海客户被多个销售同时看到后一定会出现并发领取的问题。如果代码逻辑是先查询这个客户是否无人认领再执行更新那在并发场景下一定会有两个销售同时通过检查然后都更新成功。上线第一周我就踩了这个坑两个销售同时抢了同一个客户然后客户消失了——实际上是被第二个人覆盖认领了。排查日志后发现就是典型的查询再修改导致的竞态条件。解决方案是改成一条条件更新SQLUPDATE crm_customer SET owner_id #{userId}, pool_status 1, claim_time NOW() WHERE id #{customerId} AND (owner_id IS NULL OR owner_id 0)MySQL的UPDATE语句本身是行级锁两个并发请求同时执行时只有一个能匹配到owner_id IS NULL的条件另一个影响行数为0直接在代码里提示客户已被他人领取。加个唯一索引兜底双保险。3.3 跟进记录与下次联系提醒把销售动作变成可跟踪事件跟进记录设计成独立表而不是客户表里的备注字段原因是跟进记录是一对多的而且它本身要支持按时间排序、按销售筛选、甚至在数据看板里统计本周跟进了多少次。独立成表才有这些分析价值。记录每次跟进时销售需要填写跟进方式、跟进内容然后选择一个下次跟进时间。这个设计是有意为之它把口头承诺变成系统里的待办事件。销售跟客户聊完说下周再联系那就得在系统里选一个具体的下次跟进时间到期系统提醒想忘都难。技术实现上每天定时扫描next_follow_time在当前范围的记录生成待办列表同时在首页顶部的今日待办区域展示。当天的待办如果到期未处理会一直置顶显示并且随着时间推移颜色从黄色变成红色心理压迫感直接拉满。3.4 合同回款与经营看板从管客户到管经营合同和回款这两块我参考了轻量ERP的思路但简化了很多。合同表记录客户、合同金额、签约日期、起止日期回款计划表独立一张因为一个合同可能分三期回款每期有自己的预计回款日期和金额。实际回款到账后更新对应计划的到账时间和状态。看板是管理层最关心的部分。我做了三个核心指标客户总量与新增趋势、本周待跟进数量、未来30天预计回款金额。这些指标的SQL聚合并不复杂真正要命的是性能问题。主页看板千万别实时去大表group by。客户表、跟进记录表的数据量大了以后每次打开首页都做几百万行的聚合页面基本就卡死了。我的做法是每天凌晨定时任务把所有核心指标算好写入一张汇总统计表页面直接读这个汇总结果。虽然增加了定时任务这一层但查询速度从原来的几秒钟降到了几十毫秒体验完全不一样。4. 团队协作与权限体系让整个公司在一个CRM里干活4.1 员工邀请与部门初始化从账号到组织架构很多CRM管理员第一次使用时的第一反应是怎么把团队拉进来DeskcommCRM的员工接入流程设计得很直接管理员在系统管理→用户管理里新增成员填上姓名、手机号、所属部门系统会自动生成初始密码和专属登录链接新员工收到后打开链接、登录、强制修改初始密码就能直接进入系统。团队人数多的时候也支持Excel模板批量导入。下载导入模板填好姓名、部门、手机号一次性上传几十个人的账号几分钟就能建好。这一步实现起来并不复杂但对管理员来说能省掉大量重复操作。4.2 数据权限哪些人能看到哪些客户RuoYi自带的DataScope注解帮了大忙。通过注解可以在SQL执行前自动追加数据范围条件支持五种模式全部数据权限、自定义数据权限、本部门数据权限、本部门及以下数据权限、仅本人数据权限。我给不同角色配置了不同范围角色数据范围备注普通销售仅本人数据只能看自己名下的客户销售主管本部门及以下能看到本部门所有销售的客户财务人员仅回款与合同模块不放开客户列表老板账运营全部数据用于全局看板和分析这里有一个非常容易踩的坑RuoYi原版本的DataScope默认基于创建人所属部门过滤而客户是可能被转交的。当一个销售离职客户转给另一个部门的人时客户的创建人部门和当前负责人部门会不一致。如果继续按创建人部门过滤主管就会看不到原本应该在自己部门名下的客户。我的做法是在客户表上额外存了一个dept_id字段每次客户负责人变更时同步更新数据过滤统一按当前负责人所在部门走。这样主管看到的客户范围始终和当前团队对齐。4.3 用Webhook把CRM接进钉钉群销售不是每天都会主动打开CRM看有没有提醒所以我把关键提醒同步到了钉钉群。实现的方案很简单系统里配置一个钉钉群机器人的Webhook地址定时任务扫描到客户距离上次跟进已超过25天或者合同回款计划已到期时后端调一次HTTP POST把提醒内容推送到群里。消息格式用的是钉钉自定义机器人支持的Markdown类型会解析成一条带标题的提醒消息。Webhook地址没有写死在代码里而是放在系统配置表中后台可以随时更换避免泄露后被外人恶意调用。5. 部署与日常运维让系统真正永久在线5.1 Docker Compose编排一套命令拉起整个环境生产环境我用Docker Compose来做编排包含四个核心服务mysql8.0数据目录挂载到宿主机的/data/mysql设置character-set-serverutf8mb4备份时直接打包数据目录。redis6.2开启AOF持久化用于缓存和会话管理。backend后端服务用Maven打出来的jar包构建镜像环境变量注入数据库地址、Redis地址。frontendNginx托管Vue打包后的静态文件配置/prod-api路径反向代理到后端服务。version: 3.8 services: mysql: image: mysql:8.0 container_name: crm-mysql restart: always environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: deskcomm_crm volumes: - /data/mysql:/var/lib/mysql command: --character-set-serverutf8mb4 redis: image: redis:6.2 container_name: crm-redis restart: always command: redis-server --appendonly yes volumes: - /data/redis:/data backend: image: deskcomm-backend:latest container_name: crm-backend restart: always environment: DB_HOST: mysql DB_PASSWORD: ${MYSQL_ROOT_PASSWORD} REDIS_HOST: redis ports: - 8080:8080 depends_on: - mysql - redis frontend: image: deskcomm-frontend:latest container_name: crm-frontend restart: always ports: - 80:80 depends_on: - backenddocker-compose up -d一条命令整个环境就起来了。前端Nginx容器对外暴露80端口用户直接通过域名访问。5.2 Nginx反向代理与HTTPS自动续期公网访问必须上HTTPS否则用户数据在传输过程中可能被窃取。域名解析到服务器后在Nginx配置里增加一个server块监听80和443端口将/prod-api前缀的请求反向代理到后端容器的8080端口。证书用的是免费自动化签发的方案首次安装时执行一次签发命令然后配置一个每天执行的定时任务自动续期。续期后自动重载Nginx配置整个流程不需要人工干预。这个方案已经跑了半年多从未出现过证书过期导致网站打不开的情况。配置完成后用户输入https://crm.example.com就能打开登录页浏览器地址栏显示小锁图标对新用户的信任感建立非常有帮助。5.3 备份策略与监控告警数据是CRM系统的核心资产备份怎么强调都不过分。我的备份策略是每天凌晨2点用mysqldump做一次全量备份备份文件保留最近7天同时把/data/upload上传的附件目录同步到对象存储做异地备份。恢复演练每季度做一次确保备份文件真的能恢复成可用数据库——不演练的备份等于没有备份这是运维中最容易被忽视的教训。监控告警方面写了一个简单的shell脚本每分钟请求一次后端的健康检查接口/prod-api/actuator/health连续两次失败就通过Webhook通知手机。同时监控服务器磁盘使用率超过80%会触发告警。这套方案虽然土但非常稳定省心。6. 上线后踩过的坑与让销售愿意用的三个细节6.1 客户查重不准同名企业带来的数据合并难题系统上线第一周我就发现了一个严重问题客户列表里同时出现了深圳市前海XX科技有限公司和深圳前海XX科技有限公司这两个看起来几乎一样的名字销售录入了两次后续跟进记录也分散了。排查之后发现问题出在查重逻辑上。最初的查重就是简单的完全匹配customer_name ?只要企业名称里多一个市字、缺一个括号就会被当成不同的客户。而现实中企业名称的写法五花八门上海和(上海)、全角半角括号、中英文空格都会导致匹配失败。解决方法是查重时先对客户名称做标准化处理去掉所有空格、统一括号为半角、去掉横杠再做精确匹配。同时对企业客户增加一个更关键的业务唯一键校验统一社会信用代码。前端创建客户时发起异步查重请求如果命中了相似名称弹窗提示是否关联已有客户从源头上避免数据重复。6.2 并发抢客户的问题客户消失的悬案前面提到并发领取问题是在上线第一周就爆出来的。当时有个销售跑过来跟我说客户不见了我登录后台一看客户确实还在但负责人已经变成了另一个销售。两个人都说客户是自己先领的场面一度非常尴尬。日志排查的结论是两个请求几乎同时到达都通过了无人认领的查询校验然后先后执行了更新后者覆盖了前者的认领结果。这种问题靠查询再更新的逻辑永远解决不了必须用带条件的UPDATE语句保证操作的原子性。改完之后这几个月再没出现过抢客户纠纷。6.3 销售不录入怎么办三个验证过的小手段CRM失败的案例里十有八九不是技术问题而是销售根本不往里录数据。系统做得再好看不录数据就是个空壳。我试了各种方式最后有三件事是真正有效的第一压缩必填字段。新建客户时只填客户名称、联系电话、来源三个字段其他信息允许后续慢慢补。销售录一条客户的时间控制在30秒以内录入意愿会明显提升。第二让销售自己定下一步。系统不强制规定跟进动作但每次跟进时让销售填一个下次跟进时间。这个字段一旦填了就会生成待办到期顶到首页销售不得不去处理。久而久之销售反而依赖这个提醒功能因为确实能帮他们记住重要客户。第三周报数据透明化。每周自动生成全员的客户数据贡献排名新增客户数、跟进次数、回款金额。排名靠后的同事会被团队看到这种软压力比任何强制考核都管用。数据贡献高的销售还会得到系统里的数据贡献达人标签在团队内部形成正向竞争。如果你也想自建一套CRM系统我个人的建议是先别急着写代码拿张纸把客户—跟进—合同—回款这条主链路画出来想清楚每个环节谁在用、要什么数据、出了什么问题需要提醒。桌面办公和客户沟通本质上是同一批人、同一套数据流把它们放在一起CRM才有持续用下去的生命力。DeskcommCRM这套系统的价值不在于它用了多先进的技术而在于它准确地融进了团队每天的工作习惯里。
返回列表