Laravel API限流与版本控制

在构建 API 时,限流(Rate Limiting) 可以防止滥用和过度使用,保护后端服务;版本控制(Versioning) 则允许 API 在不破坏现有客户端的情况下平滑演进。Laravel 提供了内置的限流中间件和灵活的版本控制策略,让你轻松实现这些关键功能。

🎯 核心目标: 限流确保公平使用和系统稳定,版本控制保障 API 的长期可维护性。两者都是生产级 API 的必备特性。

一、API 限流(Rate Limiting)

1. 全局限流配置

Laravel 的限流功能基于 Illuminate\Cache\RateLimiter,通过中间件 throttle 实现。你可以在 App\Http\Kernel 中定义全局限流中间件,或者在路由中单独指定。

app/Http/Kernel.php$middlewareGroups 中的 api 组默认包含了 throttle:api 中间件:


protected $middlewareGroups = [
    'api' => [
        'throttle:api',  // 默认限制 60 次/分钟
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];
                    

对应的限流配置定义在 App\Providers\RouteServiceProviderconfigureRateLimiting 方法中:


use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;

protected function configureRateLimiting()
{
    // 默认 API 限流:每分钟 60 次
    RateLimiter::for('api', function ($job) {
        return Limit::perMinute(60)->by($job->user()?->id ?: $job->ip());
    });
}
                    

2. 自定义限流器

你可以为不同场景定义多个限流器,例如登录尝试、文件上传等。在 RouteServiceProviderconfigureRateLimiting 中添加:


RateLimiter::for('login', function ($job) {
    return Limit::perMinute(5)->by($job->ip());
});

RateLimiter::for('uploads', function ($job) {
    return Limit::perMinute(10)->by($job->user()?->id ?: $job->ip());
});
                    

然后在路由中使用这些限流器:


Route::post('/login', [AuthController::class, 'login'])->middleware('throttle:login');
Route::post('/upload', [FileController::class, 'upload'])->middleware('throttle:uploads');
                    

3. 动态限流(基于用户等级)

你可以根据认证用户的不同等级设置不同的限制次数:


RateLimiter::for('api', function ($job) {
    $user = $job->user();
    $maxRequests = $user && $user->isPremium() ? 200 : 60;
    return Limit::perMinute($maxRequests)->by($user?->id ?: $job->ip());
});
                    

4. 路由级别限流

你可以直接在路由上使用 throttle 中间件并传递参数:throttle:次数,分钟


// 每分钟最多 10 次
Route::get('/search', [SearchController::class, 'index'])->middleware('throttle:10,1');
                    

也可以使用多个限流器组合:


// 先按 IP 限制 100 次/分钟,再按用户 ID 限制 10 次/分钟
Route::get('/api/data')->middleware('throttle:100,1', 'throttle:10,1:user_id');
                    

5. 限流响应与头信息

当超过限制时,Laravel 默认返回 429 Too Many Requests 响应,并携带以下头信息:

  • X-RateLimit-Limit – 最大请求次数
  • X-RateLimit-Remaining – 剩余次数
  • Retry-After – 需要等待的秒数

你可以自定义限流响应的内容,通过抛出 ThrottleRequestsException 或自定义中间件实现。

⚡ 性能提示: 生产环境建议使用 Redis 作为缓存驱动(CACHE_STORE=redis),因为限流器依赖缓存进行计数,Redis 的原子性操作更适合高频限流。

二、API 版本控制(Versioning)

Laravel 本身没有内置强制的版本控制方式,但提供了多种灵活的实现模式。以下是三种常见策略:

1. URL 路径版本(推荐)

将版本号直接放在 URL 中,例如 /api/v1/users/api/v2/users。这种方式最直观,易于缓存和调试。


// routes/api.php
Route::prefix('v1')->group(function () {
    Route::get('/users', [V1\UserController::class, 'index']);
});

Route::prefix('v2')->group(function () {
    Route::get('/users', [V2\UserController::class, 'index']);
});
                    

你可以将不同版本的控制器放在不同的命名空间下,例如 App\Http\Controllers\Api\V1App\Http\Controllers\Api\V2

2. 请求头版本(Accept Header 或自定义头)

客户端通过 Accept 头或自定义头(如 X-API-Version)指定版本,路由根据版本动态分发。


// 基于 Accept 头的示例: Accept: application/vnd.myapi.v1+json
Route::match(['get', 'post'], '/users', function () {
    $version = request->header('Accept');
    if (str_contains($version, 'v1')) {
        return app()->call('App\Http\Controllers\Api\V1\UserController@index');
    } elseif (str_contains($version, 'v2')) {
        return app()->call('App\Http\Controllers\Api\V2\UserController@index');
    }
    abort(406, 'API version not supported');
});
                    

更优雅的方式是使用中间件解析版本并动态调用控制器。

3. 子域名版本(不常用)

将版本放在子域名上,如 v1.api.example.com/users。需要在 config/routes.php 中配置子域名路由组。


Route::domain('v1.api.example.com')->group(function () {
    Route::get('/users', [V1\UserController::class, 'index']);
});
                    

4. 使用资源类进行版本兼容

当 API 返回的数据结构发生变化时,可以使用不同的 API 资源类来适配版本,而不必创建全新的控制器。


// 根据版本返回不同资源
if ($version === 'v1') {
    return new V1\UserResource($user);
} else {
    return new V2\UserResource($user);
}
                    

5. 最佳实践:结合限流与版本控制

你可以为不同版本的 API 设置不同的限流策略。例如,新版 API 可能更严格:


RateLimiter::for('api_v1', function ($job) {
    return Limit::perMinute(100)->by($job->ip());
});

RateLimiter::for('api_v2', function ($job) {
    return Limit::perMinute(50)->by($job->ip());
});

// 路由中分别应用
Route::prefix('v1')->middleware('throttle:api_v1')->group(...);
Route::prefix('v2')->middleware('throttle:api_v2')->group(...);
                    

三、最佳实践与常见问题

  • 限流粒度: 按用户(认证后)比按 IP 更精确,避免误伤同一 IP 下的多个用户。
  • 限流键设计: 使用 by($user->id ?: $request->ip()) 可以同时处理认证和未认证用户。
  • 返回友好提示: 在限流响应中建议包含 Retry-After 和说明文档链接。
  • 版本声明策略: URL 路径版本是最清晰、最不容易出错的方式,强烈推荐。
  • 保持向后兼容: 旧版本应至少维护一段时间,避免强制客户端升级。
  • 使用 API 网关: 对于微服务架构,限流和版本控制更适合在网关层实现。

四、完整示例:整合限流与版本控制


// app/Providers/RouteServiceProvider.php 中定义限流器
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;

protected function configureRateLimiting()
{
    RateLimiter::for('api_v1', function ($job) {
        return Limit::perMinute(100)->by($job->user()?->id ?: $job->ip());
    });
    RateLimiter::for('api_v2', function ($job) {
        return Limit::perMinute(80)->by($job->user()?->id ?: $job->ip());
    });
}

// routes/api.php
Route::prefix('v1')->middleware('throttle:api_v1')->group(function () {
    Route::apiResource('users', V1\UserController::class);
});

Route::prefix('v2')->middleware('throttle:api_v2')->group(function () {
    Route::apiResource('users', V2\UserController::class);
});
                    
📖 扩展阅读: Laravel Rate LimitingController 命名空间 可提供更多细节。