本文目录导读:

在 Laravel 中,默认的限流中间件 (throttle) 通常按 IP 地址 进行限流,而不是直接按用户 ID。
要实现按用户 ID 限流,需要手动指定 key。 有两种主要方式:
使用 throttle 中间件的动态参数(推荐)
在路由中,可以将 throttle 中间件的第三个参数(key)设置为用户的 ID,或者一个包含用户 ID 的字符串。
在 routes/api.php 中:
use Illuminate\Support\Facades\Route;
Route::middleware('auth:sanctum') // 假设使用 Sanctum 认证
->group(function () {
// 关键点:将 'throttle:10,1,user_id' 中的 'user_id' 换成动态的用户 ID
Route::get('/user-data', function () {
return auth()->user();
})->middleware('throttle:10,1,' . auth()->id()); // 直接拼接 user_id
});
更优雅的写法(使用路由组):
Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
Route::get('/user-data', function () { ... })->name('user.data');
});
然后在 App\Http\Kernel.php 的 $routeMiddleware 或 App\Providers\RouteServiceProvider.php 中定义动态限流逻辑:
// 在 RouteServiceProvider 的 boot 方法中
RateLimiter::for('api', function (Request $request) {
// 如果是认证用户,用用户 ID;否则用 IP
$key = $request->user()
? 'user_' . $request->user()->id
: 'ip_' . $request->ip();
return Limit::perMinute(10)->by($key);
});
优点:
- 清晰、易于管理
- 可以在 HTTP Kernel 中集中配置所有限流逻辑
使用 RateLimiter Facade 手动限流(更灵活)
在控制器中手动调用限流器,并以用户 ID 作为键值。
use Illuminate\Support\Facades\RateLimiter;
class UserController extends Controller
{
public function getProfile(Request $request)
{
$userId = auth()->id(); // 或者 $request->user()->id
// 定义 key,确保唯一性
$key = 'user_' . $userId . '_profile';
// 检查限流
$executed = RateLimiter::attempt(
$key,
$maxAttempts = 5, // 最多尝试 5 次
function() {
return true; // 执行成功
},
$decaySeconds = 60 // 60 秒后重置
);
if (! $executed) {
// 限流触发
$seconds = RateLimiter::availableIn($key);
return response()->json([
'error' => 'Too many requests',
'retry_after' => $seconds
], 429);
}
// 正常处理请求
return response()->json(['data' => '...']);
}
}
优点:
- 完全自定义限流逻辑(如按用户 ID + 路由 + 方法)
- 可以返回自定义错误消息和头部信息
自定义中间件(高级用法)
创建一个自定义中间件,在其中根据 user_id + IP 或只根据 user_id 生成限流 key。
// app/Http/Middleware/ThrottleByUser.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Cache\RateLimiter;
use Symfony\Component\HttpFoundation\Response;
class ThrottleByUser
{
public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1)
{
$key = 'user_' . (auth()->id() ?: $request->ip());
$limiter = app(RateLimiter::class);
if ($limiter->tooManyAttempts($key, $maxAttempts)) {
return response('Too Many Attempts.', 429);
}
$limiter->hit($key, $decayMinutes * 60);
$response = $next($request);
// 返回限流头部信息
return $this->addHeaders(
$response, $maxAttempts,
$limiter->attempts($key)
);
}
protected function addHeaders($response, $maxAttempts, $currentAttempts)
{
$response->headers->set('X-RateLimit-Limit', $maxAttempts);
$response->headers->set('X-RateLimit-Remaining', max(0, $maxAttempts - $currentAttempts));
return $response;
}
}
然后在 Kernel.php 注册:
protected $routeMiddleware = [
'throttle.user' => \App\Http\Middleware\ThrottleByUser::class,
];
使用:
Route::middleware(['auth:sanctum', 'throttle.user:10,1'])->group(...);
| 方式 | 是否按用户 ID | 适用场景 |
|---|---|---|
| 方式一(推荐) | ✅ 是 | 大多数情况,简单、Laravel 原生支持 |
| 方式二(手动) | ✅ 是 | 需要精细控制或复杂限流逻辑 |
| 方式三(自定义) | ✅ 是 | 需要完全自定义行为或复用中间件 |
最佳实践建议:
- 使用 方式一(在
RateLimiter::for中定义)最为简洁、可维护 - 如果限流逻辑简单且只在单一路由使用,可以选用方式二
- 如果需要在多个控制器/路由中复用复杂限流逻辑,使用方式三