实操记录:从 0 到 1 打造外贸人专用 CRM:开发经验与心得分享
本文记录了「外贸人 CRM 系统」从需求构思到代码落地的全过程开发经验。
这是一套面向外贸个人的轻量获客 + 客户管理 + 智能跟进工具,技术栈为 PHP + MySQL + Bootstrap,运行于 XAMPP 环境。
不讲大道理,只分享真实踩过的坑、做过的取舍以及回头看值得改进的地方。
一、为什么做这个项目
外贸人的工作日常被三件事割裂:找客户、管客户、跟客户。
找客户需切换 Google、LinkedIn、海关数据;管客户靠 Excel 表格满天飞;跟进记录散落在微信、邮件、备忘录里。工具切换成本高、客户跟进断层、获客转化低效,是几乎所有外贸个人从业者的共同痛点。
因此决定打造一款「够用、轻量、一站式」的 CRM,目标明确:
- 不追求重型 CRM(如 Salesforce)的复杂,避免给个人造成负担;
- 打通获客与管理,搜到的线索可一键转为客户;
- 实现跟进闭环,下次跟进时间、提醒及历史记录一目了然;
- 内置外贸场景工具(关键词生成、时差查询、黄页导航等),无需开启多个标签页。
定位清晰后,所有功能取舍便有了标尺。
二、技术选型:为什么是 PHP + 自研 MVC
2.1 选 PHP 而非 Node/Python/Java
这是个易受争议的决定,但对外贸目标人群而言,PHP 是最优解:
| 考量 | PHP 的优势 |
|---|---|
| 部署门槛 | XAMPP 一键安装,放入 htdocs 即可运行,外贸人无需配置环境 |
| 主机成本 | 几十元的虚拟主机即可运行 PHP+MySQL,远低于 VPS 成本 |
| 生态成熟 | 邮件、PDF、CSV、PDO 等原生支持,无需折腾依赖 |
| 迭代速度 | 修改后刷新即生效,无编译打包等待时间 |
技术选型容易陷入「技术先进性」的执念,但用户的部署能力才是第一约束。外贸人非程序员,若要求安装 Node、配置 npm 或运行 Docker,项目往往死在安装环节。
2.2 自研轻量 MVC 而非使用 Laravel/ThinkPHP
这是更激进的取舍。为何不直接用 Laravel?
- 学习成本:项目需长期维护,框架越重,后人接手越难;
- 部署体积:Laravel 的 vendor 目录高达几百兆,在虚拟主机上传极为不便;
- 可控性:自研框架每行代码皆由己出,出问题能快速定位。
因此编写了 8 个核心类(App/Router/Controller/Model/Database/Auth/Session/Helper),总计不到 1000 行,覆盖路由、ORM 基类、PDO 单例、认证、Session、CSRF 及视图渲染。既够用,又易懂。
心得:框架并非越先进越好,而是越匹配场景越好。对于总代码量不足 2 万行的项目,引入全家桶框架属于过度设计。当然,代价是缺少了 Laravel 的迁移、队列、Eloquent 等利器,后续将通过约定来弥补。
三、架构设计:几个关键决策
3.1 单入口 + 约定式路由
所有请求经 .htaccess 重写到 index.php?url=xxx,再由 Router 解析成 Controller/action。
路由器做了优化:连字符自动转驼峰。例如 URL 中写 followup,自动映射到 FollowUpController。此举既兼顾 SEO 与用户友好度,又符合 PHP 类命名规范。
3.2 Model 基类:用约定换 ORM
心得:必须设置 fillable 白名单。早期未加 fillable,导致前端多传 is_admin 字段直接入库,形成典型的批量赋值漏洞。加上白名单后,多余字段被 array_intersect_key 过滤,从根源堵住风险。
3.3 控制器基类:收敛重复逻辑
所有控制器继承 Controller,统一处理:
- 视图渲染(render 自动套用主布局,renderPartial 输出裸视图);
- JSON 响应(jsonSuccess/jsonError 统一格式);
- CSRF 校验(validateCsrf,所有 POST 请求必过);
- 权限前置(requireLogin/requireAdmin);
- 闪存消息(flash 一次性提示)。
这套基类让业务控制器保持精简,业务方法平均仅 20-30 行,易于阅读。
四、数据库设计的心得
4.1 18 张表够不够
最终落地 18 张表 + 2 视图 + 1 存储过程。核心实体包括:用户、客户、线索、跟进、邮件、产品、报价单、订单、寄样、会员。
心得:勿为「显得专业」而过度拆表。早期曾想将客户联系方式独立拆表,后发现外贸场景下 90% 的客户仅有一个主联系人,拆表反而导致查询需频繁 JOIN。最终将 contact_name/position/email/phone 作为单字段存入 customers 表,简单直接。
注:若是 B2B 大客户场景,联系人则必须拆表——表结构应服务于业务形态,而非设计范式。
4.2 用存储过程处理撞单
外贸 CRM 最敏感的是撞单(同一客户被两销售争抢),此处采用存储过程解决。
4.3 公海私海用 is_public 一个字段
简化状态管理,仅用一个字段区分公海与私海。
4.4 全文索引解决模糊搜索
客户搜索若用 LIKE '%关键词%',数据量大时性能极差。因此在 customers 表上添加了全文索引。
五、安全实践:这是底线不是加分项
CRM 涉及客户数据,安全不容马虎。该系统落地了 6 道防线。
六、几个有意思的功能实现
6.1 撞单检测:实时 AJAX 校验
客户录入时,邮箱失焦即触发 AJAX 调用 customer/checkConflict。若被占用直接弹出红色提示,体验远优于「提交后才报错」。
6.2 报价单:明细动态计算
报价单创建页支持前端动态加行并计算小计,提交时将 items 数组 JSON 化传至后端。
后端 QuoteController::save 解析 JSON 后循环计算总价,连同明细入库。报价单转订单亦可一键完成,复用客户和金额字段。
6.3 13 个外贸工具的集成
将外贸人常用的十几个小工具(关键词生成、时差查询、黄页导航等)集成进系统,做成侧边栏折叠菜单。
每个工具即为独立 view(如 views/tools/1.b2b.php ... 12.huangye.php),控制器仅需简单 render 调用。工具逻辑全在前端 JS,不占后端资源,也不增加数据库负担。
心得:并非所有功能都要后端化。纯前端工具应留在前端,后端仅负责持久化和权限控制。「厚前端薄后端」的分工让系统更轻盈。
虽然生产环境需改为正式域名,但这带来重要启示:复杂能力可独立部署再嵌入。AI 工具迭代快,与 CRM 主系统解耦后,AI 端可独立升级而不影响 CRM 稳定。
七、第三方接口的「预留」哲学
本项目特别之处在于:AI 邮件、邮箱验证、邮件发送、海关数据、支付等功能均为模拟实现,但全部预留了接口开关。
config/api.php 中每个第三方服务均配置 enabled/api_key/api_url 三件套:
业务代码通过 if (config('api.doubao.enabled')) 切换「模拟返回」与「真实调用」。
为何如此设计?
- 开发阶段不依赖外部服务:AI 接口收费、邮件接口需配域名、支付需资质,开发时若全卡住将无法编码。
- MVP 先跑通流程:模拟发送邮件设定 90% 成功率,确保前端逻辑、状态流转、错误处理全跑通,接真实接口时仅需替换函数。
- 降低用户上手门槛:用户装完即用,无需先申请一堆 API key,待用熟后再配真实接口。
心得:预留接口是工程上的「渐进式替换」策略,而非偷懒。但需警惕陷阱——模拟实现与真实实现的行为应尽量一致。例如模拟邮件发送设定 90% 成功、10% 失败,旨在测试前端对失败状态的处理。若模拟永远成功,上线后必因未测失败处理而出错。
八、会员体系与商业化预留
系统设计之初即考虑商业化,memberships 表包含配额字段:
max_customers -- 最大客户数
max_leads_per_month -- 每月线索数
max_emails_per_month -- 每月邮件数
设 4 档套餐:免费版(¥0,50 客户)、基础版(¥99)、专业版(¥299)、企业版(¥999,无限)。
心得:商业化字段需一开始就设计好,切勿等做付费时再加。后加配额字段意味着要修改所有写入点并增加校验,工作量翻倍且易遗漏。第一版即在 customers 写入前预留配额检查钩子。
支付接口(微信/支付宝)同样预留配置项,后台设有支付配置页和记录展示。真正接入时,前端下单→支付→异步通知→开通会员的链路清晰可见。
九、踩过的坑与反思
9.1 安装脚本的 DELIMITER 坑
install.php 用 PDO 执行 SQL 文件,但 PDO 不支持 DELIMITER // 语法(此为 mysql 命令行客户端特性),导致存储过程段报错。
解法:用正则去掉存储过程段,逻辑改用 PHP 实现。
$sql = preg_replace('/DELIMITER\s\/\/.?DELIMITER\s;/is', '', $sql);
反思:这是绕过的坑,非彻底解决。若未来真需用存储过程,得用 multi_query 或单独迁移脚本。此事表明:SQL 文件并非跨工具可移植,DELIMITER、CREATE PROCEDURE、触发器等均属 mysql 客户端特性。
9.2 路由器的「约定式」陷阱
路由器自动将 controller/action 解析成类,省去了写路由表,但也导致任何不存在的控制器均返回 404,而无明确「路由未注册」提示。
早期因拼写错误(custmer 少个 o),用户点击即 404,后端却无日志记录访问痕迹。若用显式路由表,未注册路由一开始便会暴露。
教训:约定优于配置虽好,但调试性差。折中方案是:显式注册核心路由 + 兜底约定式解析,既保留灵活性又有可观测性。
9.3 权限控制停留在「展示」层
admin/permissions 页面展示了角色权限矩阵,但 Auth::can() 的细粒度校验并未在所有控制器方法中落地。例如「普通用户不能删除客户」的规则,代码中实际未拦截。
反思:权限设计要「先做减法再做加法」。与其设计一套 RBAC 矩阵却落地不全,不如先将「管理员 vs 普通用户」的二态权限做扎实,待真有多角色需求再上 RBAC。半套权限系统比没有更危险——它给人「已防护」的错觉。
9.4 报价单 PDF 的妥协
报价单「生成 PDF」功能最终仅输出了 HTML:
header('Content-Type: text/html');
echo $this->renderQuoteHtml($quote, $customer);
理由是 TCPDF/mpdf 库体积大、虚拟主机运行慢。但这对用户是半成品体验——他们需要可下载的 PDF 文件,而非网页。
教训:功能可分期做,但要明确标注「未完成」。代码注释中的 TODO 在迭代中易被遗忘,后来在需求文档中专门列出「待完善项」清单,集中管理此类半成品。
十、给后来者的几条建议
若重来一次或给类似项目建议,我会说:
- 先想清楚给谁用,再选技术栈。给程序员用就上 Laravel + Docker;给外贸人用就 PHP + XAMPP。技术选型的第一约束是用户,而非技术本身。
- 核心类自己写一遍。框架用多了会「会用不会改」。亲手实现 MVC、ORM 基类、认证,能对底层机制形成肌肉记忆,排查问题快十倍。
- 第三方接口一律预留开关。任何付费/外部服务,开发阶段全用模拟,上线再切真实。但模拟行为要逼近真实(包括失败概率)。
- 安全措施做成默认项。CSRF、XSS、密码加密等不能是「可选」,必须在基类强制实施。一个疏漏就是一次事故。
- 状态机能简单就简单。公海私海一个 is_public 字段搞定,别拆两个字段。复杂度是累加的,每多一个字段就多一份不一致风险。
- 待办事项集中管理。所有 TODO、半成品、预留接口都写入清单文档,定期 Review。散落在代码注释里的 TODO 会被遗忘。
- 前端工具能独立就独立。纯展示型工具(导航、生成器)让它们待在前端,后端只管需要持久化的业务。系统越薄越稳。
- 会员配额字段一开始就建好。商业化字段后加成本极高,第一版表结构就要留好位置。
- 安装流程要傻瓜化。制作 4 步安装向导(环境检测→数据库→管理员→完成),这对非技术用户是刚需。难装的系统,功能再强也推不开。
- 写文档。不是写给别人看,是写给三个月后的自己看。撰写需求方案文档的过程,让我重新审视了整个项目,发现了 7 处待完善——写文档是最好的代码 Review。
十一、写在最后
这个项目不算大,但五脏俱全。从需求定位、技术选型、架构设计、数据库建模、安全实践、第三方集成、商业化预留,到踩过的坑和反思,它完整呈现了一个「能跑且能卖」的 SaaS 雏形是如何诞生的。
它不完美——PDF 未真做、权限未全落地、多语言没埋钩子、AI 接口仍是模拟。但它有一个最大优点:每一行代码都知道为什么这么写,每一个妥协都记录在案。
做产品最大的敌人不是技术难度,而是「不知道为什么这么做」的混沌状态。把这个项目从混沌推到「清晰且有据可查」,是此次开发最大的收获。
如果你也在做类似项目,或对某个模块的实现细节感兴趣,欢迎交流。代码和完整的需求方案文档均在仓库中,本文即是它的「开发日记」。

