1. 为什么要自己动手做一套CRM我是从2024年三季度开始动手做 DeskcommCRM 这个项目的。当时团队规模不大,销售、客户成功、售前加起来也就二十来号人,但客户信息已经散得没法看了:有人用Excel登记,有人直接记在微信备注里,还有几个老销售习惯把跟进记录写在纸质本子上。每次要拉一条客户生命周期的完整视图,基本等于“跨系统考古”。市面上的CRM不是没有,团队早期也用过几个免费版。但用下来就两个感觉:一是数据不在自己手里,哪怕只是把自己关在系统外面,也可能只是因为对方调整了免费策略;二是字段和流程是别人定死的,我要加一个“客户的行业细分”“预计回款周期”这类字段,要么升级付费,要么自己妥协去适配对方的产品逻辑。我后来跟同事开玩笑说,免费CRM最大的问题不是功能少,而是“规则由别人定”。这个痛点积累了大半年,最终还是决定自己搭一套,就是后来的 DeskcommCRM。这个项目解决了三个核心问题:客户数据集中管理,从前期的线索跟进到成交后的售后记录,全链路统一在一个系统里。销售协作有迹可循,谁什么时候联系了哪个客户、客户怎么反馈的,全程留痕,不再靠人肉同步。数据和系统归属自己,字段、流程、权限都可以按团队情况调整,也不再担心免费版突然限流或停服。这篇文章不是讲“CRM行业分析”的,而是把 DeskcommCRM 从设计、选型、实现到上线运营的完整过程拆开讲。如果你也在纠结“要不要自建CRM”,或者正在被各种SaaS收费策略折腾,可以参考下我的实操思路和踩坑记录。2. 项目整体设计与思路拆解2.1 先想清楚:CRM到底管的是什么很多人对CRM有误解,以为它就是个“通讯录Plus”或者“销售漏斗图表工具”。但真正跑过业务就会发现,CRM管理的核心其实是一串状态变化:客户从“陌生线索”变成“有初步意向”,再变成“进入商务谈判”,最后变成“已成交”或“已流失”。每个状态之间,都依赖销售人员的具体动作来驱动——打电话、发资料、约访、报价、催款。所以 DeskcommCRM 的第一步不是写代码,而是先把业务状态机画出来。当时我跟几个核心销售聊了一轮,梳理出团队实际推进项目时的动作序列,最终定了五个核心状态:线索:刚获取到联系方式,还没有有效沟通。跟进中:已建立联系,客户有比较明确的兴趣点。方案阶段:客户索要方案或报价,进入实质性比较。商务谈判:涉及合同条款、价格折扣、回款周期。成交/流失:要么签单,要么明确放弃。这个状态机是整个系统的骨架。我见过不少自制CRM失败的案例,原因往往不是技术不行,而是压根没理清业务状态,直接把Excel表格搬上线,结果只是“换了个地方填表”。2.2 为什么选择自建而不是继续用SaaS市面上不是没有好用的SaaS CRM,蝉鸣CRM、飞鱼CRM这些我都实际注册用过一段时间。蝉鸣的界面做得挺清爽,普通销售上手很快;飞鱼的员工邀请和看板设计也花过心思,团队用起来体验不差。但真正让我下定决心自建的,是三个很现实的问题。第一,定制成本。免费版只能改改字典项,一旦涉及“企业客户多联系人”这种结构,免费版基本无解。第二,数据所有权。客户信息是公司最核心的资产,放别人服务器上,对方系统出问题或者策略调整,我们没有任何话语权。第三,长期费用。免费版看着不要钱,但团队一超过10个人,很多功能就开始提示升级;算一下人均费用,五年总成本其实非常高。自建当然也有代价,主要是开发和维护的时间。但如果你团队里有人能写代码,或者愿意学一点,这套投入比长期被SaaS绑架要划算得多。DeskcommCRM 从立项到内测上线,实际只用了六周的业余时间,之后的迭代都是按需加功能。2.3 项目范围控制:哪些做、哪些不做自建系统最容易翻车的地方不是“不会做”,而是“什么都想做”。我曾经列过一张愿望清单:在线客户沟通、合同电子签、自动外呼、BI大屏……每一项看着都合理,但全做完至少得半年。DeskcommCRM 第一版很克制,只做了四个核心模块:客户档案管理:企业客户信息、联系人信息、自定义字段。跟进记录:每次沟通留时间戳、内容、下一步计划。任务提醒:设置下次跟进日期,系统到期主动提醒。数据看板:按销售人、按状态维度统计转化数据。至于电子签、呼叫中心、财务对接这些,我全部砍掉。原因是这些场景有更成熟的专业工具,硬塞进CRM反而臃肿。做项目不丢人的地方在于“暂时做不到”,丢人的是“什么都做,什么都糙”。第一版只解决数据集中和流程留痕两个问题,用起来顺手比功能多重要。3. 核心细节解析与实操要点3.1 客户模型的设计:一个企业多个联系人第一版我差点犯一个经典错误——把客户做成“一行一条”。“客户”在B2B业务里其实是个复合对象:一个企业客户下面可能有创始人、技术负责人、采购联系人,这三个人对项目的影响力完全不同,跟进对象也要区分。所以客户模型被拆成了两级结构:客户(企业)表:存公司名称、行业、规模、来源渠道、所属销售。联系人表:存姓名、职位、电话、微信、邮箱、以及他在决策链里的角色。实际操作中,销售跟进一个客户时,默认是选择“公司下的某个联系人”,而不是笼统地写“跟进某公司”。这样设计的好处很快显现出来:到了月底要盘点某个项目到底推进到哪一步时,不用再问“你上次聊的是哪位”,系统里一目了然。这个结构在技术上只是多建了一张表,但业务逻辑清晰了很多。如果你也要自建CRM,我强烈建议第一版就把“企业-联系人”的模型做对,否则后面数据多了再迁移,非常痛苦。3.2 跟进记录怎么写才不会变成流水账跟进记录是CRM系统里使用频率最高的功能,但也是产品设计里最容易被低估的。很多人以为一个文本框就够了,实际使用中会遇到两个问题:一是记录写得太长太散,二是内容是“我今天给客户打了个电话”这种没有信息量的废话。DeskcommCRM 的做法是给记录加了一个轻量结构:记录字段说明沟通对象关联到客户下的具体联系人沟通方式电话、微信、线下、邮件沟通结论客户当前态度、是否有新需求、有无异议下一步计划明确到时间和动作,如“周三前发新版报价”关联商机可选,绑定到对应的销售机会这样写下来的记录,即使换一个人接手,也能在30秒内看懂历史脉络。我还做了一个小约束:保存跟进记录时,“下一步计划”是必填项,可以为空选项“暂无”,但必须主动选一次。这看似是强迫症,实际上是逼销售养成“每次沟通都有目的”的习惯,对提升跟进质量很有帮助。3.3 字段和流程的灵活自定义团队成员后来提了很多个性化需求——市场部想看投放来源,财务想看回款节点,售前想看交付进度。如果每个需求都改表结构,系统很快会变成一个改不完的“怪物”。所以 DeskcommCRM 从第一版就预留了自定义字段模块,分成两类:基础字段:系统预置的名称、所属销售、状态、来源等不可删除字段。扩展字段:由管理员自行添加,类型包括单行文本、数字、日期、下拉框。新增一个扩展字段整个过程不超过一分钟,管理员后台操作,前端表单自动渲染。这个设计非常实用,比如后来加了一个“客户星级”字段,用来标记重点客户,整个团队就可以按星级筛选。另外在流程上,我只做了三个全局规则:新建客户后自动分配给指定销售;客户状态改为“已成交”时自动创建回款提醒;连续30天无跟进记录的客户自动进入“沉默客户”列表。这三个规则基本覆盖了团队90%的管理需求,并没有做复杂的审批流引擎。4. 技术选型与部署实现:让系统真正永久在线4.1 技术栈选择与数据模型落地的考量关于技术栈,我收到的最高频问题就是“用什么语言写的”。实际下来,真的不用迷信某个特定框架,关键是选团队熟悉的、能长期维护的。DeskcommCRM 我用了 Python 的 FastAPI 做后端接口,前端用的是 Vue3 + Element Plus,数据库选的 PostgreSQL。为什么选 FastAPI?主要是看中它的开发速度和自动生成接口文档,前后端联调时省掉了大量“接口对不对”的沟通成本。PostgreSQL 则是看中它对 JSON 字段的支持,自定义字段模块就是把扩展字段信息存成 JSON 结构,查询时用 PG 的 JSON 操作符,灵活性和性能都不错。数据模型里最重要的一张表是 customers,大概结构如下:CREATE TABLE customers ( id BIGSERIAL PRIMARY KEY, company_name VARCHAR(200) NOT NULL, industry VARCHAR(50), scale VARCHAR(20), source VARCHAR(50), owner_id BIGINT REFERENCES users(id), status VARCHAR(20) DEFAULT 'lead', custom_fields JSONB DEFAULT '{}', created_at TIMESTAMP DEFAULT now(), updated_at TIMESTAMP DEFAULT now() );联系人表则通过 customer_id 外键关联。这种“主表 + 自定JSON字段”的组合,既满足了结构化存储,又保留了弹性。4.2 部署架构与“永久在线”的真实含义网上搜“永久在线的CRM网站”,很多人以为是个玄学,好像搭好之后系统会自动永远活着。其实“永久在线”拆开来看,就是四个层面的工作:服务层面:保证进程不宕机,用 systemd 或 supervisor 守护,进程挂了自动拉起。网络层面:服务器有固定公网IP,域名解析稳定,端口映射正确。数据层面:数据库定时备份,至少做到每日备份 + 异地转储。证书层面:SSL证书自动续期,避免过期后浏览器报不安全。我实际部署用的是云服务器 + Nginx 反向代理。FastAPI 应用跑在 8000 端口,Nginx 监听 443,配置好域名证书后,团队直接通过 https://crm.example.com 访问。关键配置里有两件事很重要:第一是 systemd 服务文件。设了 Restart=always,进程意外退出时,两秒内自动拉起来。第二是定时任务备份数据库。我写了一个脚本,每天凌晨两点用 pg_dump 导出数据,保留最近30天的备份。0 2 * * * pg_dump -U crm_user deskcomm_crm | gzip > /backup/crm_$(date +%Y%m%d).sql.gz“永久在线”说到底是一种运维习惯,而不是某个一次性的部署操作。只要备份不断、进程守护开着、证书能自动续期,这个系统就可以稳定跑很长时间。4.3 私有部署 vs 托管SaaS的本质差异这里必须回应一个很多人搜过的问题:免费CRM与私人网站(自建系统)的区别到底在哪。百度知道上问这个问题的人很多,我自己用下来,核心差异基本可以总结成三点:第一是数据归属。SaaS免费版的数据存在对方的服务器上,哪怕平台承诺数据安全,规则和条款也是对方定的;自建系统数据在自己的服务器上,心理踏实,安全策略也由自己掌控。第二是功能边界。免费CRM的功能是“平台给你的能力”,你不能随意增加;私人自建网站则可以从零设计,也可以找到成熟开源项目二次开发。比如 DeskcommCRM 里的自定义字段、团队专属状态流,这类需求在SaaS免费版里基本做不到。第三是长期成本。免费套餐前期确实省钱,但团队扩大后往往被迫购买付费版,年费叠加起来不低;自建是一次性开发和持续运维成本,长期下来可控性更强。反过来说,如果没有技术人力,自建的维护成本也会变成负担,这也是很多团队选择付费SaaS的原因。5. 团队成员加入与权限控制:像飞鱼CRM那样邀请员工5.1 团队引入方式的设计飞鱼CRM的员工邀请功能做得不错,我试用的时候研究过它的交互逻辑。大致流程是管理员在后台生成一个邀请链接,员工点链接自助注册,然后绑定到指定部门或角色。DeskcommCRM 也参考了这个思路,但做了一点简化:管理员在“成员管理”页面点击“邀请成员”,系统生成一个一次性邀请链接。链接通过企业微信/钉钉发给新同事。新同事打开链接后,填入姓名、手机号、设置密码即可完成注册。注册成功后,管理员可以调整其角色和可访问的数据范围。这个设计避免了“管理员手动建账号、再告诉对方初始密码”的麻烦,也方便了后续的离职交接。离职人员只需在后台禁用账号,所有历史记录仍然保留在系统里,不会因为人走了数据就丢失。当然我也踩过坑。第一版做的邀请链接是永久有效的,后来发现存在安全隐患:链接一旦被转发出去,任何人都能注册进来。后来改成一次性链接,访问过一次就失效,需要新的邀请就重新生成,安全性高了很多。5.2 权限体系:销售只能看自己的数据吗权限体系是CRM项目里最容易出问题的部分。刚开始我以为“销售只能看自己的客户”就够了,实际上线后发现,这个逻辑太死板。举个例子:一个老销售休假,他的客户需要临时由同事代管;公司高管要看所有客户的总体情况,但不能看到具体报价细节;财务只需要看成交金额和回款日期。这些都是普通“按人隔离”解决不了的。DeskcommCRM 最终采用了一个比较灵活的权限模型:角色数据范围操作权限管理员全部客户增删改查、成员管理、字段管理销售主管本组客户 + 全部客户只读跟进记录、指派客户销售人员仅本人客户新增、编辑、跟进只读成员按指定范围仅查看,不能修改实现上,后端每个接口都会先做数据权限过滤:查询客户列表时,根据当前用户的角色拼接不同的 WHERE 条件。这个逻辑写在统一的地方,而不是散落在各个视图里,避免了“某个接口忘了鉴权”的低级漏洞。5.3 试用蝉鸣CRM之后的对比心得我在决定自建前,专门花了一个周末注册试用蝉鸣CRM。它的界面和交互确实做得不错,尤其对小型团队来说,拿来即用,一段时间内承载日常客户管理完全够。但试用时我也发现了几个“卡住”的地方:一是自定义字段的灵活性有上限,比如我需要的“多选下拉框 + 后续统计”功能,免费模式下不给开权限。二是数据的导出和迁移不是全量开放,想往出拿数据时还要走申请流程。三是对我这种有一定开发能力的人来说,蝉鸣这类成熟产品就像精装修好的公寓——住着舒服,但想砸墙改格局,物业不让。当然不是说自建就适合所有人。如果你团队没有技术人手,也没有精力维护部署,直接买专业SaaS或者用蝉鸣这类轻量工具,能省掉大量时间。自建的适应场景是:你已经有明确的技术负责人,并且对数据和功能的定制有长期需求。6. 实操过程与核心环节实现6.1 从注册到使用:DeskcommCRM的完整实操流程如果你只是团队里的业务人员,不关心技术实现,上手 DeskcommCRM 大约五分钟就能完成整个配置路径:第一步,管理员登录后台,进入“系统设置 -> 客户字段”,检查是否需要新增自定义字段。比如你们是做SaaS的,可能想看“客户当前使用的版本”,那就加一个下拉框,选项填“免费版/专业版/旗舰版”。第二步,进入“成员管理”,点击“邀请成员”,把链接发给团队同事。同事注册完成后,管理员把角色设置好。第三步,销售登录后,在“客户”页面点击“新建客户”,录入公司名称、来源、所属销售。这里系统会自动读取管理员配置的字段,形成表单。第四步,创建跟进记录。打开某个客户详情页,点击“添加跟进”,选择沟通方式,填写沟通结论和下一步计划,保存。如果设置了“下次跟进时间”,到了时间系统会在“今日待办”里提醒。第五步,查看数据看板。默认看板包含各状态客户数、本月新增线索、人均跟进次数三个指标。管理员可以自行调整展示的维度。整个流程走下来,团队只需要熟悉五个页面:客户列表、客户详情、跟进记录、待办列表、数据看板。对比SaaS动辄十几个菜单的设计,这个精简程度对内训成本很友好。6.2 关键页面和接口的开发笔记前端部分,客户列表页是使用频率最高的,也是团队第一印象的来源。我用 Element Plus 的 el-table 加自定义列显示,把“公司名称、行业、所属销售、状态、近期跟进时间”作为默认列,点击行可以进入详情。列表页还做了两个高频操作快捷入口:一是“一键新建跟进”,不用先打开详情页,直接在列表右侧就能弹出记录框;二是“快速筛选”,可以按我负责的、今日待跟进、七天内无跟进等条件直接过滤。这种细节在定位中帮了大忙。后端接口方面,最核心的是客户列表接口,关键代码如下:@router.get("/customers") async def list_customers( status: str = None, owner_id: int = None, keyword: str = None, user = Depends(get_current_user) ): query = select(Customer) if user.role == "sales": query = query.where(Customer.owner_id == user.id) elif user.role == "manager": # 主管可看本组成员 member_ids = await get_team_member_ids(user.team_id) query = query.where(Customer.owner_id.in_(member_ids)) if status: query = query.where(Customer.status == status) if keyword: query = query.where( Customer.company_name.ilike(f"%{keyword}%") ) result = await db.execute(query.order_by(Customer.updated_at.desc())) return result.scalars().all()注意这里的关键点:权限过滤不是写在每个业务函数里,而是抽成了一个 get_visible_customer_ids() 的工具函数,任何接口都可以复用。否则不同的开发者各写各的,非常容易出数据越权问题。6.3 数据看板与月底盘点的应用CRM上线后最有成就感的事,是每月月底的盘点效率大幅提升。以前销售主管要一个一个人问“这个月你名下几个客户在谈判阶段”,现在打开看板,所有数据都真实地摊在那里。看板第一版用的是最简单的统计SQL加前端ECharts柱状图。比如统计各状态客户数:SELECT status, COUNT(*) FROM customers GROUP BY status;这行简单的SQL就能支撑“销售漏斗分布”。后来又加了按人统计的跟进次数排行,逻辑类似,按 owner_id 分组 + COUNT(跟进记录)。这些统计在数据量数千条时性能完全没问题,根本不需要引入大数据组件。这套看板解决的最大问题,不是“看数字”,而是“对责任”。数据一旦透明,每个销售名下有多少个客户、有多少个超过两周没跟进,所有人一视同仁地暴露在系统里。它不像老板口头催办那么有压迫感,但效果反而更好,因为数字不会撒谎,也没法扯皮。7. 常见问题与排查技巧实录7.1 邀请链接失效了怎么办上文提到邀请链接我设置成一次性失效,但第一次上线时有人说“我链接打不开”。排查后发现原因不是链接失效,而是邮箱客户端自动换行,把 URL 截断成了两段。解决方法是把邀请链接缩短,同时在注册页加了一个手工输入邀请码的入口——链接里带的参数其实就是邀请码,用户手动复制那串码粘贴进去也能完成注册。后来我干脆在注册表单里同时支持“链接跳转”和“邀请码输入”两种方式,兼容各种网络环境和操作习惯。这个优化也提醒了我:设计线上协作功能时,一定不能假设所有用户都熟悉现代浏览器的所有操作。尽量多做一步容错,省去的麻烦远超想象。7.2 客户撞单问题怎么处理撞单是销售团队最常见的矛盾之一:同一个客户,A说是我先登记的联系方式,B说客户明确表示想跟我谈。DeskcommCRM 里我设计了一个简单的“客户认领”机制:客户新建时会有一个“所属销售”字段,默认是创建者本人。其他销售看到这个客户后,可以发起“申请认领”,进入待审核列表,由管理员或客户本人指定的主管来决定归属。这种做法谈不上多智能,但至少给了规则落地的空间。实际操作中,我们从系统上线的第二个星期就开始审视这个逻辑,最终定为:客户创建后15天内没有有效跟进记录,自动释放回“公海”,任何销售都可以重新领取。这个规则很残酷,但极大程度上清掉了“占着不干”的数据垃圾。我在IT行业干了这些年,一个很深的体会是:很多业务系统的问题不是需求不够清晰,而是没有把“异常情况”的规则提前定好。撞单处理、沉默释放、离职转派,这些看起来低频的场景,决定了CRM系统能不能长期稳定运行。7.3 系统变慢时如何定位瓶颈DeskcommCRM 运行到第三个月,客户数据量从几百条涨到几千条,加上跟进记录上万行,部分页面打开了明显变慢。最开始怀疑是服务器配置低,后来排查发现主要瓶颈在数据库查询:问题一是列表页默认全量加载所有客户数据,团队人一多,数据量涨了,页面就会卡。解决方法是后端加分页,前端只显示当前页20条。问题二是状态筛选时,没有给 status 字段建索引,导致全表扫描。补上索引后,查询时间从几百毫秒降到个位数毫秒。CREATE INDEX idx_customers_status ON customers(status); CREATE INDEX idx_customers_owner ON customers(owner_id);这类问题在数据量不大的初期根本感知不到,我建议大家从第一天就养成给常用过滤字段加索引的习惯,同时在前端强制分页,别等系统卡了再救火。7.4 数据备份恢复演练备份是大家都说重要、但很少真去验证的工作。我第一次做恢复演练就翻车了:备份脚本虽然每天在跑,但我试图恢复到新服务器时,才发现忘了备份 PostgreSQL 的角色和权限定义,结果导入数据后应用一直报数据库连接失败。后来我完善了备份策略,除了 pg_dump 导出数据,还会把角色创建语句和系统配置也记录在一个单独脚本里:-- 备份角色定义,以便恢复环境时先运行 CREATE ROLE crm_user WITH LOGIN PASSWORD 'strong_password'; GRANT ALL PRIVILEGES ON DATABASE deskcomm_crm TO crm_user;恢复流程也变得标准化:新服务器先安装好依赖,再创建角色和数据库,然后导入数据,最后启动应用。这个流程我每个月会完整演练一次,确保任何一台服务器出问题后,最多两个小时就能把系统恢复到最新状态。不要等数据丢了才去测备份。建议所有自建系统的管理者,立刻确认三件事:备份有没有自动执行、有没有异地留一份、有没有真正做过一次恢复演练。8. 写在最后的一些体会DeskcommCRM 这个名字一开始其实就是个临时仓库名,项目走到今天,已经成了团队日常运营不可或缺的一部分。回看整个过程,最深的感触不是技术上有多难,而是“想清楚为什么做”比“怎么做”重要得多。如果一开始没有盯住“数据归我、流程可改、团队能用”这三个目标,中途任何一个功能诱惑都可能把项目带偏。团队内部现在也养成了一个习惯:任何新业务需求出来后,第一反应不再是“开个Excel吧”,而是“去CRM里提个字段需求”。这个转变本身,就已经验证了这套自建系统的价值。如果你也在考虑自建CRM,或者正在SaaS和自建之间犹豫,希望这篇记录能给你一些参照。工具永远是为业务服务的,选哪条路不重要,重要的是系统里沉淀下来的数据,在关键时刻能真正帮团队做对决策。
1. 为什么要自己动手做一套CRM我是从2024年三季度开始动手做 DeskcommCRM 这个项目的。当时团队规模不大,销售、客户成功、售前加起来也就二十来号人,但客户信息已经散得没法看了:有人用Excel登记,有人直接记在微信备注里,还有几个老销售习惯把跟进记录写在纸质本子上。每次要拉一条客户生命周期的完整视图,基本等于“跨系统考古”。市面上的CRM不是没有,团队早期也用过几个免费版。但用下来就两个感觉:一是数据不在自己手里,哪怕只是把自己关在系统外面,也可能只是因为对方调整了免费策略;二是字段和流程是别人定死的,我要加一个“客户的行业细分”“预计回款周期”这类字段,要么升级付费,要么自己妥协去适配对方的产品逻辑。我后来跟同事开玩笑说,免费CRM最大的问题不是功能少,而是“规则由别人定”。这个痛点积累了大半年,最终还是决定自己搭一套,就是后来的 DeskcommCRM。这个项目解决了三个核心问题:客户数据集中管理,从前期的线索跟进到成交后的售后记录,全链路统一在一个系统里。销售协作有迹可循,谁什么时候联系了哪个客户、客户怎么反馈的,全程留痕,不再靠人肉同步。数据和系统归属自己,字段、流程、权限都可以按团队情况调整,也不再担心免费版突然限流或停服。这篇文章不是讲“CRM行业分析”的,而是把 DeskcommCRM 从设计、选型、实现到上线运营的完整过程拆开讲。如果你也在纠结“要不要自建CRM”,或者正在被各种SaaS收费策略折腾,可以参考下我的实操思路和踩坑记录。2. 项目整体设计与思路拆解2.1 先想清楚:CRM到底管的是什么很多人对CRM有误解,以为它就是个“通讯录Plus”或者“销售漏斗图表工具”。但真正跑过业务就会发现,CRM管理的核心其实是一串状态变化:客户从“陌生线索”变成“有初步意向”,再变成“进入商务谈判”,最后变成“已成交”或“已流失”。每个状态之间,都依赖销售人员的具体动作来驱动——打电话、发资料、约访、报价、催款。所以 DeskcommCRM 的第一步不是写代码,而是先把业务状态机画出来。当时我跟几个核心销售聊了一轮,梳理出团队实际推进项目时的动作序列,最终定了五个核心状态:线索:刚获取到联系方式,还没有有效沟通。跟进中:已建立联系,客户有比较明确的兴趣点。方案阶段:客户索要方案或报价,进入实质性比较。商务谈判:涉及合同条款、价格折扣、回款周期。成交/流失:要么签单,要么明确放弃。这个状态机是整个系统的骨架。我见过不少自制CRM失败的案例,原因往往不是技术不行,而是压根没理清业务状态,直接把Excel表格搬上线,结果只是“换了个地方填表”。2.2 为什么选择自建而不是继续用SaaS市面上不是没有好用的SaaS CRM,蝉鸣CRM、飞鱼CRM这些我都实际注册用过一段时间。蝉鸣的界面做得挺清爽,普通销售上手很快;飞鱼的员工邀请和看板设计也花过心思,团队用起来体验不差。但真正让我下定决心自建的,是三个很现实的问题。第一,定制成本。免费版只能改改字典项,一旦涉及“企业客户多联系人”这种结构,免费版基本无解。第二,数据所有权。客户信息是公司最核心的资产,放别人服务器上,对方系统出问题或者策略调整,我们没有任何话语权。第三,长期费用。免费版看着不要钱,但团队一超过10个人,很多功能就开始提示升级;算一下人均费用,五年总成本其实非常高。自建当然也有代价,主要是开发和维护的时间。但如果你团队里有人能写代码,或者愿意学一点,这套投入比长期被SaaS绑架要划算得多。DeskcommCRM 从立项到内测上线,实际只用了六周的业余时间,之后的迭代都是按需加功能。2.3 项目范围控制:哪些做、哪些不做自建系统最容易翻车的地方不是“不会做”,而是“什么都想做”。我曾经列过一张愿望清单:在线客户沟通、合同电子签、自动外呼、BI大屏……每一项看着都合理,但全做完至少得半年。DeskcommCRM 第一版很克制,只做了四个核心模块:客户档案管理:企业客户信息、联系人信息、自定义字段。跟进记录:每次沟通留时间戳、内容、下一步计划。任务提醒:设置下次跟进日期,系统到期主动提醒。数据看板:按销售人、按状态维度统计转化数据。至于电子签、呼叫中心、财务对接这些,我全部砍掉。原因是这些场景有更成熟的专业工具,硬塞进CRM反而臃肿。做项目不丢人的地方在于“暂时做不到”,丢人的是“什么都做,什么都糙”。第一版只解决数据集中和流程留痕两个问题,用起来顺手比功能多重要。3. 核心细节解析与实操要点3.1 客户模型的设计:一个企业多个联系人第一版我差点犯一个经典错误——把客户做成“一行一条”。“客户”在B2B业务里其实是个复合对象:一个企业客户下面可能有创始人、技术负责人、采购联系人,这三个人对项目的影响力完全不同,跟进对象也要区分。所以客户模型被拆成了两级结构:客户(企业)表:存公司名称、行业、规模、来源渠道、所属销售。联系人表:存姓名、职位、电话、微信、邮箱、以及他在决策链里的角色。实际操作中,销售跟进一个客户时,默认是选择“公司下的某个联系人”,而不是笼统地写“跟进某公司”。这样设计的好处很快显现出来:到了月底要盘点某个项目到底推进到哪一步时,不用再问“你上次聊的是哪位”,系统里一目了然。这个结构在技术上只是多建了一张表,但业务逻辑清晰了很多。如果你也要自建CRM,我强烈建议第一版就把“企业-联系人”的模型做对,否则后面数据多了再迁移,非常痛苦。3.2 跟进记录怎么写才不会变成流水账跟进记录是CRM系统里使用频率最高的功能,但也是产品设计里最容易被低估的。很多人以为一个文本框就够了,实际使用中会遇到两个问题:一是记录写得太长太散,二是内容是“我今天给客户打了个电话”这种没有信息量的废话。DeskcommCRM 的做法是给记录加了一个轻量结构:记录字段说明沟通对象关联到客户下的具体联系人沟通方式电话、微信、线下、邮件沟通结论客户当前态度、是否有新需求、有无异议下一步计划明确到时间和动作,如“周三前发新版报价”关联商机可选,绑定到对应的销售机会这样写下来的记录,即使换一个人接手,也能在30秒内看懂历史脉络。我还做了一个小约束:保存跟进记录时,“下一步计划”是必填项,可以为空选项“暂无”,但必须主动选一次。这看似是强迫症,实际上是逼销售养成“每次沟通都有目的”的习惯,对提升跟进质量很有帮助。3.3 字段和流程的灵活自定义团队成员后来提了很多个性化需求——市场部想看投放来源,财务想看回款节点,售前想看交付进度。如果每个需求都改表结构,系统很快会变成一个改不完的“怪物”。所以 DeskcommCRM 从第一版就预留了自定义字段模块,分成两类:基础字段:系统预置的名称、所属销售、状态、来源等不可删除字段。扩展字段:由管理员自行添加,类型包括单行文本、数字、日期、下拉框。新增一个扩展字段整个过程不超过一分钟,管理员后台操作,前端表单自动渲染。这个设计非常实用,比如后来加了一个“客户星级”字段,用来标记重点客户,整个团队就可以按星级筛选。另外在流程上,我只做了三个全局规则:新建客户后自动分配给指定销售;客户状态改为“已成交”时自动创建回款提醒;连续30天无跟进记录的客户自动进入“沉默客户”列表。这三个规则基本覆盖了团队90%的管理需求,并没有做复杂的审批流引擎。4. 技术选型与部署实现:让系统真正永久在线4.1 技术栈选择与数据模型落地的考量关于技术栈,我收到的最高频问题就是“用什么语言写的”。实际下来,真的不用迷信某个特定框架,关键是选团队熟悉的、能长期维护的。DeskcommCRM 我用了 Python 的 FastAPI 做后端接口,前端用的是 Vue3 + Element Plus,数据库选的 PostgreSQL。为什么选 FastAPI?主要是看中它的开发速度和自动生成接口文档,前后端联调时省掉了大量“接口对不对”的沟通成本。PostgreSQL 则是看中它对 JSON 字段的支持,自定义字段模块就是把扩展字段信息存成 JSON 结构,查询时用 PG 的 JSON 操作符,灵活性和性能都不错。数据模型里最重要的一张表是 customers,大概结构如下:CREATE TABLE customers ( id BIGSERIAL PRIMARY KEY, company_name VARCHAR(200) NOT NULL, industry VARCHAR(50), scale VARCHAR(20), source VARCHAR(50), owner_id BIGINT REFERENCES users(id), status VARCHAR(20) DEFAULT 'lead', custom_fields JSONB DEFAULT '{}', created_at TIMESTAMP DEFAULT now(), updated_at TIMESTAMP DEFAULT now() );联系人表则通过 customer_id 外键关联。这种“主表 + 自定JSON字段”的组合,既满足了结构化存储,又保留了弹性。4.2 部署架构与“永久在线”的真实含义网上搜“永久在线的CRM网站”,很多人以为是个玄学,好像搭好之后系统会自动永远活着。其实“永久在线”拆开来看,就是四个层面的工作:服务层面:保证进程不宕机,用 systemd 或 supervisor 守护,进程挂了自动拉起。网络层面:服务器有固定公网IP,域名解析稳定,端口映射正确。数据层面:数据库定时备份,至少做到每日备份 + 异地转储。证书层面:SSL证书自动续期,避免过期后浏览器报不安全。我实际部署用的是云服务器 + Nginx 反向代理。FastAPI 应用跑在 8000 端口,Nginx 监听 443,配置好域名证书后,团队直接通过 https://crm.example.com 访问。关键配置里有两件事很重要:第一是 systemd 服务文件。设了 Restart=always,进程意外退出时,两秒内自动拉起来。第二是定时任务备份数据库。我写了一个脚本,每天凌晨两点用 pg_dump 导出数据,保留最近30天的备份。0 2 * * * pg_dump -U crm_user deskcomm_crm | gzip > /backup/crm_$(date +%Y%m%d).sql.gz“永久在线”说到底是一种运维习惯,而不是某个一次性的部署操作。只要备份不断、进程守护开着、证书能自动续期,这个系统就可以稳定跑很长时间。4.3 私有部署 vs 托管SaaS的本质差异这里必须回应一个很多人搜过的问题:免费CRM与私人网站(自建系统)的区别到底在哪。百度知道上问这个问题的人很多,我自己用下来,核心差异基本可以总结成三点:第一是数据归属。SaaS免费版的数据存在对方的服务器上,哪怕平台承诺数据安全,规则和条款也是对方定的;自建系统数据在自己的服务器上,心理踏实,安全策略也由自己掌控。第二是功能边界。免费CRM的功能是“平台给你的能力”,你不能随意增加;私人自建网站则可以从零设计,也可以找到成熟开源项目二次开发。比如 DeskcommCRM 里的自定义字段、团队专属状态流,这类需求在SaaS免费版里基本做不到。第三是长期成本。免费套餐前期确实省钱,但团队扩大后往往被迫购买付费版,年费叠加起来不低;自建是一次性开发和持续运维成本,长期下来可控性更强。反过来说,如果没有技术人力,自建的维护成本也会变成负担,这也是很多团队选择付费SaaS的原因。5. 团队成员加入与权限控制:像飞鱼CRM那样邀请员工5.1 团队引入方式的设计飞鱼CRM的员工邀请功能做得不错,我试用的时候研究过它的交互逻辑。大致流程是管理员在后台生成一个邀请链接,员工点链接自助注册,然后绑定到指定部门或角色。DeskcommCRM 也参考了这个思路,但做了一点简化:管理员在“成员管理”页面点击“邀请成员”,系统生成一个一次性邀请链接。链接通过企业微信/钉钉发给新同事。新同事打开链接后,填入姓名、手机号、设置密码即可完成注册。注册成功后,管理员可以调整其角色和可访问的数据范围。这个设计避免了“管理员手动建账号、再告诉对方初始密码”的麻烦,也方便了后续的离职交接。离职人员只需在后台禁用账号,所有历史记录仍然保留在系统里,不会因为人走了数据就丢失。当然我也踩过坑。第一版做的邀请链接是永久有效的,后来发现存在安全隐患:链接一旦被转发出去,任何人都能注册进来。后来改成一次性链接,访问过一次就失效,需要新的邀请就重新生成,安全性高了很多。5.2 权限体系:销售只能看自己的数据吗权限体系是CRM项目里最容易出问题的部分。刚开始我以为“销售只能看自己的客户”就够了,实际上线后发现,这个逻辑太死板。举个例子:一个老销售休假,他的客户需要临时由同事代管;公司高管要看所有客户的总体情况,但不能看到具体报价细节;财务只需要看成交金额和回款日期。这些都是普通“按人隔离”解决不了的。DeskcommCRM 最终采用了一个比较灵活的权限模型:角色数据范围操作权限管理员全部客户增删改查、成员管理、字段管理销售主管本组客户 + 全部客户只读跟进记录、指派客户销售人员仅本人客户新增、编辑、跟进只读成员按指定范围仅查看,不能修改实现上,后端每个接口都会先做数据权限过滤:查询客户列表时,根据当前用户的角色拼接不同的 WHERE 条件。这个逻辑写在统一的地方,而不是散落在各个视图里,避免了“某个接口忘了鉴权”的低级漏洞。5.3 试用蝉鸣CRM之后的对比心得我在决定自建前,专门花了一个周末注册试用蝉鸣CRM。它的界面和交互确实做得不错,尤其对小型团队来说,拿来即用,一段时间内承载日常客户管理完全够。但试用时我也发现了几个“卡住”的地方:一是自定义字段的灵活性有上限,比如我需要的“多选下拉框 + 后续统计”功能,免费模式下不给开权限。二是数据的导出和迁移不是全量开放,想往出拿数据时还要走申请流程。三是对我这种有一定开发能力的人来说,蝉鸣这类成熟产品就像精装修好的公寓——住着舒服,但想砸墙改格局,物业不让。当然不是说自建就适合所有人。如果你团队没有技术人手,也没有精力维护部署,直接买专业SaaS或者用蝉鸣这类轻量工具,能省掉大量时间。自建的适应场景是:你已经有明确的技术负责人,并且对数据和功能的定制有长期需求。6. 实操过程与核心环节实现6.1 从注册到使用:DeskcommCRM的完整实操流程如果你只是团队里的业务人员,不关心技术实现,上手 DeskcommCRM 大约五分钟就能完成整个配置路径:第一步,管理员登录后台,进入“系统设置 -> 客户字段”,检查是否需要新增自定义字段。比如你们是做SaaS的,可能想看“客户当前使用的版本”,那就加一个下拉框,选项填“免费版/专业版/旗舰版”。第二步,进入“成员管理”,点击“邀请成员”,把链接发给团队同事。同事注册完成后,管理员把角色设置好。第三步,销售登录后,在“客户”页面点击“新建客户”,录入公司名称、来源、所属销售。这里系统会自动读取管理员配置的字段,形成表单。第四步,创建跟进记录。打开某个客户详情页,点击“添加跟进”,选择沟通方式,填写沟通结论和下一步计划,保存。如果设置了“下次跟进时间”,到了时间系统会在“今日待办”里提醒。第五步,查看数据看板。默认看板包含各状态客户数、本月新增线索、人均跟进次数三个指标。管理员可以自行调整展示的维度。整个流程走下来,团队只需要熟悉五个页面:客户列表、客户详情、跟进记录、待办列表、数据看板。对比SaaS动辄十几个菜单的设计,这个精简程度对内训成本很友好。6.2 关键页面和接口的开发笔记前端部分,客户列表页是使用频率最高的,也是团队第一印象的来源。我用 Element Plus 的 el-table 加自定义列显示,把“公司名称、行业、所属销售、状态、近期跟进时间”作为默认列,点击行可以进入详情。列表页还做了两个高频操作快捷入口:一是“一键新建跟进”,不用先打开详情页,直接在列表右侧就能弹出记录框;二是“快速筛选”,可以按我负责的、今日待跟进、七天内无跟进等条件直接过滤。这种细节在定位中帮了大忙。后端接口方面,最核心的是客户列表接口,关键代码如下:@router.get("/customers") async def list_customers( status: str = None, owner_id: int = None, keyword: str = None, user = Depends(get_current_user) ): query = select(Customer) if user.role == "sales": query = query.where(Customer.owner_id == user.id) elif user.role == "manager": # 主管可看本组成员 member_ids = await get_team_member_ids(user.team_id) query = query.where(Customer.owner_id.in_(member_ids)) if status: query = query.where(Customer.status == status) if keyword: query = query.where( Customer.company_name.ilike(f"%{keyword}%") ) result = await db.execute(query.order_by(Customer.updated_at.desc())) return result.scalars().all()注意这里的关键点:权限过滤不是写在每个业务函数里,而是抽成了一个 get_visible_customer_ids() 的工具函数,任何接口都可以复用。否则不同的开发者各写各的,非常容易出数据越权问题。6.3 数据看板与月底盘点的应用CRM上线后最有成就感的事,是每月月底的盘点效率大幅提升。以前销售主管要一个一个人问“这个月你名下几个客户在谈判阶段”,现在打开看板,所有数据都真实地摊在那里。看板第一版用的是最简单的统计SQL加前端ECharts柱状图。比如统计各状态客户数:SELECT status, COUNT(*) FROM customers GROUP BY status;这行简单的SQL就能支撑“销售漏斗分布”。后来又加了按人统计的跟进次数排行,逻辑类似,按 owner_id 分组 + COUNT(跟进记录)。这些统计在数据量数千条时性能完全没问题,根本不需要引入大数据组件。这套看板解决的最大问题,不是“看数字”,而是“对责任”。数据一旦透明,每个销售名下有多少个客户、有多少个超过两周没跟进,所有人一视同仁地暴露在系统里。它不像老板口头催办那么有压迫感,但效果反而更好,因为数字不会撒谎,也没法扯皮。7. 常见问题与排查技巧实录7.1 邀请链接失效了怎么办上文提到邀请链接我设置成一次性失效,但第一次上线时有人说“我链接打不开”。排查后发现原因不是链接失效,而是邮箱客户端自动换行,把 URL 截断成了两段。解决方法是把邀请链接缩短,同时在注册页加了一个手工输入邀请码的入口——链接里带的参数其实就是邀请码,用户手动复制那串码粘贴进去也能完成注册。后来我干脆在注册表单里同时支持“链接跳转”和“邀请码输入”两种方式,兼容各种网络环境和操作习惯。这个优化也提醒了我:设计线上协作功能时,一定不能假设所有用户都熟悉现代浏览器的所有操作。尽量多做一步容错,省去的麻烦远超想象。7.2 客户撞单问题怎么处理撞单是销售团队最常见的矛盾之一:同一个客户,A说是我先登记的联系方式,B说客户明确表示想跟我谈。DeskcommCRM 里我设计了一个简单的“客户认领”机制:客户新建时会有一个“所属销售”字段,默认是创建者本人。其他销售看到这个客户后,可以发起“申请认领”,进入待审核列表,由管理员或客户本人指定的主管来决定归属。这种做法谈不上多智能,但至少给了规则落地的空间。实际操作中,我们从系统上线的第二个星期就开始审视这个逻辑,最终定为:客户创建后15天内没有有效跟进记录,自动释放回“公海”,任何销售都可以重新领取。这个规则很残酷,但极大程度上清掉了“占着不干”的数据垃圾。我在IT行业干了这些年,一个很深的体会是:很多业务系统的问题不是需求不够清晰,而是没有把“异常情况”的规则提前定好。撞单处理、沉默释放、离职转派,这些看起来低频的场景,决定了CRM系统能不能长期稳定运行。7.3 系统变慢时如何定位瓶颈DeskcommCRM 运行到第三个月,客户数据量从几百条涨到几千条,加上跟进记录上万行,部分页面打开了明显变慢。最开始怀疑是服务器配置低,后来排查发现主要瓶颈在数据库查询:问题一是列表页默认全量加载所有客户数据,团队人一多,数据量涨了,页面就会卡。解决方法是后端加分页,前端只显示当前页20条。问题二是状态筛选时,没有给 status 字段建索引,导致全表扫描。补上索引后,查询时间从几百毫秒降到个位数毫秒。CREATE INDEX idx_customers_status ON customers(status); CREATE INDEX idx_customers_owner ON customers(owner_id);这类问题在数据量不大的初期根本感知不到,我建议大家从第一天就养成给常用过滤字段加索引的习惯,同时在前端强制分页,别等系统卡了再救火。7.4 数据备份恢复演练备份是大家都说重要、但很少真去验证的工作。我第一次做恢复演练就翻车了:备份脚本虽然每天在跑,但我试图恢复到新服务器时,才发现忘了备份 PostgreSQL 的角色和权限定义,结果导入数据后应用一直报数据库连接失败。后来我完善了备份策略,除了 pg_dump 导出数据,还会把角色创建语句和系统配置也记录在一个单独脚本里:-- 备份角色定义,以便恢复环境时先运行 CREATE ROLE crm_user WITH LOGIN PASSWORD 'strong_password'; GRANT ALL PRIVILEGES ON DATABASE deskcomm_crm TO crm_user;恢复流程也变得标准化:新服务器先安装好依赖,再创建角色和数据库,然后导入数据,最后启动应用。这个流程我每个月会完整演练一次,确保任何一台服务器出问题后,最多两个小时就能把系统恢复到最新状态。不要等数据丢了才去测备份。建议所有自建系统的管理者,立刻确认三件事:备份有没有自动执行、有没有异地留一份、有没有真正做过一次恢复演练。8. 写在最后的一些体会DeskcommCRM 这个名字一开始其实就是个临时仓库名,项目走到今天,已经成了团队日常运营不可或缺的一部分。回看整个过程,最深的感触不是技术上有多难,而是“想清楚为什么做”比“怎么做”重要得多。如果一开始没有盯住“数据归我、流程可改、团队能用”这三个目标,中途任何一个功能诱惑都可能把项目带偏。团队内部现在也养成了一个习惯:任何新业务需求出来后,第一反应不再是“开个Excel吧”,而是“去CRM里提个字段需求”。这个转变本身,就已经验证了这套自建系统的价值。如果你也在考虑自建CRM,或者正在SaaS和自建之间犹豫,希望这篇记录能给你一些参照。工具永远是为业务服务的,选哪条路不重要,重要的是系统里沉淀下来的数据,在关键时刻能真正帮团队做对决策。