ARTICLE DETAIL

资讯详情

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

从零自建CRM:DeskcommCRM的设计思路与落地实践

从零自建CRM:DeskcommCRM的设计思路与落地实践 上周帮一个做企业服务的客户梳理客户跟进流程发现他们的销售和客服还在用共享Excel表格记录客户反馈线下群里讨论单子进展客户信息散落在五六个渠道里。这个场景太典型了——不少中小团队其实就是缺一个能把客户全流程串起来的地方。DeskcommCRM这个项目就是针对这个问题做的它把客户信息、工单处理、沟通记录、销售跟进放到同一个工作台里让一个客户从初次咨询到成交再到售后支持所有动作都在一条时间线上留痕。这篇文章我会把DeskcommCRM的整体设计思路、核心功能模块、技术选型与部署方式、数据模型与权限设计以及我在实际操作中踩过的坑和排查经验都整理出来给正在规划自建CRM或准备从表格切换到专业系统的团队做一个参考。1. 整体设计与思路拆解为什么做DeskcommCRM它和通用CRM有什么不同在决定动手做之前我先花了些时间调研了市面上现成的CRM产品。坦白说成熟的商业CRM功能确实全从线索管理到商机预测都有。但问题恰恰出在“全”字上——对一个小型团队来说一大半功能用不上但为了这些用不上的功能却要付出不少学习成本和费用。更麻烦的是很多CRM的工单模块和客服模块是割裂开的销售看到的客户资料和客服处理的客户问题不在一个视图里两边信息不同步时就容易出现互相甩锅的情况。DeskcommCRM的定位很明确它不是一个什么都能干的CRM而是一个“沟通和客户记录不分离”的工作平台。观察一下实际业务流程就会发现客户生命周期里最重要的两种行为就是“沟通”和“记录”。销售打电话、客服回消息这是沟通沟通完要把结论、下一步计划、客户偏好记下来这是记录。大多数团队的痛点不是没有工具而是沟通工具和记录工具是两套聊天记录在微信或IM上客户资料在表格里时间长了完全对不上。所以DeskcommCRM从设计第一天就把这几件事揉在一起客户档案、工单、沟通记录、跟进任务全部围绕客户聚合展示。一个客户进来以后不需要在多个模块之间来回切换就能看到这个客户的所有历史信息。这种“以客户为根的单一视图”听起来简单但实际做起来涉及数据建模、权限设计、页面组织等一系列问题后面章节我会逐个展开。1.1 核心需求解析从“记录客户”到“管理客户关系”做项目之前我先把需求整理成了四类这也是后面功能开发的依据客户信息统一管理联系人、公司、来源渠道、备注标签必须有一个唯一的数据入口。沟通留痕与共享通过电话、邮件、在线工单产生的沟通内容自动挂到客户时间线上团队内可查。工单与任务流转客户问题要能转化为工单或任务指定负责人跟踪到闭环不能聊完就丢。数据看板与提醒管理者能快速看到待处理工单、客户跟进状态、团队工作量并且关键变更要有通知。需求看起来不复杂但每一条往下拆都有细节。比如“沟通留痕”到底留哪些内容是只留系统内的工单记录还是也导入邮件、导入历史记录我的取舍是先把系统内产生的沟通行为全部留痕外部渠道的导入做成批量入口这样既控制了项目初期的复杂度又保证了关键场景可用。1.2 方案选型对比自研、开源改造与商业产品的取舍不少朋友问我为什么不直接用现成的开源CRM做二次开发非要自己从零写。这里我对比过几条路说下当时的考量商业SaaS CRM部署快、功能全但定制化受限数据不在自己手里按坐席收费后期成本会一直涨。开源CRM二次开发比如国内外几款知名的PHP或Java写的开源项目适合已有团队熟悉该技术栈、需求匹配度高的场景。但不少开源CRM代码较重改起来等于重写且数据模型不一定贴合“工单沟通记录”这种强参与式场景。从零自研可以完全按团队需求设计数据结构和交互流程后续调整也灵活。代价是前期开发量较大维护成本也需要团队扛住。DeskcommCRM走的是从零自研的路线但并不是为了炫技。核心原因是这个项目最看重的“客户时间线”和“沟通与工单统一视图”在现有开源方案里几乎找不到理想组合与其花大力气改别人的地基不如按自己的结构搭房子。如果让我给建议我会说当你的核心业务流程在现成产品中无法满足时自研是合理选择但如果你只是需要一个标准CRM请直接买现成的省下来的时间拿去跑业务更值。2. 核心功能模块解析与实操要点五个模块撑起一个客户工作台DeskcommCRM的功能模块划分我遵循的原则是“围绕客户全生命周期而不是围绕部门职能”。传统CRM按销售、市场、客服来划分模块容易造成同一个客户的数据散落在不同部门的功能里。DeskcommCRM则反过来按客户生命周期各个阶段需要做的事划分功能。2.1 客户档案与360度视图一切信息的聚合点客户档案是整个系统的基础。这里的“客户”分两层公司级别的客户Account和具体联系人Contact。之所以要分开是因为实际业务中一个人可能代表多家公司咨询而一家公司也可能有多个联系人分别找你们。两者分开设计以后做数据分析时才不会有歧义。360度视图是DeskcommCRM比较有辨识度的一个页面。它把客户的基本信息、关联联系人、历史工单、跟进记录、待办任务、备注标签都放在一个页签组合里。操作上我推荐用左侧主信息区、右侧时间线的布局左侧解决“这个客户是谁”的问题右侧解决“这个客户发生过什么”的问题。时间线的实现是整个项目里比较复杂的点我放在第三部分讲。实操要点客户来源字段一定要保留原始值客户是来自官网留资、线下活动、还是老客户转介绍这个字段直接影响渠道效果分析建议做成单选并允许自定义。标签体系要克制一开始就设计几十个标签看似灵活最后一定用不起来。建议先设计几个高区分的标签比如“高意向”、“售后纠纷”、“待回访”等后续需要再加。重复客户的合并机制这是细节但很影响体验。我建议在创建客户时做名字和手机号的重合度提示避免一个客户被建两次。2.2 工单管理与流程闭环每个问题都必须有结果工单模块解决的是“客户提出问题后事情怎么推进到结束”的问题。很多人会把工单理解成简单的任务列表但工单更核心的是状态流转。一个工单从创建到关闭要经历待受理、处理中、等待客户反馈、已解决、已关闭等状态不同状态下负责人和可见权限都可能不同。我在DeskcommCRM里把工单的状态机设计成了可配置的JSON结构而不是写死在代码里。这样做的好处是不同团队可以按自己的流程调整状态名称和流转方向不需要改代码。举例来说一个做纯售后支持的团队可能只需要“待处理、处理中、已解决”三个状态而一个项目制服务的团队可能需要“待分派、进行中、验收中、已完成、已驳回”这套流程。工单处理里一个比较实用的设计是“SLA超时提醒”。如果一个大客户提交了一个紧急工单超过2小时没人接单系统会自动在工单上打标记同时通知主管。这个功能实现并不复杂定时任务扫一遍超时的工单就能做到但价值很高能显著减少工单石沉大海的情况。实操要点工单标题建议模板化比如“客户类型问题关键词公司名”例如“合同发票-发票开具失败-某科技公司”便于后续检索。关联客户和联系人要必填从设计上强行约束避免产生一堆找不到归属的孤儿工单。工单关闭前建议加确认环节客服可以标记“已解决”但更严谨的做法是允许客户反馈“问题仍存在”并自动重新打开工单这个循环机制能提升解决问题的一次性通过率。2.3 沟通记录与时间线让历史对话成为决策依据这是DeskcommCRM所有模块里我自己最喜欢、也是实际使用后认为最有价值的一个设计。它解决的核心痛点是新接手客户的人能不能快速了解这个客户之前发生过什么。实现上时间线是一个按时间倒序排列的事件流所有与客户相关的事件都会写进来包括创建客户、工单状态变更、跟进任务完成、备注内容、通话记录摘要等。每个事件有类型、操作人、时间和详情。技术实现上我用了“事件仓储”的思路不是像传统的业务表那样只存业务状态本身而是把每次业务动作当作一条独立事件记录下来查询时按客户聚合展示。这个思路是从Event Sourcing借来的但没有做得那么彻底本质上是一条“轻量操作日志业务快照”。通话记录算是这个模块里比较麻烦的一块。我最初打算接入网络电话API后来发现不同运营商和IM平台接口差异太大接多了反而不好维护。所以DesktopCRM采用的方式是“手动记录自动关联工单”客服打完电话后填写通话摘要选择关联的客户和工单系统自动记录通话时间。这种方式牺牲了一部分自动性但换来了稳定性和对多渠道的兼容。实操要点时间线事件必须不可修改历史沟通记录不应被编辑或删除只能新增补充说明。这是审计诉求也防止有人顺手改记录、后面说不清。事件详情用富文本存JSON便于后续扩展附加域比如将来要加通话录音链接、邮件附件ID不需要改表结构。时间线加载要做分页或按时间范围加载一些老客户可能积累了上百条事件一次性全查出来页面会很重。2.4 任务与自动化规则把重复劳动交给系统任务模块解决的是“今天有哪些事情要做”。设计的关键不是做待办清单而是让任务能在正确的时间出现在正确的人面前。DeskcommCRM里实现了三种任务来源手动创建销售自己创建跟进任务比如“明天上午回访某客户”。工单自动派生工单创建时自动给负责人建一个“处理工单”的任务。规则自动触发比如客户超过7天没有跟进动作自动生成一个“客户未跟进”提醒任务。规则引擎是这里比较出彩的部分。我参考了轻量规则链的思路每条规则有三个要素触发事件、条件判断、执行动作。比如{ name: 长期未跟进提醒, trigger: daily_check, conditions: { field: last_followup_at, operator: older_than, value: 7d }, action: { type: create_task, assign_to: account_owner, priority: medium } }这套规则JSON化的设计让非技术人员也能通过后台配置简单规则不用写代码。不过在设定条件时要注意规则条件别写太多三层嵌套以上日常使用中维护成本会陡增我的经验是规则尽可能简单直接复杂场景拆成多条规则串行处理。实操要点自动化规则先从小范围验证刚上线时每条规则先对少量客户开启观察一周确认效果再放大作用范围。任务截止时间要给缓冲区自动规则生成任务时截止时间建议按“触发时间N天”算不要直接设置为触发当天避免刚开始用就被大量任务轰炸。任务完成要有确认反馈在任务详情里记录完成时间、完成人和完成说明形成闭环方便管理者检查执行质量。2.5 数据看板与统计看清团队效率和客户健康度看板模块是给管理者和团队负责人用的。主要提供三类视图业务概览、工单统计、团队工作量。业务概览展示客户总数、今日新增、待处理工单数、跟进中的商机数量工单统计展示各状态工单占比、平均响应时长、平均解决时长团队工作量展示每个人当前处理中的工单数、今日完成的任务数、未完成任务数。做统计模块有个地方需要特别注意——统计口径。同样是“工单数”按创建时间算和按解决时间算结果可能差别很大。DeskcommCRM的做法是在所有统计卡片右上角标注统计口径比如“按创建时间统计”、“按最后更新时间统计”并在筛选器里允许切换时间维度。这个细节在自用阶段可能无所谓一旦多人使用统一口径能省掉很多扯皮。实操要点看板数据允许缓存5分钟没必要每次打开都实时查询数据库。核心指标做了定期汇总表非核心指标走实时查询平衡性能与实时性。维度下钻很实用在图表上点击某个数字能下钻到具体明细列表比如点击“今日新增客户”直接跳到客户列表页并带入筛选条件这个功能的使用频率比想象中高得多。同比和环比先想好怎么定义业务是自然周还是自然月、是否排除非工作日这些在开发前定清楚否则后期改起来很麻烦。3. 技术选型与部署落地从零搭起一个稳定可维护的CRM系统功能设计说完了聊聊技术方案。DeskcommCRM在技术选型上偏保守原则就是“用成熟的、团队最熟悉的技术不追新”。一个客户管理系统稳定性、可维护性和团队上手速度远比技术栈本身是否新潮重要。3.1 技术栈选型与理由为什么是PostgreSQL加Node.js后端我用的是Node.js NestJS前端是React Ant Design数据库选PostgreSQL缓存用Redis。选择NestJS主要是看中它的模块化结构和对TypeScript的良好支持团队本身也有TypeScript基础后端和前端能共享部分类型定义。项目的目录结构按业务模块划分每个模块是一个NestJS Module内部包含controller、service、repository和entity。数据库选择PostgreSQL而不是MySQL原因有三点第一PostgreSQL对JSONB的支持很好便于给客户、工单等实体附加动态字段第二它的数组类型和全文检索能力在实现标签系统、搜索功能时非常顺手第三系统后续如果要上地理位置查询PostgreSQL的PostGIS扩展能直接支持。对于一个需要灵活扩展业务字段的业务系统来说PostgreSQL的收益是很明显的。部署层面我准备了一整套Docker Compose配置把NestJS服务、PostgreSQL、Redis、Nginx都放在里面。这样一个全新的环境执行docker compose up -d就能跑起来省去了手工安装数据库和配置环境的时间。3.2 数据库设计核心从客户绑定到全局数据权限DeskcommCRM的数据库设计遵循“先分后合”的思路。所谓“分”是适应团队多部门、多工作流的差异每个团队的数据彼此分离所谓“合”是数据分析与跨团队协作时数据又能被精确聚合。实现上最核心的表包括customers、contacts、tickets、ticket_status_histories、activities、tasks、users、team_members和roles。关键表设计如下CREATE TABLE customers ( id BIGSERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, source VARCHAR(50), status VARCHAR(20) DEFAULT active, owner_id BIGINT, labels TEXT[], custom_fields JSONB DEFAULT {}, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE tickets ( id BIGSERIAL PRIMARY KEY, ticket_no VARCHAR(30) UNIQUE NOT NULL, customer_id BIGINT NOT NULL, contact_id BIGINT, subject VARCHAR(255) NOT NULL, description TEXT, status VARCHAR(30) DEFAULT pending, priority VARCHAR(20) DEFAULT medium, assignee_id BIGINT, sla_due_at TIMESTAMPTZ, resolved_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE activities ( id BIGSERIAL PRIMARY KEY, customer_id BIGINT NOT NULL, actor_id BIGINT, action_type VARCHAR(30) NOT NULL, content JSONB, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_activities_customer_time ON activities (customer_id, created_at DESC);在客户表里使用TEXT[]存标签、JSONB存自定义字段是一期开发时比较划算的选择。它能快速满足不同团队对客户字段的差异化需求不需要为每个新字段都跑一次ALTER TABLE。但要提醒一点JSONB虽然灵活不适合做频繁的范围查询或排序所以需要被统计的字段还是要抽成独立列。权限方面DeskcommCRM采用基于角色的访问控制模型。角色大方向上分管理员、经理、普通成员这三类在权限配置上做到字段级和操作级两个维度。字段级用于控制敏感信息谁能看到比如客户收入字段只对经理以上角色可见操作级控制谁能创建工单、谁能转移工单、谁能删除客户。数据范围用“全部数据”、“本团队数据”、“仅本人数据”三档来区分实际使用中最常用的是“本团队数据”既能保护数据隐私又能让团队内部信息充分流动。3.3 Docker部署与日常运维一键拉起和备份策略部署这块我给DeskcommCRM准备了一个精简但完整的docker-compose文件这个文件在团队里反复用了很久如果你只是个人试用或内部小团队部署完全可以照搬。version: 3.8 services: db: image: postgres:15-alpine restart: unless-stopped environment: POSTGRES_USER: deskcomm POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: deskcomm_crm volumes: - db_data:/var/lib/postgresql/data - ./backups:/backups ports: - 5432:5432 redis: image: redis:7-alpine restart: unless-stopped command: redis-server --appendonly yes volumes: - redis_data:/data app: build: . restart: unless-stopped depends_on: - db - redis environment: DATABASE_URL: postgresql://deskcomm:${DB_PASSWORD}db:5432/deskcomm_crm REDIS_URL: redis://redis:6379 JWT_SECRET: ${JWT_SECRET} ports: - 3000:3000 web: image: nginx:alpine restart: unless-stopped ports: - 80:80 - 443:443 volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf - ./dist:/usr/share/nginx/html depends_on: - app volumes: db_data: redis_data:日常运维里有两件事我建议从一开始就做好。第一是数据库自动备份在宿主机上配置一个每天凌晨执行的pg_dump脚本保留最近14天的备份文件并使用cron定期将备份同步到对象存储。很多内部系统出事都是因为重建之前没备份这个坑踩一次就够疼了。第二是应用日志的定期清理NestJS输出的请求日志如果在容器里无限累积最终占满磁盘导致服务不可用所以日志轮转策略必须配置好。4. 实战中遇到的典型问题与排查技巧实录这个项目开发完并跑了几个月以后我整理了一些真实遇到并且解决掉的问题。这些细节直接写代码时不太容易注意到但遇到一次就会印象深刻。4.1 时间线查询越来越慢索引设计与分页策略客户时间线上线一段时间后随着客户数量和活动事件增多列表加载明显变慢。排查后发现activities表虽然建了复合索引但在前端一次需要拉取最近50条数据的情况下查询需要扫描大量事件并作排序部分老客户的事件已经上千条响应时间从几十毫秒涨到几秒。解决办法有两层。第一层是把分页改成游标分页用created_at和id做联合游标不再用传统OFFSET分页。第二层是增加了一个“聚合时间线摘要”字段存储最近50条事件的关键信息快照日常列表页优先读摘要点击“查看全部”才全量查询。这个优化上线后列表加载速度基本稳定在200毫秒以内。4.2 工单状态并发更新行锁和乐观锁的取舍工单处理过程中客服和销售可能同时操作同一张工单。最初我用的是“先查后改”的方式结果出现了后提交的人覆盖前提交内容的情况。后来在工单表加了一个version字段更新时用“版本号状态条件”作为更新条件如果影响行数为0说明记录已被别人改动前端会提示用户刷新后再操作。这个乐观锁方式对工单这种低频并发修改的场景足够用而且实现简单没必要引入复杂的锁机制。4.3 Webhook通知丢失重试队列与幂等消费系统要对接企业微信和钉钉的群机器人通知最初Webhook发出去就没有后续了偶尔会出现通知丢失的情况。排查下来是因为网络闪断或者目标接口临时不可用时直接丢弃了。解决方案是在Redis里建一个通知任务队列发送失败的任务进入重试队列按指数退避策略最多重试5次。同时在接收端做了幂等控制用消息ID做去重防止重复推送导致用户收到重复消息。4.4 权限边界漏洞多团队数据隔离的修正一次测试中发现普通成员的请求在特定条件下能看到其他团队客户的工单详情。原因是前端传了customerId后后台查询时没有校验当前用户和该客户是否属于同一数据范围。修复方式是在所有查询入口统一加一个数据权限拦截器DataScopeInterceptor根据当前用户角色和数据权限范围自动拼上过滤条件。这也提醒我涉及权限的代码一定要集中在统一层级实现不要散落在每个service方法里否则迟早会漏掉某个入口。常见问题速查表方便读者对照排查现象可能原因排查思路时间线加载慢缺少索引或用OFFSET分页检查索引改用游标分页工单内容被覆盖并发更新无版本控制增加version乐观锁Webhook通知偶发丢失网络波动或对端超时直接丢弃引入重试队列和幂等消费用户能看到非本团队数据查询入口缺少数据权限过滤统一增加DataScope拦截器看板统计口径对不上各统计项时间口径不统一统一统计口径并在界面标注备份恢复失败备份时未停止写操作或备份文件不完整使用pg_dump时加锁或选低峰期执行5. 项目扩展方向与个人实践心得DeskcommCRM当前版本已经能覆盖一个中小团队客户管理的主流程但离“好用”还有很长的路可以走。我自己接下来计划做三个方向的扩展也分享出来供参考。第一个是邮件渠道的深度集成。当前版本对邮件的支持只做到了手动转发和归档下一步想尝试用IMAP协议拉取指定邮箱的往来邮件通过发件人地址自动关联客户把邮件正文和附件自动写入客户时间线。这一步做完客户沟通的留存率会提高很多。第二个是智能摘要生成。工单里客服写的沟通摘要质量参差不齐可以考虑接入大模型API自动从客户描述和聊天记录中生成结构化摘要并提取关键词和待办事项。不过要注意的是对接外部模型前需要做好隐私确认和敏感信息过滤客户数据不能随意发到第三方服务。第三个是移动端轻量适配。现在团队出门拜访客户时只能在PC端查看资料不太方便。不需要做完整App做一个移动端H5适配版本核心功能就是查客户、看时间线、快速建工单和打卡拜访记录就能覆盖大多数外出场景。我从这个项目中体会最深的一点是做业务系统最难的不是单点功能实现而是如何用数据把这些功能串成一个完整的故事。DeskcommCRM的核心价值恰恰在于把通信记录、工单、任务、客户信息这些原本分散的“零件”整合在一个客户维度里让任何接手的人都能快速知道这个客户是谁、发生过什么、下一步该做什么。对正在考虑自建CRM的团队我会建议先别急着写代码花两周时间把核心的客户流程画清楚想清楚“一条客户记录从创建到成交流转的过程中每一步需要记录什么信息”然后再动手也不迟。
返回列表