1. 项目概述与整体设计思路
1.1 为什么叫“DeskcommCRM”
看到“DeskcommCRM”这个名字,第一反应往往会落在“CRM”三个字母上,这很正常。但真正值得留意的,其实是前面的“Deskcomm”——它是由“Desk”和“Comm”组合而成的合成词,Desk代表桌面办公场景,Comm则是Communication(沟通/通信)的缩写。连起来理解,这是一套定位于桌面端办公沟通场景下的客户关系管理系统。
这个定位从一开始就和市面上大多数“重销售漏斗、轻日常沟通”的CRM区分开了。我们团队在做这套系统时,最初的出发点非常简单:销售团队日常最频繁的动作,其实不是看报表、调漏斗,而是打开电脑、回复客户消息、记录沟通要点、安排下一步跟进。这些动作高度依赖桌面端操作,并且和“沟通记录”强相关。所以DeskcommCRM的核心设计思路,是把“沟通”和“客户管理”揉在一起,而不是让销售在CRM系统和聊天工具之间反复横跳。
1.2 这套系统能解决什么问题
先说一个实际场景。早些年团队用过几款通用型CRM,功能很全,但真正落地时会发现一个尴尬的情况:销售白天花大量时间在微信、企业IM、邮件上和客户沟通,到了下班前再花半小时把沟通内容手动补录进CRM系统。补录这件事,大概率是能省则省,能简则简。结果就是系统里的客户信息永远滞后,跟进记录缺胳膊少腿,管理层看数据时总觉得“不真实”。
DeskcommCRM要解决的核心问题,就是这种“沟通与记录脱节”的痛点。它把客户信息管理、跟进记录沉淀、任务提醒、数据看板整合到一套桌面优先的界面中。销售在系统内直接记录沟通要点,系统按时间线自动归档;管理者从数据看板能直接看到每名销售的跟进频率、客户转化阶段、任务完成率。同时它支持私有化部署,数据完全握在自己手里,适合对客户信息安全要求比较高的团队,比如做B2B服务、项目制交付、咨询类业务的公司。
1.3 适合谁来参考这套方案
如果你是以下三类人之一,这篇文章的内容会对你有直接帮助:
- 有自研CRM想法,但不确定模块怎么拆、数据表怎么设计的技术负责人;
- 正在选型或优化团队客户管理流程,想知道一套“重沟通记录”的CRM应该具备哪些核心能力的业务管理者;
- 对全栈开发感兴趣,想通过一个完整项目理解权限设计、数据模型、操作日志这类工程要点的开发者。
这篇文章不会讲大而全的CRM理论,而是围绕DeskcommCRM从零搭建过程中的关键决策、核心数据结构、权限实现、常见坑点做一次系统复盘。所有内容都是基于我们实际开发和上线过程中的真实经验整理,拿来就能用得上的部分会直接给出表结构和代码片段,方便你复现或参考。
2. 核心功能模块与数据模型设计
2.1 客户-联系人-商机的三角关系
CRM系统的数据模型是整个系统的基础,设计得好不好,直接影响后续开发效率和业务扩展空间。DeskcommCRM里最核心的三张业务表是客户表、联系人表和商机表,它们之间的关系可以用一句话概括:一个客户下有多个联系人,一个客户下有多个商机,商机归属于客户但不直接归属于联系人。
为什么商机不挂在联系人下?这是一个很实际的业务问题。B2B业务中,一个客户的采购决策链通常涉及多个角色:使用部门的人提需求,采购部门的人走流程,财务部门的人审批付款。这时候商机如果只关联到一个联系人,销售很难看清整个决策链条。把商机挂到客户层级,联系人在商机中通过“角色”字段区分,比如“需求提出人”“决策人”“采购对接人”,业务模型更接近真实情况。
落实到表结构上,客户表的核心字段除了常规的公司名称、行业、规模、来源渠道外,我们另外加了一个“客户状态”字段,用来区分“潜在客户、进行中客户、已成交客户、已流失客户”。这个字段看起来简单,但它是后面所有统计看板的筛选基础,能直接决定漏斗图、转化率这些数据是否准确。
联系人的表结构比较常规,核心字段包括姓名、职位、手机号、邮箱、微信、是否关键决策人。这里有一个值得提醒的细节:手机号、邮箱这些联系方式应该单独存原始值,同时用统一的格式清洗函数做归一化处理,避免同一个客户被录成两条记录,因为“138-0000-0000”和“13800000000”在系统里如果不做处理,会被当成两个不同的号码。
商机表则承载了整个销售流程的状态流转。我们设计的核心字段有:商机名称、预计金额、当前阶段、成交概率、预计成交日期、负责人。其中“当前阶段”字段和配置中心的“销售阶段配置”挂钩,默认包括“初步接触、需求确认、方案报价、商务谈判、合同签订”这几个阶段。每个阶段对应的成交概率不是硬编码在代码里的,而是放在后台配置表中,允许管理员按业务实际情况调整。
2.2 跟进记录的轻量设计
跟进记录是整个DeskcommCRM里我私心最看重的一块内容。很多CRM会把跟进记录设计成一个大字段,让销售填一堆结构化表单,实际上填单率会大幅下降。我们的设计原则是“轻量记录、时间线归档”。
系统里跟进记录表的核心字段只有五个:关联客户ID、关联商机ID(可空)、记录类型(电话/面访/邮件/即时沟通)、内容正文、创建人。不需要单独建“跟进类型表”,因为字段就一个枚举,硬拆成表反而浪费。内容正文采用长文本,支持在编辑器里直接加@提及队友,被提及的人会收到任务提醒,这条设计后续会讲到。
数据展示上,所有跟进记录按客户维度聚合,形成一个按时间正序排列的时间线。销售点进客户详情页,一眼就能看到这个客户的沟通全貌:第一次电话聊了什么、上周发过什么方案、客户对报价的反馈是怎样的。时间线的数据粒度到分钟级,排序直接用创建时间,不做复杂的列表分页,而是用“加载更多”的流式交互,实测下来比传统分页更符合销售翻记录的直觉。
2.3 任务与提醒的触发机制
任务模块在DeskcommCRM里不是一个单纯的“待办清单”,它和客户、商机、跟进记录是联动的。设计上,任务表的核心字段包括:任务主题、关联类型(客户/商机)、关联ID、执行人、截止时间、优先级、状态、创建方式。
创建方式这个字段很有意思,它区分了“手动创建”和“系统自动生成”两种来源。手动创建就是销售自己添加的任务,比如“明天上午给张总回电话确认合同细节”。自动生成则是由系统事件触发的,典型场景有两个:
- 商机阶段变更时,系统自动给负责人生成一条“准备下一阶段所需材料”的任务,例如从“需求确认”变更为“方案报价”,自动生成任务“输出报价方案”。
- 跟进记录中有人被@时,系统给被@的人生成一条任务,内容直接引用跟进记录正文,点击任务可以跳转到对应的客户详情页。
自动生成任务这个功能极大提升了团队的响应速度。以前靠群消息提醒,消息一多就被刷没了,现在任务直接进入个人待办列表,配合首页的今日任务看板,不容易漏事。
提醒机制上,我们做的不是简单的“截止前X小时提醒”,而是加了多层递进式提醒。任务创建后24小时未完成,系统给执行人发一次轻提醒;超过截止时间仍未完成,提醒会升级抄送给负责人的直属上级。这个设计是跟业务团队聊完需求后加的——销售任务普遍有“能拖就拖”的倾向,稍微加一点管理压力,任务完成率提升非常明显。
3. 关键技术选型与架构方案
3.1 为什么选单体应用而不是微服务
DeskcommCRM在技术选型上主动选择了单体应用架构,而不是当前更“时髦”的微服务。原因很简单:这套系统的业务规模预期是内部团队或中型企业使用,用户量级在几十到几百人之间,数据量在百万级以内,单体应用在开发效率、部署运维、故障排查上的综合成本是最低的。
技术栈具体组合是:后端用Java Spring Boot 2.7 + MyBatis-Plus,数据库用MySQL 8.0,缓存用Redis,前端采用Vue 3 + Element Plus,权限认证使用Sa-Token框架。这套组合的最大优势是生态成熟、资料多、招人容易。Spring Boot自带的约定优于配置特性,配合MyBatis-Plus的代码生成器,可以从数据表直接生成基础CRUD代码,把重复工作省下来,业务功能开发速度能提高不少。
有人可能会问,为什么不用前后端分离更彻底一点的方案,比如后端纯提供API、前端用更重一点的工程化方案?我们的选择是折中处理:前端用Vue 3工程,开发和部署完全独立,但后端在做页面跳转时保留了少量服务端渲染的页面,主要用于管理后台的配置类功能。这样做的好处是,配置类功能逻辑简单、访问量低,用服务端渲染可以少写不少前端代码,降低维护成本。
3.2 权限模型:RBAC加数据范围的双层控制
权限设计是所有企业级系统的核心敏感点,DeskcommCRM采用“功能权限 + 数据权限”双层模型。
功能权限走的是标准RBAC(基于角色的访问控制)模型,角色-菜单-按钮三层结构。用户表、角色表、菜单表、用户角色关联表、角色菜单关联表这五张表是标配,这里不展开细说,但有一点必须提醒:菜单表里除了记录菜单名称和路径外,一定要加上“按钮权限标识”字段,比如“customer:add”“customer:export”。这样才能实现按钮级别的权限控制,否则角色配置只能管到“能不能看到这个页面”,完全管不到“能不能点导出按钮”。
数据权限则是一个比功能权限更隐蔽也更关键的维度。同样是查看客户列表,销售经理应该能看到所有销售的客户,普通销售只能看到自己名下的客户。DeskcommCRM中的数据权限采用“数据范围”字段控制,在角色表上增加一个枚举字段,可选值包括“仅本人”“本部门”“全部数据”。在实际查询时,MyBatis-Plus的拦截器会在SQL层面自动拼接数据权限条件,比如当前用户角色是“仅本人”,拦截器就会在客户查询SQL后面自动加上WHERE owner_id = 当前用户ID。
这里有一个实际开发中很容易踩的坑:数据权限的拼接一定要放在用户输入的所有查询条件之外,并且必须使用参数绑定而非字符串拼接,否则会有SQL注入风险。另外,部门的数据范围还要考虑子部门,我们用的是递归查询部门下级部门ID列表,再把IN (部门ID列表)拼接进SQL。部门层级深的情况下要提前考虑性能,建议给部门表加一个路径字段,比如“/1/3/7/”,查询子部门直接用WHERE path LIKE '/1/3/%',比递归高效得多。
3.3 客户公海与回收机制
客户公海是CRM系统里一个非常实用的功能,它解决的核心问题是销售资源闲置。很多CRM新手做开发时容易忽略这个模块,但实际业务中它的价值往往比想象中大。
DeskcommCRM的公海规则配置为:当客户负责人超过30天未更新跟进记录时,客户自动掉入公海池,其他销售可以领取。对应的,当销售领取公海客户后,客户自动归属到该销售名下,同时重置计时器。这个机制我们用了一张“公海规则配置表”来实现,表里保存着“掉入公海天数阈值”这个参数,而不是把30天硬编码在Java代码里。这样一来,不同团队可以配置不同的阈值——比如新客户要求15天必须跟进,老客户可能放宽到45天,灵活度完全由后台配置决定。
定时任务的实现用的是Spring Boot自带的@Scheduled注解,每天凌晨2点跑一次扫描。扫描逻辑核心是两步:查出所有“负责人不为空且最近跟进时间超过阈值”的客户ID集合,批量更新这些客户的负责人为空、状态置为“公海”。为了不影响正常业务高峰,建议定时任务执行时间避开工作时段,同时查询时要带上索引,否则这张客户表数据量上来后,全表扫描会拖垮数据库。
还有一个小细节容易忽略:当客户掉入公海时,系统要向原负责人发一条站内信通知,说明客户掉入公海的原因和时间。这看起来是小事,实际上非常重要。如果不通知,销售发现自己跟了很久的客户突然不见了,第一反应是找管理员查数据,反而增加额外沟通成本。
4. 从零实现核心流程
4.1 登录认证与操作日志
登录认证这块直接采用了Sa-Token框架,没有造轮子。选择它的原因一是轻量,二是和Spring Boot集成简单,三是有成熟的踢人下线、账号封禁功能。用户输入账号密码登录成功后,后端返回一个Token,前端存储到LocalStorage中,并在每次请求的Header里携带这个Token。
登录流程上有一个容易忽略的安全细节:密码传输必须使用加密方式。DeskcommCRM的做法是前端用RSA公钥加密密码,后端用私钥解密后再做校验。数据库里存储的密码字段,采用BCrypt算法加密存储,绝不存明文密码。这两个措施叠加之后,即使数据库泄露,攻击者拿到的是BCrypt hash值和被RSA加密过的传输密文,也无法直接还原出用户的原始密码。
操作日志这块,我们用的方案是AOP自定义注解加拦截。定义一个“操作日志”注解,在Controller方法上标注后,通过切面自动记录操作人、操作类型、操作内容、IP地址、耗时这些信息,异步写入操作日志表。这样做的优势是代码侵入性低,业务代码和日志逻辑完全解耦。操作日志不只是用来排查看谁改了什么数据,更重要的是在出现数据纠纷时能追溯责任,比如客户被误删、合同金额被篡改,这些问题有日志在手就很好追。
4.2 客户360度视图的聚合查询
客户详情页是整个系统销售使用频率最高的页面,它要在一个页面里聚合展示客户基本信息、联系人列表、商机列表、跟进记录时间线、待办任务。这个功能在技术实现上并不复杂,但在性能和数据一致性上需要注意一些细节。
最开始的方案是把所有数据放在一个接口里返回给前端,前端一次请求全量渲染。但实际测试下来,一个合作深度较高、跟进记录有几十条的客户,接口响应时间可能超过2秒,体验非常差。后来改成拆分为四个子接口,按需加载:进入页面先加载客户基本信息和联系人列表,商机列表和跟进记录通过前端Tab切换时再异步加载,同时接口层加了Redis缓存,把客户基本信息缓存5分钟,极大降低数据库压力。
跟进记录时间线接口有一个必须优化的点:分页查询时不要直接ORDER BY create_time DESC LIMIT offset, size,这样数据量大了之后偏移量越大越慢。正确做法是使用“基于游标的分页”,用“创建时间小于上一页最小创建时间”的方式翻页,比如WHERE create_time < #{lastTime} ORDER BY create_time DESC LIMIT 10,实测性能稳定,不会随页数增加而退化。
4.3 看板统计的SQL优化技巧
数据看板是管理层最喜欢用的模块,也是后端最容易写出性能瓶颈的地方。看板页面展示的数据包括:今日新增客户数、今日跟进次数、商机总金额、销售排行榜、客户阶段分布等。
第一版看板接口逻辑比较粗暴,每个指标单独查一次数据库,一个页面十几个指标就是十几次查询。业务高峰期时,管理层集中打开看板,数据库压力直线上升。优化方案是“合并SQL + 定时聚合”。
合并SQL的做法,比如今日新增客户数和今日跟进次数,可以用一条SQL配合条件聚合来实现:
SELECT COUNT(DISTINCT CASE WHEN create_date = CURDATE() THEN id END) AS today_new_customers, COUNT(DISTINCT CASE WHEN DATE(follow_time) = CURDATE() THEN follow_id END) AS today_follow_count FROM customer c LEFT JOIN follow_record f ON c.id = f.customer_id WHERE c.deleted = 0;定时聚合的做法更加彻底,用@Scheduled每天凌晨跑一次统计任务,把结果写入“每日统计汇总表”。看板页面默认读取最近5天的汇总数据,不再实时查询明细表。只有用户主动点击“刷新最新数据”按钮时,才走实时统计接口。这样在绝大多数情况下看板秒开,数据库负载也能降下来。
看板统计还有一个经常被忽略的问题——时区。如果服务器的默认时区是UTC,而业务用户都在国内,日期函数CURDATE()取到的日期可能和用户看到的日期不一致。解决方案是在数据库连接串上显式配置serverTimezone=Asia/Shanghai,并且在JVM启动参数中加上-Duser.timezone=Asia/Shanghai,双重保障。
5. 常见问题与避坑指南
5.1 跟进时间线为什么会出现乱序
上线初期接到过反馈,说客户跟进记录时间线偶尔会出现乱序,比如今天补录的一条记录跑到了上周记录的中间位置。排查后发现原因在创建时间字段上。
问题出在数据库的create_time字段使用了DEFAULT CURRENT_TIMESTAMP,但部分历史数据是通过数据迁移脚本导入的,导入时create_time被显式指定为业务发生时间。当销售在界面上补录历史跟进记录时,create_time被手动设置为过去的时间,而id还是自增生成的,按create_time排序时就和真实的创建顺序产生了冲突。
解决方案是把时间线排序改成双重排序:先按“业务时间”排序(允许用户指定的跟时间),业务时间相同时再按ID倒序。同时在前端表单上补录历史记录时,插入位置直接根据业务时间动态计算,避免用户误把新记录补到错误位置。
5.2 数据权限在批量导出时失效
另一个真实踩过的坑是数据权限批量导出场景。在页面上,数据权限拦截器正常工作,销售只能看到自己的客户。但做Excel导出功能时,第一版直接写了一个独立的查询SQL来导出所有客户,虽然前端按钮按角色控制了“谁能点导出”,但没有在导出服务层做数据权限过滤,导致部分权限较小的销售,可以通过拼接导出接口参数直接导走全量客户数据。这是一个很严重的数据越权漏洞,在内部安全审计时被查了出来。
修复方案是把数据过滤逻辑抽成一个公共的方法,页面查询和导出都必须经过这个方法。方法内部根据当前登录用户的角色,自动拼接数据权限SQL条件,导出时重新查一遍当前用户有权限的客户ID,再通过这些ID去导出,而不是沿用前端传过来的“全部客户”查询条件。这个经验想特别提醒做企业系统的开发者:功能权限是第一步,数据权限才是企业系统安全的核心门槛,每一步查询都要问自己一句“当前用户有权限看到这些数据吗”。
5.3 公海回收规则误伤客户记录
公海回收机制上线两个月后,销售反馈了一个问题:有客户虽然30天没有更新跟进记录,但期间一直通过私下渠道在和客户沟通,结果系统自动把客户收回公海,导致客户被其他销售领走,造成了内部抢客户的矛盾。
这个问题的根源是规则设计太刚性,没有把业务场景的所有变量都考虑进来。后来我们在规则配置里增加了“豁免条件”,比如客户状态为“已成交”或“进行中项目交付阶段”的,不参与公海回收。同时增加了“预警机制”,客户掉入公海前3天,系统会给负责人发预警通知,提醒“该客户即将掉入公海,请尽快更新跟进记录或申请豁免”。预警机制上线后,客户被误回收的投诉量几乎降为零。
5.4 数据同步与迁移的坑
最后补充一个数据迁移阶段的常见问题。DeskcommCRM早期从Excel和旧系统导入客户数据时,经常出现重复客户。当时团队还没有上“查重合并”功能,结果导入后用了一段时间才发现数据质量很差,一家客户在系统里可能有三四条记录。
查重方案我们最终选择了“规则 + 人工确认”的两阶段方案。规则阶段,系统通过公司名称精确匹配、公司名称相似度(编辑距离小于等于2)匹配、联系人手机号匹配,自动识别出疑似重复客户分组。人工确认阶段,管理员进入“查重合并”页面,逐条确认哪些客户需要合并,确认后系统把联系人、商机、跟进记录等关联数据全部转移到保留客户下,物理删除被合并客户。整个过程在管理后台操作,不需要写SQL处理,对运营人员来说友好很多。
6. 实测体验与后续扩展方向
DeskcommCRM从立项到内部试用,再到正式上线,整个过程有两点体会特别深。第一点,CRM系统成功的关键不在于功能堆得多全,而在于真正贴合业务的使用习惯。我们把核心页面设计成“打开客户详情就能完成80%的日常工作”,这个交互设计带来的使用率提升,远比花哨的数据大屏有效。第二点,系统上线只是开始,后续的规则调优、数据清洗、用户反馈跟进才是真正耗时的工作,需要持续投入。
目前这套系统正在做移动端适配方案。因为销售出差在外时,掏出手机快速查看客户信息、记录一条跟进、处理待办任务的需求非常强烈。我们的方案不是做一套完整的移动端App,而是先做一套基于H5的移动端适配页面,只包含客户查看、跟进记录、任务处理三个核心功能,小程序和原生App的版本在后续迭代中再逐步规划。
另一个计划中的方向是智能化的销售助手。当前系统沉淀了大量客户跟进记录和商机数据,后续想基于这些数据做两个能力:一是自动提取跟进记录中的关键信息,比如客户预算、决策时间节点、竞品信息,辅助销售快速了解客户全貌;二是基于历史赢单数据分析,对商机给出成交概率预测和风险提示。这些功能实现起来需要投入不少精力,但方向已经验证过,是有实际业务价值的。
回到最早的那个问题,DeskcommCRM到底解决了什么?它把客户管理中最重要的信息来源——日常沟通记录,变废为宝,把零散的沟通沉淀成结构化的客户资产。这套系统的完整实现,覆盖了数据建模、权限控制、业务流程引擎、定时任务、数据聚合这些企业级开发中的核心知识点,如果你正在规划自研CRM,或者想把团队客户管理流程理得更顺,文中提到的这些设计思路和踩坑经验可以直接拿去做参考。