尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

从零构建内部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 跟进记录新增记录的完整流程新增跟进记录是整个系统里最重要的操作。我在实现后端接口时处理了一个关键问题事务一致性。一个完整的“新增跟进记录”流程包括校验客户ID是否存在是否有权限编辑。写入跟进记录表。如果勾选了“生成下一步任务”则同步写入待办任务表。更新客户表的updated_at字段方便列表按最近跟进时间排序。如果指定了下次回访时间把该时间同步到客户的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() { ListTodoTask 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到现在稳定运行了大半年团队的使用率维持在九成以上。回看这段经历我个人最大的感受是做内部系统赢在“懂业务”而不是“炫技术”。很多技术方案看起来高大上但如果脱离了团队实际工作场景做出来的东西只会成为负担。我们全程克制地控制功能范围先把核心沟通链条打通持续根据反馈迭代才让这套系统真正扎根下来。另一个经验是要特别重视非功能性需求。响应速度、权限安全、数据备份这些看起来不起眼但对用户的日常使用体验影响极大。一次白屏、一次数据丢失可能就会让用户对系统的信任度大幅下降。最后想给正在做类似项目的人一个建议内部系统的“成功标准”不是代码写得多优雅而是团队每天真的在用它并且愿意把里面的数据当作唯一可信的信息源。能做到这一点这个项目就已经成功了。
返回列表