news 2026/9/25 6:02:26

从零构建内部CRM系统:客户沟通记录与团队协作实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零构建内部CRM系统:客户沟通记录与团队协作实战

DeskcommCRM这个项目,名字听起来有点长,其实就是Desk Communication CRM,翻译过来是“桌面沟通型客户关系管理系统”。说白了,这就是我们销售和客服团队自己用的那套客户沟通管理工具。做这个系统的初衷特别朴素:我们每天跟客户的沟通记录,散落在微信、企业微信、电话通话记录和一堆Excel表格里,想查一个客户的历史沟通情况,得翻好几个地方,甚至经常出现销售A谈过的客户,销售B不知道来龙去脉,又跑去重复沟通惹恼客户的情况。

这个项目是我带团队从零到一搭起来的,目标很明确:把“客户档案”和“沟通记录”这两件事收拢到一个系统里,让每次跟进都有迹可循,让新接手的人能快速读档。如果你正在考虑给自己团队做一套类似的内部CRM,或者想知道一个轻量级客户管理系统背后有哪些设计和坑,这篇内容应该能给你一些可以直接用的经验。

1. 项目背景与需求拆解

在做DeskcommCRM之前,我先花了大概两周时间做需求调研。很多团队一开始做CRM容易犯一个错误——一上来就想把销售管理、客户分类、商机漏斗、合同回款全塞进去,结果做出来一个四不像,谁都不爱用。我们当时定的原则很简单:只做三个核心场景,其余一概不碰。

1.1 核心业务痛点:沟通记录散落各处

我们团队当时的业务形态是典型的B2B客户服务加二次销售,销售和客服人员每天要接触大量客户。最痛苦的事情是:

  • 客户来电话咨询,接待的人换了,新同事对客户之前的情况一无所知。
  • 重要客户的生日、合同到期日、回访提醒全靠个人手机备忘录,人一走,信息就带走了。
  • 管理层想统计一下“今天有多少客户需要跟进”,根本统计不出来。

这些痛点归纳起来就一句话:客户信息在个人手里,没有沉淀到组织层面。我们做DeskcommCRM,本质上就是把本应该属于公司的客户资产重新“收归公有”。

1.2 功能边界划定:MVP阶段的三个核心模块

项目第一版只做了三个核心模块:客户档案管理、跟进记录管理、待办提醒。每个模块再往下拆就很清晰了:

  • 客户档案管理:客户的姓名、公司、联系方式、来源渠道、所属销售、客户状态、备注。
  • 跟进记录管理:按时间线记录每一次和客户的沟通内容,支持文字和附件。
  • 待办提醒:基于跟进记录自动生成下一步任务,包括一般回访时间、合同到期提醒。

其他什么数据分析、自动标签、营销活动这些,全部排在第二期。做内部系统最怕的就是需求堆砌,第一版一定要小,小到团队能用起来,才会有后续迭代的可能。

1.3 需求调研的方法:和真正的使用者聊

我走访了销售、客服、售后三个角色的实际使用者,发现不同角色对CRM的诉求差别很大。销售关心的是“客户资料好不好找”“跟进记录省不省事”;客服关心“记录能不能快速填充”“能不能看到客户是否有过投诉”;管理层关心“能不能出报表”。

最后我们把这三个角色的共性需求提取出来,形成了核心功能列表。这一步特别值得做,因为你会发现,用户嘴上说的需求和实际工作流里的需求,往往是两回事。只有站在他们座位的角度去看,才能设计出真正有人用的功能。

2. 技术方案选型与整体设计思路

技术选型这件事,没有绝对的好坏,只有合不合适。因为我们团队本身前后端都有,所以选型上相对灵活。这里我把当时的思考和取舍过程写出来,给大家一个参考。

2.1 前端框架:Vue 3 + Element Plus

前端用了Vue 3配合Element Plus组件库。选Vue而没选React,不是因为Vue比React好,而是因为团队成员的熟悉度更高,同时Element Plus在后台管理系统这块非常成熟,表格、表单、弹窗这些常用组件开箱即用,能极大提升开发效率。

页面结构上采用经典的“左侧菜单栏加右侧内容区”的布局,菜单包括“客户列表”“跟进记录”“待办任务”“数据看板”“系统设置”。这套东西没什么创新,但胜在稳定、学习成本低,新员工上手也快。

