Route::group() 是唯一正解,其第一个参数必须是含 prefix、middleware、as 等键的配置数组;API 版本路由应分文件物理隔离,控制器与资源类按语义断裂程度决定是否拆分,中间件和错误响应须按版本粒度对齐。

Route::group() 是唯一正解,别用数组或链式调用
直接在 Route::get() 或 Route::resource() 外套个数组,或者写成 Route::prefix('v1')->middleware('api')->get(...),都不生效——Laravel 路由分组只认 Route::group() 这一种语法。它的第一个参数必须是配置数组,里面明确定义 prefix、middleware、as 等键。
常见错误现象:php artisan route:list 里看不到带版本前缀的路由名,或者中间件没跑;IDE 点击路由名跳不到控制器;生成 URL(如 route('api.v1.users.index'))报错。
-
prefix值不加开头斜杠,写'api/v1',别写'/api/v1' -
as推荐以字母开头、结尾加点号,如'v1.',避免和数字路由参数冲突 - 嵌套
Route::group()会拼接prefix,但命名(as)不会自动继承,必须每层显式写
API 版本路由必须独立文件 + prefix 分离
把所有版本塞进 routes/api.php 会快速失控。正确做法是在 App\Providers\RouteServiceProvider::boot() 里显式加载各版本路由文件,靠物理隔离降低耦合。
使用场景:v2 上线后要灰度发布、v1 还需长期维护、不同版本限流策略不同。
- 手动创建目录
routes/api/v1.php和routes/api/v2.php - 在
RouteServiceProvider中这样注册:Route::prefix('api/v1')->middleware('api')->group(base_path('routes/api/v1.php')); - 不要用
Route::version()——这不是 Laravel 原生方法,是某些包伪造的,会导致route:list和缓存异常
控制器要不要新建?看语义变更程度
v2 不是必须新建 API\V2\UserController。是否拆分,取决于行为是否“断裂”:字段增减、响应结构微调、新增端点,复用 API\V1\UserController 加条件判断即可;但若分页逻辑从 offset/limit 改成 cursor-based,或认证方式从 token 换成 JWT,就必须分控制器。
容易踩的坑:在资源类(如 UserResource)里写 if (request()->routeIs('api.v2.*')) 来切换字段——路由信息在资源构造时不可靠,且破坏单元测试可运行性。
- 推荐做法:v2 用专属资源类
API\V2\Resources\UserResource,与控制器一一对应 - 通用逻辑(如数据格式化、权限检查)下沉到 Service 或 Form Request,别堆在控制器里
- 错误响应格式(404/422/500)必须和当前版本对齐,v2 请求不能返回 v1 风格的 JSON 错误体
中间件绑定和缓存必须按版本粒度控制
同一个 throttle:api 中间件,v1 可能限 60 次/分钟,v2 因为客户端升级可放宽到 200 次——这只能靠路由分组时单独绑定实现,没法全局统一。
性能影响:路由缓存(php artisan route:cache)后,改了任何版本路由文件都得手动清缓存,否则新路由不生效;IDE 跳转失败、Postman 自动补全失效,往往就是因为缓存没清或 as 名没写对。
- 每个版本分组必须显式声明
middleware数组,不能依赖外部包裹 -
Route::resource()生成的全部 7 个路由自动继承分组中间件,无需单独配 - 千万别混用
web和api中间件——session、csrf、cookie 相关中间件在 API 路由里会直接报错或静默失效
最常被忽略的是异常响应格式与版本对齐这件事:v2 的错误结构变了,但 App\Exceptions\Handler 里还是按 v1 渲染 JSON,前端一解析就崩。这不是路由问题,但它是版本分组之后立刻要补上的缺口。











