必须用静态分组+物理目录隔离实现v1/v2 API独立运行:在route/app.php定义Route::group('v1')和Route::group('v2'),对应创建app/api/v1/User.php与app/api/v2/User.php,命名空间分别为app\api\v1和app\api\v2,类名严格为User,路由前缀不加斜杠,中间件按版本单独绑定,最后清除路由缓存验证。

要在ThinkPHP 8中让v1和v2两个版本的API接口互不干扰、各自独立运行且上线后不出现404,必须绕过动态参数路由陷阱,直接用静态分组+物理目录隔离的方式落地。这种做法不依赖中间件拦截、不拼接变量、不靠请求头判断,从路由解析那一刻起就切断混淆可能。
定义v1和v2两套独立路由分组
打开 route/app.php,在文件末尾添加以下两组路由定义:
Route::group('v1', function () { Route::get('user/:id', 'api/v1.User/read'); Route::post('user', 'api/v1.User/create'); });
Route::group('v2', function () { Route::get('user/:id', 'api/v2.User/read'); Route::post('user', 'api/v2.User/create'); });
【前缀必须是字符串字面量,不能是变量或配置读取值】。哪怕你写成 $v = 'v1'; Route::group($v, ...),开发环境看似正常,上线后路由缓存生成失败,所有请求全部返回404。
立即学习“PHP免费学习笔记(深入)”;
注意:'v1' 不带开头斜杠,ThinkPHP会自动拼接;若写成 '/v1',最终路径变成 //v1/user/123,触发解析错误。
创建对应命名空间与控制器文件
执行命令创建两个子目录:
php think make:controller api/v1/User
php think make:controller api/v2/User
检查生成的文件路径是否为:
app/api/v1/User.php 和 app/api/v2/User.php
打开 app/api/v1/User.php,确认首行命名空间为:
namespace app\api\v1;
类名必须与文件名完全一致——User.php 对应 class User,【不能写成 UserController】。如果写了 UserController,而路由里没显式写 api/v1.UserController/read,就会报 Class not found 错误。
Linux服务器严格区分大小写,v1 目录名写成 V1 或 ApiV1,部署后立刻500。Windows下能跑通只是假象。
为不同版本绑定专属中间件
v1 版本用 session 鉴权,v2 要求 JWT,两者中间件不能混用。
修改 v1 路由分组,加入 session 中间件:
Route::group('v1', function () { … })->middleware(['allow_cross_domain', 'session_auth']);
单独为 v2 配置 JWT 中间件:
Route::group('v2', function () { … })->middleware(['allow_cross_domain', 'jwt_auth']);
别把中间件规则写进 middleware.php 的路径映射数组里,例如 ['api/v1' => [JwtAuth::class]] 是无效的——ThinkPHP根本不解析这种路径字符串。
清除路由缓存并验证路径
第一步:执行命令清空旧缓存
php think route:clear
第二步:访问测试地址
GET http://your-domain.com/api/v1/user/1 → 应进入 app\api\v1\User@read
GET http://your-domain.com/api/v2/user/1 → 应进入 app\api\v2\User@read
第三步:检查响应头中的 X-Powered-By 是否包含 ThinkPHP,确认未因缓存残留导致路由错配。



