2.2 后端框架:Spring Boot + MyBatis-Plus

后端用了Java的Spring Boot,搭配MyBatis-Plus做数据访问。Java在这类内部管理系统里的优势不用多说,稳定、类型安全、生态成熟。Spring Boot让我能用最少的配置搭起来一个可用服务,而MyBatis-Plus极大简化了单表CRUD操作,很多通用方法直接继承就行。

接口设计上,统一返回RESTful风格的JSON数据:成功返回{ "code": 0, "data": ... },失败返回{ "code": 非0, "message": 错误信息 }。这套约定看起来简单,但团队协作时避免了大量沟通成本。我还统一加了全局异常处理,业务异常和服务异常都能返回结构化信息,而不是一坨堆栈。

2.3 数据库选型:MySQL + Redis

数据存储用了MySQL,这几乎是企业应用的标配了。表结构设计上,我比较克制,核心表就五张:客户表、联系人表、跟进记录表、待办任务表、用户表。再多就容易乱了。

Redis用在了两个地方:一是登录会话,存token;二是热门客户的查询缓存。一开始我没打算上Redis,是因为后来发现客户列表的查询量越来越大,很多查询是重复的同一个列表,加一层缓存效果立竿见影。

2.4 为什么不用现成的第三方CRM

调研阶段也考虑过直接用市面上现成的CRM产品,但最后放弃了,原因有三:

  • 价格不透明,按账号收费,团队人一多成本就上来了。
  • 定制需求做不了的场景多,比如我们内部某些字段和业务流程是独有的。
  • 客户数据在自己手里才最安心,不想被绑定在某个平台的体系里。

自己做并不是因为第三方不好,而是因为内部系统最核心的价值是可控和定制,这个逻辑要搞清楚。

3. 数据库设计:五张核心表是怎么构建起来的

数据库设计是整个DeskcommCRM的“地基”。这个环节如果偷懒,后面所有功能都会别扭。我在这里花的时间最多,也最建议后来者在这个阶段慢下来。

3.1 客户表:怎么存才能不重复

客户表是系统的核心,我设计了这些关键字段:id、name、company、phone、source、owner_id(归属销售)、status、created_at、updated_at、deleted_at。

这里想强调一个细节:客户表必须做软删除,用一个deleted_at字段标记删除时间,而不是物理删除记录。做内部系统时,经常遇到员工误删客户资料的情况,软删除保证数据可以恢复,同时统计报表不会被破坏。

还有一个很关键的逻辑:客户去重。我们的去重规则是“手机号完全匹配”,在phone字段上建立了唯一索引。真实业务里这个规则可能会漏掉一些客户(比如一个人换了新手机号),但它能挡住90%以上的重复输入,效果立竿见影。

3.2 跟进记录表:不能直接修改的设计

跟进记录表是我个人认为整个系统设计最值得讲的部分。表中核心字段有:id、customer_id、user_id、content、next_action_time、next_action_type、attachments、created_at。

关于这个表,我定了一条铁律:记录只能新增,不允许修改、不允许删除。所有操作只有逻辑删除的选项,由管理员在特定场景下执行。为什么?因为客户的沟通记录是一种“日志型数据”,如果允许任何人随意编辑,很容易出现“翻旧账”的问题——比如某人把之前的沟通内容改掉了,将来说不清是谁的责任。

实际操作中,我给跟进记录接口只暴露了POST(新增)和GET(查询),在代码里根本没写更新和物理删除的方法。这是从接口层就卡死,而不是靠前端隐藏按钮。

3.3 待办任务表:沟通记录的后续延伸

待办任务表设计为独立于跟进记录表,但跟跟进记录是强关联的。它包含id、customer_id、task_type(回访/续费/合同等)、due_time、assignee_id(处理人)、status、related_record_id(关联的跟进记录ID)。

这样设计的好处是,销售每次新建跟进记录时,可以顺手勾选一个“下一步计划”,系统自动生成一条待办任务,并出现在对应人的待办列表中。这个方法减少了重复输入成本,把计划的生成自然地嵌入到日常操作中。

3.4 索引设计:哪些字段需要建索引

