ThinkPHP 6.x 是生产环境唯一合理选择,因5.1已终止维护且不兼容PHP 8.2+ strict type;6.x强制PSR-4加载、事务需显式回滚、中间件注册方式变更,并要求客户与联系人分表、销售阶段校验、跟进待办事务一致性。

ThinkPHP 5.1 vs 6.x:选型前必须确认的兼容性断层
直接上结论:如果你要上线生产环境且需长期维护,ThinkPHP 6.x 是唯一合理选择;ThinkPHP 5.1 已停止安全更新(官方于2024年12月终止维护),现有项目若未升级,vendor/composer/autoload_classmap.php 可能因 PHP 8.2+ 的 strict type 检查直接报 Fatal error: Declaration of ... must be compatible with ...。
关键差异点:
-
ThinkPHP 6.x默认启用 PSR-4 自动加载,模型类必须严格按命名空间路径存放(如app\model\Customer对应app/model/Customer.php),而 5.1 允许扁平化路径 - 数据库事务默认行为不同:6.x 中
Db::transaction()内部异常不自动 rollback,必须显式捕获并调用Db::rollback();5.1 会自动回滚 - 中间件注册方式变更:6.x 要求在
app/middleware.php中返回数组,5.1 放在app/common.php里用Hook::add()
客户与联系人必须分表,且外键约束不能靠应用层“自觉”
常见错误是把联系人字段全塞进 customers 表,结果一家客户有 3 个对接人时,contact_name、contact_phone 字段只能存一个,历史跟进完全丢失。
正确做法:
立即学习“PHP免费学习笔记(深入)”;
-
customers表只存公司级属性:name、industry、scale、source、owner_id,加联合索引(owner_id, created_at)支撑销售日报查询 -
contacts表必设customer_id外键(InnoDB),并加索引;字段含position、is_decision_maker(tinyint(1))、email,避免用 text 存手机号(影响索引效率) - 模型关联写死:在
app\model\Customer中定义public function contacts() { return $this->hasMany(Contact::class, 'customer_id'); },查询时用Customer::with('contacts')->find($id),别手写 join
销售漏斗阶段跳转必须服务层校验,禁止前端传参直改
很多团队把阶段存在 customers.current_stage_id 字段,然后前端下拉框提交新值——这等于把状态机逻辑交给浏览器,POST /customer/update 一发就跳到 won,绕过所有资格审核规则。
可靠实现方式:
- 建独立
sales_stages表,字段含id、name、sort_order、is_closed,并预置合法转移路径(如qualified → proposal_sent,但不允许qualified → won) - 封装
PipelineService::moveToStage(int $customerId, int $targetStageId):先查当前最新 stage(从customer_pipeline表取 max id 记录),再比对sales_stages中的allowed_from_ids字段(JSON 数组或用中间表) - 成功后写入
customer_pipeline新记录,并更新customers.current_stage_id和last_stage_at,这两步必须包在同一个事务里
跟进记录必须带 next_step_at,且触发待办的逻辑不能放在前端
用户填了“下次联系时间”,但系统没提醒——大概率是把 next_step_at 存进了 activities 表却没联动更新 todos 表,或者用 JS 定时器在浏览器里轮询,页面一关就失效。
真实可用方案:
-
activities表必须含next_step_at(datetime NULL)、status(enum('pending','done'))、created_by,且next_step_at允许为空(比如纯备注不需要跟进) - 新增跟进时,用命令行调度器跑定时任务:
php think todo:sync,扫描activities中status='pending'且next_step_at <= NOW()的记录,批量写入todos表(含user_id、related_type、related_id) - 不要依赖 MySQL event 或 trigger 做实时同步——TP6 的连接池机制下,event 可能漏触发,且无法跨库事务
最易被忽略的是 todos 表的清理时机:当用户手动将某条跟进标记为 done,必须同时删掉对应的 todo 记录,否则待办列表越积越多。这个 delete 逻辑必须和 update activity 同事务执行,不能靠前端二次请求。



















