thinkphp接口灰度发布需路由层分流+中间件识别版本,通过versionmiddleware解析header注入$request->version,统一路由分组+versiondispatch调度,模型与验证器按版本隔离,redis控制开关并记录日志确保可回滚。

ThinkPHP 做接口版本灰度发布,核心不是靠框架内置功能,而是靠路由层主动分流 + 请求上下文识别,旧版接口必须继续跑、新版接口要可控上线——这意味着你得自己接管版本判断逻辑,不能依赖 Route::rule() 的简单前缀匹配。
用中间件做请求入口级版本识别
灰度不是“全量切流”,而是按用户 ID、设备号、Header、AB 测试分组等条件动态决定走 v1 还是 v2。直接在全局中间件里解析并注入版本上下文最稳妥,避免每个控制器重复判断。
- 在
app/middleware/VersionMiddleware.php中读取Accept头(如application/vnd.myapi.v2+json)或自定义 Header(如X-API-Version: 2),解析后写入thinkRequest的扩展属性:$request->version = 'v2' - 若灰度规则更复杂(比如只对
user_id % 100 的用户放行 v2),把规则配置抽到 <code>config/version.php,中间件里加载判断 - 务必在中间件中调用
$next($request)前完成版本赋值,否则控制器里$this->request->version会是 null
路由分组 + 控制器命名空间隔离新旧版本
ThinkPHP 不支持同一 URL 同时绑定两个控制器,所以必须让 v1 和 v2 的路由指向不同命名空间下的控制器,且路径一致(如都响应 /api/user/info),靠中间件分流后,路由只是“容器”,真正执行谁由中间件提前决定。
- 定义两套路由分组:
Route::group('api', function () { ... })->namespace('app\api\v1');和Route::group('api', function () { ... })->namespace('app\api\v2');——但这样会冲突,实际只能保留一个分组,用中间件动态切换命名空间 - 推荐做法:所有 API 路由统一注册到一个分组(如
Route::group('api', [...])),不指定namespace,改用闭包路由显式实例化控制器:function () { return (new pppi2User())->info(); },并在闭包内根据$this->request->version分支调用 - 更干净的做法是自定义一个
VersionDispatch路由调度类,在route/app.php里统一注册:Route::any('api/:controller/:action', 'VersionDispatch@dispatch'),由它查版本、反射对应控制器
如何安全共存数据库模型与验证规则
v1 和 v2 接口可能对应不同字段、不同校验逻辑,但表结构往往不能立刻改——模型和验证器也得支持多版本共存,否则改一个就崩一片。
- 模型不要直接继承
hinkModel,改为抽象基类BaseModel,再派生UserV1Model和UserV2Model,各自覆盖table、schema、validate等属性 - 验证器同样按版本拆:创建
app\validate\v1\UserValidate和app\validate\v2\UserValidate,控制器里根据$this->request->version实例化对应验证器 - 特别注意:如果 v2 新增了非空字段,但 v1 请求不带该字段,验证器必须允许 v1 场景跳过该规则,可用
scene或动态rule数组控制,别硬编码require
灰度开关与线上回滚怎么落地
灰度不是“开了就完事”,得能随时关、能看日志、能查某用户走的是哪版——这些没法靠 config 文件热更新解决,得有运行时控制能力。
- 把灰度开关存在 Redis(如 key
api:version:enable),中间件每次请求都GET一次,避免重启服务;设默认值为v1,确保故障时自动降级 - 记录关键日志:在中间件末尾写一条
Log::info('api_version_dispatch', ['url' => $request->url(), 'version' => $request->version, 'uid' => $uid]);,方便 ELK 搜特定用户路径 - 回滚不是删代码,而是改 Redis 开关值 + 清空路由缓存:
php think clear --route,否则旧路由可能还缓存在runtime/route里被误用
最难的不是写两套代码,而是让 v1 用户完全感知不到 v2 上线,v2 用户也绝不误触 v1 的副作用逻辑——所有共享资源(Redis 键名、消息队列 topic、第三方回调地址)都得带版本后缀,连 cache('user_token_v2_'.$uid) 这种细节漏掉一个,灰度就变成事故。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











