十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

从0到1自建轻量级Web CRM:PHP+SQLite+Docker实践

从0到1自建轻量级Web CRM:PHP+SQLite+Docker实践 就是今年年初的事。我们团队从三个人涨到八个人客户资料还躺在各个人的微信聊天记录、Excel 表格和邮箱签名里。谁跟进过哪个客户、上次聊到哪、承诺过什么价格全靠开会时候互相“考古”。我一开始想直接上现成的 SaaS 平台后来算了一笔账又对比了一番免费版和自建的差别最终决定自己动手写一个 CRM认准的就是长期能用、数据掌握在自己手里、按我们自己的流程来。这个项目我命名为 DeskcommCRM——一个基于 Web、可以永久在线访问、专门为“销售协作”设计的轻量级客户管理系统。写这篇文章的目的很直接把 DeskcommCRM 从需求分析、技术选型到部署上线的完整过程拆开讲清楚。它适合三类人一是正在用 Excel 管客户、团队又不大、想找一个轻量替代方案的人二是用过免费 CRM 但被各种功能限制和收费墙搞烦了的人三是对自建系统有兴趣、想了解完整落地过程的技术朋友。这篇文章里没有什么花哨的架构全是踩过坑之后沉淀下来的实际方案。1. 为什么我会动手写一个 CRM从Excel到SaaS的反复折腾1.1 小团队用 SaaS 平台 CRM 的真实痛点现在市面上的 CRM 产品其实非常多从国际大厂到国内各种垂直方案随便一搜就是几十款。但对一个只有几个人、十几个人的小团队来说用大而全的 SaaS CRM 真的香吗我自己实际用下来的体验是功能太多配置太复杂费用还不低。先说费用。主流 SaaS 类 CRM 基本按“坐席数”收费一个用户一年下来少则几百、多则几千。我们现在八个人按一年算下来是一笔不小的运营支出。这还只是基础版如果想要更细的权限控制、更完整的报表、更多的 API 配额就要加钱。对于刚过生存期的团队来说每一分钱都该花在刀刃上CRM 这种内部工具能省就该省。再说功能复杂度。给一个八人团队用需要的就是客户记录、跟进日志、任务提醒、简单的销售漏斗很多 SaaS 平台把这些功能做得非常深——字段可以自定义一百多个自动化流程甚至可以搭出花来。有时候想改一个字段的显示名称都要在配置中心找半天。这中间的“认知成本”和“维护成本”小团队根本承受不起。最后是数据流动问题。SaaS 平台的数据迁移非常麻烦想导出完整的历史跟进记录、附件和备注通常不是一键完成的而且导出的格式往往不符合个人后续处理习惯。长久下来数据就被“绑”在平台里了。1.2 免费 CRM 和私人自建网站的本质区别很多人问既然不想付费那用免费的 CRM 不就行了这里必须讲清楚一个经常被忽略的关键区别免费 CRM 和私人自建网站看着都是“不要钱”本质完全不同。免费 CRM 通常是 SaaS 厂商的一个获客手段。它“免费”的只是基础功能等你数据量上来、团队人数增加之后一定会撞到各种限制——每个账号的存储空间、附件大小、自定义字段数量、导出权限、API 调用频率全部卡得死死的。更关键的是免费版往往不承诺服务可用性也没有完整的备份保障一旦服务商调整策略或者下线免费版本里面的数据就是真正的“一夜回到解放前”。而自己部署一个私人 CRM 网站虽然表面上也需要花时间维护但它本质上是把数据主权握在自己手里。数据存在自己的服务器里备份自己控制导出格式自己定所有功能完全属于自己。免费 CRM 的核心目标是把你变成付费会员自建系统的核心目标是把工具变成日常工作的自然延伸这两种心态带来的产品体验天差地别。DeskcommCRM 的定位就是走“私人自建永久在线”这条路线。我用一台性价比不错的云服务器放核心数据通过域名访问保证在任何有网络的地方都能进入系统它不是装在某个人的电脑上不是局域网工具而是真正的 Web 服务。1.3 触发我动手的三个瞬间真正推动我动手的其实是三个很具体的瞬间。第一个瞬间是有一次开周会我发现同一个客户被三个人“重复跟进”了。A 加了客户微信B 在邮件里跟客户报过价C 又打了个电话给客户。客户对我们的印象就是“你们公司到底谁说了算”。这件事让我意识到客户信息如果散落在个人工具里团队协作就是一句空话。第二个瞬间是月底复盘的时候。我让大家把自己跟进过的客户情况发到群里结果收到的消息五花八门有人发了个截图有人发了一段文字还有人直接语音说了三十秒。这些信息很快就会沉底下个月再想追溯什么都找不到。第三个瞬间是我自己尝试用一个免费 CRM 用了一个月结果被广告轰炸和升级提示搞得头大。当时我就想与其在别人的规则里找缝隙不如给自己做一个完全贴合需求的工具。DeskcommCRM 这个名字就是“Desk Communication CRM”的缩写我希望它像一个摆在桌面上的通讯总台所有客户沟通记录在这里汇合、流转、沉淀。2. DeskcommCRM 的定位与核心设计2.1 一套“永久在线”的 Web CRM 意味着什么DeskcommCRM 最基本的设定就是必须是 Web 系统换句话说它持续运行在服务器上不限时间、不限设备通过浏览器访问。为什么坚持这个设定因为销售的工作场景是流动的。在办公室用电脑外出见客户要用手机回家之后还可能临时打开平板看一眼第二天要拜访客户的背景资料。如果 CRM 是一个本地安装的软件数据只在一台电脑上或者只能在某个固定网络里访问就等于把信息锁在了一个笼子里。Web 化的好处在于只要有浏览器、有网络就能登录系统客户资料跟着人走而不是跟着设备走。当然“永久在线”也意味着必须考虑服务的稳定性和可用性。我为 DeskcommCRM 选择了容器化部署并通过重启策略保证服务在服务器重启后自动拉起来。数据库文件定期自动备份到异地存储空间防止服务器故障导致数据丢失。这些细节后面会拆开讲。2.2 技术选型为什么是 PHP SQLite Docker技术选型往往是最容易被“炫技心”带偏的地方。这个项目启动时我给自己定了一个原则能简单就简单能稳定就稳定学习成本和维护成本必须压到最低。基于这个原则最终选了 PHP SQLite Docker 这套组合。选择 PHP 并不是因为它多先进而是因为它“太成熟了”。我本人对 PHP 非常熟悉而且这个语言的生态里做 Web 应用、处理表单、做用户认证、连接数据库都有非常成熟的方案。它不像一些新框架那样需要搭配各种中间件和复杂配置一个 PHP-FPM 环境就能跑起来。对于中等规模团队的数据管理场景PHP 的并发处理能力绰绰有余。SQLite 的选择很多人会觉得有点意外。毕竟大部分 Web 项目都会用 MySQL 或 PostgreSQL。但我的考量是这样的作为一个 CRM核心数据量正常情况下不会超过几十万条记录SQLite 单文件数据库完全可以用最小的系统资源处理这种量级的读写。它最大的好处是“零运维”——不需要单独维护数据库服务不需要考虑数据库账号密码泄露的风险整个数据库就是一个文件备份和迁移直接复制文件即可。很多人担心 SQLite 的并发写能力但实际业务中CRM 里并发的写操作非常少顶多是几个人同时更新客户记录SQLite 的锁定机制完全够用。Docker 在这里起到的作用非常关键。它把所有依赖打包成一个镜像无论在哪台 Linux 服务器上都能一键启动。我选 Docker 的另一个原因是它让“环境一致性”变得很简单——本地调试过的版本直接打包发到生产服务器不会出现“我本地能跑服务器上跑不起来”这种经典事故。2.3 数据模型的克制每个模块都只留必要字段做内部工具的第二个诱惑是“把功能做得无限全”。我在设计 DeskcommCRM 的数据模型时始终坚持一个原则克制。每个模块只保留销售业务中真正频繁使用的字段而不是把潜在的、可能用得上的字段全部堆上去。客户表的设计是这样的字段名类型说明id整数客户主键自增name文本客户名称必填company文本所属公司选填phone文本手机号必填wechat文本微信号选填source文本客户来源选填level整数客户等级1-5默认3status整数当前状态1-线索 2-跟进中 3-已成交 4-已流失owner_id整数负责人的用户 IDremark长文本备注信息created_at日期时间创建时间updated_at日期时间更新时间这样的字段设计保证了录入一个客户不需要耗时太长同时又覆盖了销售日常最关心的核心信息这个人是谁、来自哪、现在什么状态、谁在负责、最近有没有跟进。线索管理的逻辑也遵循同样的思路。线索表有客户ID、来源渠道、线索描述、分配给谁、分配时间、状态。它和客户表是分开的因为线索并不是正式客户可能还处于“待验证”的阶段。当一条线索经过沟通验证、确认有真实业务需求后可以一键转为正式客户。这个“线索→客户”的流转过程在销售业务里是一个非常经典的漏斗模型。3. 客户线索流转从录入到成交的完整闭环3.1 线索池与客户池的设定逻辑如果把销售过程比作一个水龙头线索就是进水口客户是蓄水池。我在 DeskcommCRM 里把线索和客户分成两个独立模块用“线索池”和“客户池”的概念来组织数据流。线索池里装的是什么是那些“有潜在意向但还没有确认需求”的联系方式。比如在行业群里看到有人问有没有相关产品我记下了他的联系方式这就是一个线索。通过活动登记表收集来的名片信息也是一个线索。这些线索数量多、质量参差不齐如果直接进入客户池会稀释正式客户的管理密度。当一条线索经过电话沟通或微信交流确认对方确实有业务需求、预算范围也基本匹配之后就可以把它从线索池“转入”客户池。这个操作在系统里是一个按钮转成客户。转入时可以带上线索来源和初步沟通记录这样这张客户卡片从第一天就有完整的历史。客户池里的内容才是日常经营的核心资产。每个客户都有唯一负责人有明确的跟进状态有历次沟通记录。线索池和客户池的分离让团队对“这个月新增了多少潜在机会”和“这个月真正在跟进的客户有多少”一目了然不会把一堆未验证的线索和有效客户混在一起。3.2 跟进记录的“时间轴”设计CRM 系统里最容易烂尾的模块就是跟进记录。很多产品的跟进记录做成了一张张零散的单据每次想回看跟某位客户从第一次接触到现在的完整历程需要翻好几个页面。我在做 DeskcommCRM 的跟进记录模块时专门设计成了“时间轴”模式——客户详情页里往下拉就是一条按时间倒序排列的沟通历史流。时间轴上的每一条记录包括本次沟通方式电话、微信、邮件、面访、沟通摘要、下次计划动作、创建人和创建时间。这样的设计有一个天然的好处任何人接手一个客户打开页面拉一下时间轴就能在五分钟之内知道这个客户的历史全貌——什么时间点建立了联系中间因为什么原因冷过一段最近一次沟通聊到什么关键信息。这种信息继承能力对于团队协作、对于减少重复沟通帮助特别大。做时间轴的时候有一个小细节值得提一下。我约束了“追加记录”的逻辑——跟进的备注只能新增尽量不要修改历史记录。因为销售过程中原始记录的价值就在于“当时的判断和事实”如果允许随意修改历史过了一段时间后很难说清楚某些决策到底是基于什么做出的。做法是在后续记录里补充修正改动之前的记录只会让数据失去可信度。3.3 公海与回收机制的实现思路客户池运行一段时间之后一定会出现一个现象某些客户被某个销售拿了很久但始终没有推进既不成交也不流失占着位置不动。如果不加干预这些“僵尸客户”就白白烂在个人手里整个团队的客户流转效率会越来越低。我参考了行业内 CRM 的常见做法在 DeskcommCRM 里设计了一个“公海”机制当一条客户记录超过设定的 N 天默认30天可按团队情况调整没有任何跟进记录更新时系统自动释放该客户的归属权让这条记录回到一个共享的“公海”区域。在这个区域里其他销售可以看到并申请认领该客户认领之后重新计算跟进时效。这个机制实现起来并不复杂核心就是每次查看用户列表时执行一次“过期检查”把 updated_at 超过时限且状态不是“已成交”的客户标记为公海状态。设置回收时间之前我在团队里做过一轮小范围的征求意见最终把默认时限定为30天因为销售产品的决策周期比较长如果把时限压得太短反而会影响那些需要长时间培育的大客户。公海机制最大的价值不是惩罚跟进不积极的销售而是保证客户资源始终在流动状态。没有这个机制的时候客户数据是“静态资产”存在就是存在不存在就是不存在有了公海之后客户数据变成了“流动资产”只有持续经营才会一直留在自己手里。这种机制倒逼了每个销售都保持对客户的关注度而不是一次性获得了联系方式之后就再也不管不问。4. 团队协作如何邀请员工加入并保持数据边界清晰4.1 邀请员工的完整流程设计DeskcommCRM 是给团队用的系统所以“邀请员工”是必不可少的功能。很多人第一次接触这套系统时都会问一个问题怎么把同事加进来这里我把邀请流程的完整设计讲清楚。管理员登录后台之后进入“团队成员”页面点击“邀请成员”按钮系统会生成一个带有随机令牌的邀请链接。把这个链接发给要加入的同事对方打开链接后填写姓名、邮箱、设置登录密码完成注册后即自动关联到团队。在生成邀请链接时管理员可以预设一个新成员的角色销售或管理员后面会细讲也可以先不指定等对方加入后再调整。邀请链接默认 72 小时有效超过时间自动失效。这样做是防止链接在传播过程中被无关人员拿到延长有效期又会让链接的暴露窗口变大。这个流程和很多 SaaS 产品的邀请机制类似但它的关键不在于流程多新奇而在于每个环节都保留审计痕迹——谁在什么时间向哪个邮箱发送了邀请、对方何时完成注册、邀请链接何时失效都有日志记录。将来如果需要排查异常登录或内部权限问题这些日志就是最基本的数据依据。4.2 角色、权限与数据隔离的落地做法团队协作最大的隐患是权责不清。销售之间应该能看见彼此的客户吗普通销售能删除客户数据吗能修改别人的跟进记录吗如果没有明确的权限划分系统用久了必然乱。DeskcommCRM 里设计了三个内置角色管理员、销售主管、普通销售。权限设计如下表操作权限管理员销售主管普通销售查看全部客户是是否仅本人编辑/删除客户是是仅自己的分配/转移客户是是否管理团队成员是部分否查看团队数据报表是是仅自己的删除跟进记录是是仅自己的数据隔离的逻辑是 DeskcommCRM 里比较核心的一环。普通销售登录系统之后只能看到自己名下和公海里的客户。销售主管可以看到整个团队的客户列表但对别人的客户默认只有只读权限需要修改时必须走“转移客户”的流程。管理员对全站数据拥有完全控制权。这个设计的出发点很简单既要让管理岗有全局视角又要让一线销售在业务上保留一块自己说了算的“自留地”。如果所有数据全部透明销售会产生“我辛苦跟进的客户别人随时能看见”的顾虑在系统里填写信息就会有所保留。这个平衡点我先用“归属角色”的双层模型实现了最稳妥的基础版本。4.3 离职交接与数据归属策略团队人员变动是任何企业都逃不掉的事情。每次有人离职如何快速、顺畅地把客户交接给其他同事是一个很现实的痛点。如果没有系统的支持离职交接往往变成一场混乱的补课——新接手的人不知道哪些客户在跟进不知道每单谈到什么程度了。DeskcommCRM 专门设计了“一键交接”功能管理员把离职成员名下的所有客户和线索一次性转移给指定的另一位成员。交接过程同时会把时间轴上的历史记录完整保留——新接手的人能清楚地看到这位客户之前和哪位同事聊过什么、价格谈到什么程度、下一步计划是什么。在交接策略上我做了两个分支如果团队决定这些客户暂时不需要人接手就直接放入公海让有能力、有精力的同事主动认领如果已经有了明确的接手人就直接归属到个人名下。这两个分支在界面上对应两个按钮”放入公海“和”转移给成员“管理员按实际情况点选即可。一个细节是交接完成后系统会给被交接的客户列表打上一个“新接手”的标记持续7天。这提醒接手人这批客户不是你长期在跟的需要尽快熟悉、尽快建立联系。团队里有了这个机制人员流动导致的客户流失率就大大降低了。5. 数据安全与容灾不想让客户数据毁于一旦5.1 备份策略多层级、异地、可恢复说到自建系统最让人担心的就是数据安全。我自己以前也有过惨痛教训有一回在服务器上改配置不小心覆盖了一个重要数据库文件还好当时做了一个手动备份否则几十个客户的资料就永远找不回来了。从那次之后我把备份策略提到了整个项目的第一优先级。DeskcommCRM 目前运行在云服务器上数据库是一个 SQLite 文件。我的备份方案分三层第一层是实时本地备份。服务器上跑了一个定时任务每 6 小时把 SQLite 文件复制一份到服务器的另一个磁盘目录并保留最近 7 天的版本用 date 命令给文件命名方便按时间点找回。第二层是异地备份。服务器上的备份文件通过 crontab 定时同步到对象存储服务例如阿里云 OSS / 腾讯云 COS 这类服务这个节奏是每天凌晨 3 点执行一次。为什么要异地因为服务器本身可能遇到硬件故障、被入侵或者被运营商清退等极端情况只有数据同时存在于两个物理位置才能谈得上“容灾”。第三层是本地冷备。每周手动静默下载一次最新的数据库文件存到本地电脑和移动硬盘。这个动作虽然原始但也最可靠——无论云服务商出了什么问题我手里永远有一份完整的可以随时恢复。有一次我实际演练过恢复流程在另一台全新的服务器上部署 DeskcommCRM 的 Docker 镜像然后把备份的 SQLite 文件放进去重启容器整个系统就回来了数据一条不差。整个恢复过程不到十分钟这让全团队都安心了不少。5.2 登录安全、敏感字段与内部风险控制数据安全不只是防外部黑客还要防内部泄露。对于 CRM 这样一个客户数据高度集中的系统我给 DeskcommCRM 做了三重安全措施。第一重是登录安全。系统使用密码加盐存储。每个用户的密码在注册时随机生成一段 salt和密码拼接之后做哈希再入库这样即使数据库文件泄露密码原文也无法通过反向查询直接拿到。后台还支持“登录失败次数限制”同一个账号连续输错 5 次密码之后账号锁定 30 分钟防止暴力破解。第二重是权限安全。所有 API 请求都需要携带会话凭证且每个接口在服务端都会校验当前用户的角色和资源归属。这部分逻辑虽然增加了一些开发量但它的存在保证了用户不能通过直接构造 URL 等方式越权访问别人的客户数据。前后端分离架构下如果只做前端路由拦截而忽略后端鉴权是很容易出事的。第三重是操作审计。每次登录、每次删除客户、每次转移客户系统都会自动记录操作人、操作时间和详情。做这一步的初衷不是为了监控员工而是为了在有争议的时候能快速查清事实。这个审计日志在查“谁动了我的客户”这件事上发挥了巨大作用后来还帮我发现过一次内部误操作。6. 部署实录与踩坑记录6.1 如何把 DeskcommCRM 跑起来一朵云 一个域名说清楚了设计和实现讲讲怎么落地部署。因为项目选择了 Docker 打包整个部署过程被简化到了很小的操作量。准备一台 Linux 云服务器2 核 4G 配置起步系统推荐 Ubuntu 22.04。把代码构建成 Docker 镜像推送镜像仓库在服务器上写好一个 docker-compose.yml内容大致就是拉起一个 Web 服务容器和一份本地数据盘挂载。启动之后用 Nginx 做反向代理把域名指到对应端口再申请一份免费的 HTTPS 证书完成加密访问。整个过程一次跑通后面所有更新就变成了“拉新镜像、重建容器”两个动作。手机访问方面系统本身是响应式布局手机浏览器直接打开域名就有一个可用的界面。我一开始想得很复杂想给团队配一个专用的手机 App后来发现完全没有必要——普通销售大部分时间都是在电脑上维护数据手机上主要就是查询客户信息和快速记录跟进浏览器版已经满足需求省掉了上架应用商店的各种流程和成本。6.2 部署和升级中踩过的坑任何项目都有坑DeskcommCRM 至少让我踩了三次。第一个坑是 SQLite 并发写导致偶尔锁库。项目刚上线时出现过一段时间“操作响应特别慢”的情况。排查之后发现SQLite 在极端情况下比如好几个用户同时更新不同记录而系统又在某个时刻跑了一次全库索引任务会发生写锁竞争。解决办法是给 SQLite 的 busy_timeout 设了一个合理的等待时间并把一些不必要的即时统计查询改成异步刷新。调整之后操作慢了或者偶发报错的情况基本消失。第二个坑是时区问题。刚开始部署时没有统一设置 PHP 和数据库的时区导致记录时间比我们实际时间差了 8 个小时。销售记录跟进时看到的“刚刚”在别人那里显示的是“8 小时前”这种低级错误差点影响了团队对系统的信任。后来在建表和应用初始化时统一使用 UTC 时间存储展示时按用户的时区偏好格式化才算彻底解决。第三个坑是数据备份被“假完成”坑过一回。我原本的备份脚本只是简单复制 SQLite 文件没有校验文件完整性。有天我检查时发现备份文件存在但恢复时发现文件损坏。原因是 SQLite 在写文件的过程中如果被直接 copy文件可能处于不一致状态。后来的修复方案是在备份前先执行一条 SQLite 的 checkpoint 命令确保 WAL 日志合并完成后再复制文件同时给备份文件加上一个简单的 SHA-256 校验恢复前先校验校验不过就自动拉起前一天的备份。这之后备份才算是真正“靠谱”了。6.3 性能表现与优化当前规模下的真实数据上线运行了半年多之后我给 DeskcommCRM 做了一轮现状统计数据如下团队 8 个人累计录入客户 1200 多条线索 3000 多条跟进记录 1.6 万多条。整个 SQLite 数据库文件的体积是 18MB。在这个数据量级下所有核心操作的响应时间都很理想列表页的打开时间在 80ms 左右搜索客户的结果返回在 50ms 以内详情页时间轴加载也是在 100ms 上下。一台 2 核 4G 的云服务器CPU 占用率在日常使用中几乎在 10% 以下内存剩余空间非常充裕。有个朋友问我这套系统能撑到多大的数据量。我基于 SQLite 的特性做了估算当数据量达到几十万条时简单查询依然没问题但如果在按条件过滤时没有建好对应的数据库索引查询速度会明显下降。为了给未来预留空间我在几个高频查询字段客户名称、手机号、负责人、状态上都建了索引。如果将来真的发展到几百万条记录再考虑迁移到 MySQL/PostgreSQL 也来得及——因为核心业务逻辑已经通过一个数据库访问层隔离开了更换底层数据库引擎的影响会被控制在一个比较小的范围。7. 写在最后DeskcommCRM 带给我的一些额外收获项目上线半年多回头看不只是给团队提供了一个工具它也改变了我对“自建工具”这件事的整体认知。以前我总觉得自己从头做一套系统太费时间、没有意义市面上现成的东西那么多何必重复造轮子。但 DeskcommCRM 让我看到了另一面一个完全贴合自己工作流程的内部工具带来的效率提升和团队协作改善远远超过了我当初投入的搭建时间。在功能维护上我的迭代节奏也很有节制。每次团队有人提出“能不能加一个 XX 功能”我不会马上动手而是先问三个问题这个功能是不是每周都会用到是不是一两个人偶尔的临时需求有没有现成的方式绕过只有当第一个问题回答是“是”的情况下我才会认真考虑。克制加功能是自建系统最重要的原则——系统一旦变得复杂维护成本就会直线上升最后甚至会超过买 SaaS 服务的成本。如果你也想在自己的团队里做类似的事情我的建议是从最小可用版本开始先把客户登记 跟进记录 公海回收 成员管理这四个模块跑通其他功能等使用中碰到的真实需求积累起来以后再慢慢补。不用追求一步到位因为好的工具永远是长出来的而不是一次性攒出来的。最后再分享一个小技巧把系统每天的数据变化让管理员订阅一封简单的日报邮件——今天新增了多少客户、新增了多少线索、哪些客户进入了公海。这个机制不复杂但它能让管理者在变化发生的当天就感知到异常发现问题的时间差被大幅缩短。这种“轻提醒”比做任何复杂的 BI 分析报表都管用。
返回列表