本文目录导读:

- 方案一:通过 URL 路径区分版本(最直观、推荐)
- 方案二:通过 HTTP Header 区分版本(更“规范”但有争议)
- 方案三:使用 Package(成熟稳定)
- 最佳实践建议
- 例子:完整的路由文件结构
- 总结推荐
在 Laravel 中设计“版本路由”通常有两种核心思路:
- API 版本控制(最常见,通过 URL 或 Header 区分)
- 应用版本控制(针对不同前端版本或客户端版本返回不同数据)
下面主要围绕 API 版本路由 来详细说明设计方法。
通过 URL 路径区分版本(最直观、推荐)
这是最流行的方式,/api/v1/users 和 /api/v2/users。
基础结构:使用 Route Group + Prefix
// routes/api.php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| API Routes v1
|--------------------------------------------------------------------------
*/
Route::prefix('v1')->group(function () {
Route::get('/users', [App\Http\Controllers\Api\V1\UserController::class, 'index']);
Route::post('/users', [App\Http\Controllers\Api\V1\UserController::class, 'store']);
// ... 其他 v1 路由
});
/*
|--------------------------------------------------------------------------
| API Routes v2
|--------------------------------------------------------------------------
*/
Route::prefix('v2')->group(function () {
Route::get('/users', [App\Http\Controllers\Api\V2\UserController::class, 'index']);
// v2 可能对 store 方法做了修改
Route::post('/users', [App\Http\Controllers\Api\V2\UserController::class, 'store']);
// ... 其他 v2 路由
});
控制器结构建议:
// app/Http/Controllers/Api/V1/UserController.php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\User;
class UserController extends Controller
{
public function index()
{
return User::all(); // v1 逻辑(字段、关系不同)
}
}
// app/Http/Controllers/Api/V2/UserController.php
namespace App\Http\Controllers\Api\V2;
use App\Http\Controllers\Controller;
use App\Models\User;
class UserController extends Controller
{
public function index()
{
return User::with('profile')->get(); // v2 增加了 profile 关系
}
}
优化:使用命名空间
可以在 RouteServiceProvider 中统一设置命名空间,简化书写。
// app/Providers/RouteServiceProvider.php
public function boot(): void
{
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
// 或者单独设置不同版本的命名空间
Route::prefix('api/v1')
->middleware('api')
->namespace('App\Http\Controllers\Api\V1')
->group(base_path('routes/api_v1.php'));
Route::prefix('api/v2')
->middleware('api')
->namespace('App\Http\Controllers\Api\V2')
->group(base_path('routes/api_v2.php'));
}
通过 HTTP Header 区分版本(更“规范”但有争议)
不使用 URL 路径,而是通过 Accept 或自定义 X-Version 头来决定使用哪个控制器。
定义中间件
php artisan make:middleware ApiVersion
// app/Http/Middleware/ApiVersion.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class ApiVersion
{
public function handle(Request $request, Closure $next)
{
// 从 Header 中获取版本号,Accept: application/vnd.myapp.v2+json
$acceptHeader = $request->header('Accept');
$version = 'v1'; // 默认 v1
if (preg_match('/application\/vnd\.myapp\.(v\d+)\+json/', $acceptHeader, $matches)) {
$version = $matches[1];
}
// 将版本号绑定到请求属性中
$request->attributes->set('api_version', $version);
// 动态调用不同版本的控制器
// 注意:这里需要确保对应的控制器类存在
return $next($request);
}
}
使用 Trait 或 Base Controller 动态路由
可以写一个基类控制器或 Trait,根据版本号调用不同业务逻辑。
// app/Http/Controllers/Api/BaseController.php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class BaseController extends Controller
{
protected function getVersionedController(string $controllerName, Request $request)
{
$version = $request->attributes->get('api_version', 'v1');
$class = "App\\Http\\Controllers\\Api\\{$version}\\{$controllerName}";
if (!class_exists($class)) {
abort(404, "API version {$version} not found");
}
return app($class);
}
}
然后在路由中:
Route::get('/users', function (Request $request) {
$controller = app()->make(\App\Http\Controllers\Api\BaseController::class);
$versionedController = $controller->getVersionedController('UserController', $request);
return $versionedController->index($request);
})->middleware('api.version');
这种方式的优点是 URL 保持
/api/users不变,缺点是:
- 增加了复杂性
- 不够直观,调试困难
- 对于缓存、文档生成不友好 现实项目中较少使用,更推荐 URL 路径方案。
使用 Package(成熟稳定)
如果需要更强大、规范化的版本控制(包括 Header 版本、过期策略等),推荐使用现成包:
- Laravel API Versioning (如
mnahmad/laravel-api-versioning) - Laravel API Resource 版本(结合 Transformer)
- 自定义路由宏
不过对于多数项目,方案一(URL 前缀 + 独立控制器)足够清晰高效。
最佳实践建议
- API 版本尽量少变:设计好 v1,不要轻易改 v2,如果只是加字段,用可选的属性扩展(如
additional字段),而不是切版本。 - URL 路径版本为首选:
/api/v1/resource最清晰、易维护。 - 控制器按版本分离:不要在一个控制器里
if v1/if v2,那样会变得混乱。 - 使用 Repository / Service 模式:公共业务逻辑放在 Service 层,控制器只是负责转换(Transformer/Resource)。
- 版本过期机制:可以在
AppServiceProvider中注册一个v1的过期告警中间件,告诉客户端要升级。
// 在 v1 路由组中增加
Route::prefix('v1')->middleware('api.version.obsolete')->group(...);
// middleware 内输出 Header: X-API-Version-Obsolete: true
例子:完整的路由文件结构
routes/
├── api.php # 公共路由(默认版本)
├── api_v1.php # v1 专有路由
├── api_v2.php # v2 专有路由
├── web.php # Web 路由
└── ...
// routes/api_v1.php
Route::prefix('v1')->group(function () {
Route::get('/users', ...);
Route::post('/users', ...);
});
// routes/api_v2.php
Route::prefix('v2')->group(function () {
Route::get('/users', ...);
Route::post('/users', ...);
});
然后在 RouteServiceProvider 加载它们:
$this->routes(function () {
Route::middleware('api')
->prefix('api')
->group(base_path('routes/api_v1.php'));
Route::middleware('api')
->prefix('api')
->group(base_path('routes/api_v2.php'));
});
这样结构清晰,方便多人协作。
总结推荐
| 场景 | 建议方案 |
|---|---|
| 简单 API 版本 | URL 路径版本 (/api/v1, /api/v2) |
| 要求 URL 统一、Header 控制版本 | Header 版本(配合中间件,但慎用) |
| 大型项目、需要多种版本策略 | Package (如 Spatie 的 laravel-query-builder 中的版本支持) |
最终结论:Laravel 里版本路由最简单的设计就是 Route::prefix('v1') + Route::prefix('v2'),每个版本对应一个控制器命名空间,清晰、可维护,也是 Laravel 官方文档推荐的入门做法。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播