数据库表建好以后,索引是直接影响查询性能的。我当时给三张核心表分别建了这些索引:

  • 客户表:(phone)唯一索引、(owner_id)普通索引、(status)普通索引。
  • 跟进记录表:(customer_id)普通索引、(user_id)普通索引。
  • 待办任务表:(assignee_id, status)联合索引、(due_time)普通索引。

索引这东西,宁缺毋滥,因为索引本身也要存储和维护,写频繁的表索引太多反而拖慢写入速度。我们这几张表读取多写入少,索引多建几个问题不大。

4. 核心功能模块的实现细节

接下来是实操环节。这一节我会按功能模块来讲实现细节,重点说清楚“怎么做的”和“为什么这么做”。

4.1 客户列表:分页、搜索和列表缓存

客户列表是所有操作的门面,性能直接决定用户体验。我用了PageHelper分页插件,前端每次请求传pageNum和pageSize,后端返回当页数据和总条数。

搜索这块,因为业务里最常见的场景就是“找个手机号对应的客户”,所以搜索条件重点支持手机号、姓名、公司名称的模糊匹配。但模糊匹配的SQL如果直接LIKE '%关键词%',是走不了索引的,所以我在实现时做了个额外优化:

  • 手机号搜索:如果关键词长度大于等于7位且全是数字,按手机号前缀匹配,走索引。
  • 名字/公司搜索:只允许包含名称开头的匹配,也走索引。
  • 都不满足时,才走全表模糊查询,并加上LIMIT 50的限制,避免一次查太多。

列表缓存我在Redis里给“常用销售视图”做了60秒的缓存。第一次请求时从MySQL查出来,然后把数据存入Redis缓存,60秒内的后续请求直接读缓存。这个功能上线后,整体列表打开速度确实快了很多,尤其是销售员工每天反复查看自己客户列表的场景,体感改善明显。

4.2 跟进记录:新增记录的完整流程

新增跟进记录是整个系统里最重要的操作。我在实现后端接口时,处理了一个关键问题:事务一致性。

一个完整的“新增跟进记录”流程包括:

  1. 校验客户ID是否存在,是否有权限编辑。
  2. 写入跟进记录表。
  3. 如果勾选了“生成下一步任务”,则同步写入待办任务表。
  4. 更新客户表的updated_at字段,方便列表按最近跟进时间排序。
  5. 如果指定了下次回访时间,把该时间同步到客户的next_follow_time字段。

这五步必须放在同一个事务里,任何一个环节失败都要全部回滚。如果拆成多个请求各自单独执行,就会出现“记录写进去了但任务没生成”这种脏数据,很难排查。

核心伪代码大致是这样的:

