Laravel版本路由怎么设计

wen PHP项目 2

本文目录导读:

Laravel版本路由怎么设计

  1. 方案一:通过 URL 路径区分版本(最直观、推荐)
  2. 方案二:通过 HTTP Header 区分版本(更“规范”但有争议)
  3. 方案三:使用 Package(成熟稳定)
  4. 最佳实践建议
  5. 例子:完整的路由文件结构
  6. 总结推荐

在 Laravel 中设计“版本路由”通常有两种核心思路:

  1. API 版本控制(最常见,通过 URL 或 Header 区分)
  2. 应用版本控制(针对不同前端版本或客户端版本返回不同数据)

下面主要围绕 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 版本、过期策略等),推荐使用现成包:

  1. Laravel API Versioning (如 mnahmad/laravel-api-versioning)
  2. Laravel API Resource 版本(结合 Transformer)
  3. 自定义路由宏

不过对于多数项目,方案一(URL 前缀 + 独立控制器)足够清晰高效。


最佳实践建议

  1. API 版本尽量少变:设计好 v1,不要轻易改 v2,如果只是加字段,用可选的属性扩展(如 additional 字段),而不是切版本。
  2. URL 路径版本为首选/api/v1/resource 最清晰、易维护。
  3. 控制器按版本分离:不要在一个控制器里 if v1 / if v2,那样会变得混乱。
  4. 使用 Repository / Service 模式:公共业务逻辑放在 Service 层,控制器只是负责转换(Transformer/Resource)。
  5. 版本过期机制:可以在 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 官方文档推荐的入门做法。

抱歉,评论功能暂时关闭!