ThinkPHP 多版本路由应使用 Route::group() 配静态前缀(如 'v1'、'v2'),避免参数式分发;各版本需独立中间件、控制器目录和资源格式化,确保逻辑隔离与缓存友好。

ThinkPHP 路由怎么配多版本前缀(如 v1、v2)
直接在路由定义里加路径前缀是最简单也最可控的方式。ThinkPHP 的 Route::group() 支持路径前缀自动拼接,不需要额外中间件或重写规则。
常见错误是把版本号写死在控制器方法里,或者用参数式路由(如 /api/<var>version</var>/user),这会导致路由缓存失效、IDE 无法跳转、Swagger 文档难生成。
- 版本前缀必须是静态字符串,比如
'v1'、'v2',不能是变量或正则捕获 - 每个版本组要单独定义中间件,比如
v2可能需要新鉴权逻辑,别复用v1的middleware - 如果用了域名绑定(如
api.v1.example.com),就别再叠加路径前缀,否则变成/v1/v1/user
Route::group('v1', function () {
Route::get('user', 'api/v1.User/get');
})->middleware('throttle:60,1');
Route::group('v2', function () {
Route::get('user', 'api/v2.User/get');
})->middleware('throttle:100,1');
为什么不用 version 参数做路由分发
ThinkPHP 原生不提供基于请求参数或 header 的自动版本路由分发机制。硬要用 version 参数(如 ?version=v2)会破坏 RESTful 约定,且带来三个实际问题:
- 缓存不可靠:同一 URL 不同
version参数会被 CDN 或浏览器当成同一个资源缓存 - 日志和监控难区分:所有请求都打到
/user,没法按版本统计错误率或响应时间 - OpenAPI 文档必须手动拆分,不能靠路径自动归类
更关键的是,TP 的路由匹配优先级中,带参数的规则默认低于静态前缀——你写了 Route::get('user', ...) 和 Route::get('v2/user', ...),后者永远优先生效,前者形同虚设。
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
立即学习“PHP免费学习笔记(深入)”;
Route::rule() 配置多版本时容易漏掉的兼容点
如果你用 Route::rule() 手动写带前缀的完整路由(而不是 group),要注意闭包或数组方式定义时,模块/控制器/操作的解析逻辑会变。
- 路径写成
'v1/user' => 'api/v1.User/get'是 OK 的,但别写成'v1/user' => 'v1.User/get'—— TP 不会自动补api/前缀 - 如果开启了
app_multi_module,确保v1和v2对应的控制器类确实放在app/api/v1/和app/api/v2/目录下,否则加载失败报class not found - 路由缓存开启时,改完版本路由必须运行
php think route:clear,否则旧规则还在生效
跨版本共用模型但行为不同的处理方式
版本升级常遇到「接口路径和入参不变,但返回字段删减或逻辑调整」。这时候别在控制器里 if-else 切版本,而是把差异下沉到服务层或 DTO 层。
- 避免在
v1.User控制器里调v2.UserService,会造成依赖倒置和测试困难 - 推荐做法:统一用
UserAppService,构造时传入版本上下文,内部用策略模式分发逻辑 - 如果只是字段过滤不同,用
think\facade\Db查询后,通过v1.UserResource/v2.UserResource类分别格式化输出,比在 JSON 中手动 unset 更可维护
版本控制真正的难点不在路由怎么写,而在于如何让 v1 的业务逻辑不被 v2 的修改意外影响——路由只是入口,隔离得越早,后期越省事。


