@Transactional public Long addFollowRecord(FollowRecordCreateDTO dto) { // 1. 校验客户权限 Customer customer = customerMapper.selectById(dto.getCustomerId()); if (customer == null) { throw new BusinessException("客户不存在"); } // 2. 写入跟进记录 FollowRecord record = new FollowRecord(); record.setCustomerId(dto.getCustomerId()); record.setUserId(currentUserId()); record.setContent(dto.getContent()); followRecordMapper.insert(record); // 3. 生成待办任务 if (dto.getNeedTask() != null && dto.getNeedTask()) { TodoTask task = new TodoTask(); task.setCustomerId(dto.getCustomerId()); task.setDueTime(dto.getNextActionTime()); task.setAssigneeId(dto.getAssigneeId()); task.setStatus(0); // 未完成 task.setRelatedRecordId(record.getId()); todoTaskMapper.insert(task); } // 4. 更新客户表 Customer update = new Customer(); update.setId(dto.getCustomerId()); update.setNextFollowTime(dto.getNextActionTime()); customerMapper.updateById(update); return record.getId(); }

4.3 待办提醒:基于时间的自动推送

待办任务做出来后,最大的挑战是如何“让该看到的人看到”。我当时的方案是:不做即时推送,改成“进入页面的轮询查询”。

也就是说,用户打开待办页面时,后端自动查询当前用户所有未完成的任务,并且按照due_time排序,把过期的放在最前面。过期任务用红色标识,当天任务用黄色标识,未来任务用正常色。

后面还做了一层“每日待办通知”,通过企业微信机器人每天早上9点把当天有到期任务的销售名单推送到群里。这个实现不复杂,一个定时任务就能搞定:

@Scheduled(cron = "0 0 9 * * ?") public void sendDailyReminder() { List<TodoTask> todayTasks = todoTaskMapper.selectTodayTasks(); // 按 assigneeId 分组,组装每个销售的任务清单 // 调用企业微信机器人 Webhook 发送消息 }

不过这里要注意一点:定时任务的执行时间要避开业务高峰。我们当时定时任务都安排在凌晨或者早上上班前,一定不要在中午业务高峰期跑大批量任务,否则会拖慢正常接口。

4.4 权限控制:前后端配合的越权防护

权限控制是DeskcommCRM里很容易被忽略但绝对不能省的部分。我们的权限模型分三层:

  • 管理员:看到所有客户的资料,管理整个系统。
  • 主管:看到自己所在小组的客户。
  • 普通销售:只能看到自己名下客户和主动共享给团队的客户。

后端用一个拦截器解决大部分权限判断——从请求头里取token,解析出当前用户ID和角色,再在具体接口里做数据范围的校验。关键方法如下:

public boolean hasAccessToCustomer(Long customerId) { // 管理员直接放行 if (isAdmin()) return true; // 普通用户校验 customer.owner_id 是否等于当前用户ID Customer customer = customerMapper.selectById(customerId); if (customer == null) return false; return customer.getOwnerId().equals(currentUserId()) || isTeamShared(customerId); }

前端菜单和按钮也做了对应的权限控制。但前端控制的目的只是优化体验,真正安全的防线一定在服务端。任何时候都别相信前端传来的参数,后端必须验证客户ID和当前用户的关系。

4.5 看板统计:用分组查询算出团队绩效

数据看板在第二版加上去的,主要给管理层看三个指标:每个销售的客户总数、今日新增跟进数、待办完成率。实现其实很简单,就是几条SQL的聚合查询:

SELECT owner_id, COUNT(*) FROM customer WHERE deleted_at IS NULL GROUP BY owner_id;

这类统计查询不需要实时,所以我做了一层异步缓存,每天凌晨把前一天的统计数据计算好,放到一张独立的统计表里,管理界面的看板直接读统计表,不查业务表。这样即便业务量涨上来,统计查询也不会拖垮主流程。

5. 系统实现过程中的常见问题与排查技巧

项目开发过程中踩了不少坑,有些问题看着不大,但解决起来很费时间。这里挑几个典型的写出来,希望大家能少走弯路。

5.1 客户数据重复:索引没生效的原因

第一版上线后两个星期,我们就发现了客户重复问题。排查后发现问题出在一个很隐蔽的地方:phone字段在MySQL里定义成了VARCHAR(20),但有个接口在写入前做了字符串的trim处理,而另一个导入接口没有做,导致“138 1234 5678”和“13812345678”被当成了两个不同客户。

解决思路是:在服务端入口统一处理手机号格式化,所有写入都走同一个normalizePhone方法,去掉空格、横线等分隔符。同时在手机号唯一索引的基础上,把客户录入接口改成先查询再插入,查不到才新增,从接口层防止重复。

5.2 查询慢:模糊搜索拖垮了列表页

客户列表页在数据量涨到三万条之后变得很慢,单次查询要两三秒。排查发现罪魁祸首是一个LIKE '%关键词%'的模糊查询,如果是全表模糊搜索,数据量一大就是灾难。

我们的调整方案是:

  • 查询条件改成多字段分别匹配,手机号前缀走索引。
  • 把模糊搜索改成“必须输入至少两个字符才触发”。
  • 统一加上1秒的Redis缓存。

调整后列表响应时间降到了200毫秒以内。给一个建议:凡是涉及比较耗时的查询,都先想清楚这个查询频率高不高、是不是可以加缓存,这比事后调优舒服得多。

5.3 并发编辑导致的数据覆盖:字段级更新 vs 整行更新

跟进记录的“下一步任务”如果生成两次,会出现待办任务重复的问题。原因是两个请求同时进来,都看到了客户当前没有未完成任务,就各自插入了一条。

解法是给待办任务表的customer_id加上“未完成唯一约束”的功能。MySQL里没法直接在部分行上建唯一索引,我最终是额外加了一个字段active_flag,当任务未完成时设置为固定值1,完成之后变为0。然后建联合唯一索引(customer_id, active_flag),这样未完成的任务在同一客户下只能存在一条,已完成的可以有多条。这个方案用一个库存的小技巧解决了并发问题。

5.4 配置文件泄漏:本地环境的几个坑

开发过程中每个人的本地环境配置不一样,出现过几次“本地跑得好好的,部署到服务器就报错”的问题。后来我们统一用application-dev.yml、application-prod.yml区分环境,把数据库连接、Redis连接、机器人Webhook地址这些配置全部外置,并在代码里写清楚必须从配置中心读取,不再允许硬编码。

5.5 排查工具:一把好用的瑞士军刀

顺便推荐几个排查问题的小工具:

  • Arthas:线上JVM诊断工具,能直接看接口耗时、方法调用参数、异常堆栈,对排查线上问题非常有帮助。
  • Flyway:数据库自动版本控制,每次表结构变更都写一个迁移脚本,团队协作不会把库搞乱。
  • Postman/APIFox:接口调试和文档管理,前后端协作效率能提升一大截。

6. 部署上线与推广使用

开发完成后,上线部署这块也值得一提。我们自己有一台内网服务器,部署方案用的是:Nginx做反向代理,前端打包成静态文件丢在Nginx里面,后端用java -jar跑一个Spring Boot包,数据库用MySQL 8.0。

为了让团队真正愿意用,我在上线时做了三件事:

  • 数据导入:把原有Excel里的客户数据批量导入到系统,让团队打开系统就能看到熟悉的客户,而不是空空如也。
  • 演示培训:组织两场实操演示,专门讲“怎么用、对公司有什么好处”,让每个人都能当场试一遍。
  • 树立标杆:让部门里一位比较有影响力的销售先用起来,做出几个成功案例,其他人看到效果自然会跟上。

上线后的第一周我还特意每天看一眼后台日志,哪个页面访问最多、哪个接口报错最多,第二天就修,尽量保证第一印象是好的。内部系统最怕的是一开始体验差被大家抛弃,一旦被抛弃就很难再拉回来了。

7. 项目复盘:做内部系统的一些体会

DeskcommCRM到现在稳定运行了大半年,团队的使用率维持在九成以上。回看这段经历,我个人最大的感受是:做内部系统,赢在“懂业务”,而不是“炫技术”。

很多技术方案看起来高大上,但如果脱离了团队实际工作场景,做出来的东西只会成为负担。我们全程克制地控制功能范围,先把核心沟通链条打通,持续根据反馈迭代,才让这套系统真正扎根下来。

另一个经验是:要特别重视非功能性需求。响应速度、权限安全、数据备份,这些看起来不起眼,但对用户的日常使用体验影响极大。一次白屏、一次数据丢失,可能就会让用户对系统的信任度大幅下降。

最后想给正在做类似项目的人一个建议:内部系统的“成功标准”不是代码写得多优雅,而是团队每天真的在用它,并且愿意把里面的数据当作唯一可信的信息源。能做到这一点,这个项目就已经成功了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 6:01:29

中兴B860AV3.2-M线刷全攻略:S905L3刷机与EmotnUI桌面实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 6:00:38

智慧社区管理系统毕设实战:Spring Boot+Vue全栈开发与避坑指南

简介&#xff1a;这份资源是面向高校计算机相关专业学生与课程设计学习者的「小康之家智慧社区管理系统」毕业设计完整源码包&#xff0c;围绕现代社区数字化管理场景&#xff0c;覆盖居民信息管理、物业缴费、社区公告、报修服务、智能安防、活动报名、数据报表与用户权限等核…

作者头像 李华
网站建设 2026/9/25 5:59:57

OpenSSL在64位系统下的lib/dll与头文件配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 5:59:23

使用 AWS SDK for Java 2.x 操作 IAM 的完整实践指南

示例工程教程后端 【免费下载链接】aws-doc-sdk-examples Welcome to the AWS Code Examples Repository. This repo contains code examples used in the AWS documentation, AWS SDK Developer Guides, and more. For more information, see the Readme.md file below. 项目地…

作者头像 李华