RateLimiter Facade 的机制
Laravel 的限流由 Illuminate\Cache\RateLimiting\Limit 类与 RateLimiter Facade 组成。内部将计数器保存在缓存驱动中(默认为 file 或 redis),以此追踪请求数。
throttle 中间件对接收到的请求执行由 RateLimiter::for() 定义的闭包,一旦达到限制便返回 429 Too Many Requests。
在 AppServiceProvider 中定义自定义 Limiter
限流配置写在 App\Providers\AppServiceProvider 的 boot() 方法中。
<? php
namespace App\Providers ;
use Illuminate\Cache\RateLimiting\ Limit ;
use Illuminate\Http\ Request ;
use Illuminate\Support\Facades\ RateLimiter ;
use Illuminate\Support\ ServiceProvider ;
class AppServiceProvider extends ServiceProvider
{
public function boot () : void
{
RateLimiter :: for ( 'api' , function ( Request $request ) {
return Limit :: perMinute ( 60 ) -> by ( $request -> user () ?-> id ?: $request -> ip ());
});
}
}
RateLimiter::for() 的第 1 个参数是 Limiter 名,在 throttle 中间件中引用。第 2 个参数的闭包需要返回 Illuminate\Cache\RateLimiting\Limit 实例。
按用户、IP、套餐限流
对已登录用户与访客采用不同限制
RateLimiter :: for ( 'uploads' , function ( Request $request ) {
return $request -> user ()
? Limit :: perHour ( 100 ) -> by ( $request -> user () -> id )
: Limit :: perHour ( 10 ) -> by ( $request -> ip ());
});
根据用户套餐限制
RateLimiter :: for ( 'api' , function ( Request $request ) {
$user = $request -> user ();
if ( ! $user ) {
return Limit :: perMinute ( 30 ) -> by ( $request -> ip ());
}
return match ( $user -> plan ) {
'enterprise' => Limit :: none (),
'pro' => Limit :: perMinute ( 500 ) -> by ( $user -> id ),
default => Limit :: perMinute ( 60 ) -> by ( $user -> id ),
};
});
按 IP 全局限流
无论访问哪个端点,都按 IP 限流。
RateLimiter :: for ( 'global' , function ( Request $request ) {
return Limit :: perMinute ( 1000 ) -> by ( $request -> ip ());
});
组合多个限制
以数组形式返回时,所有限制都会被评估,只要触达其中之一就返回 429。
RateLimiter :: for ( 'login' , function ( Request $request ) {
return [
Limit :: perMinute ( 10 ) -> by ( $request -> ip ()),
Limit :: perMinute ( 5 ) -> by ( $request -> input ( 'email' )),
];
});
如果多个限制共享同一 by 值,请为 key 加上前缀以避免冲突。
RateLimiter :: for ( 'uploads' , function ( Request $request ) {
return [
Limit :: perMinute ( 10 ) -> by ( 'minute:' . $request -> user () -> id ),
Limit :: perDay ( 1000 ) -> by ( 'day:' . $request -> user () -> id ),
];
});
通过 throttle 中间件指定自定义 Limiter 名
将已定义的 Limiter 名传给 throttle 中间件。
use Illuminate\Support\Facades\ Route ;
Route :: middleware ([ 'throttle:api' ]) -> group ( function () {
Route :: get ( '/user' , function () { /* ... */ });
Route :: post ( '/posts' , function () { /* ... */ });
});
在 bootstrap/app.php 中注册
Laravel 11 之后,中间件在 bootstrap/app.php 中管理。
use Illuminate\Foundation\ Application ;
return Application :: configure ( basePath : dirname ( __DIR__ ))
-> withRouting (
web : __DIR__ . '/../routes/web.php' ,
api : __DIR__ . '/../routes/api.php' ,
apiPrefix : 'api' ,
)
-> withMiddleware ( function ( \Illuminate\Foundation\Configuration\ Middleware $middleware ) : void {
$middleware -> throttleApi ( 'api' );
})
-> create ();
应用于 API 路由的示例
定义 Limiter
在 AppServiceProvider 中定义多个 Limiter。 public function boot () : void
{
// 通用 API 访问
RateLimiter :: for ( 'api' , function ( Request $request ) {
return Limit :: perMinute ( 60 ) -> by ( $request -> user () ?-> id ?: $request -> ip ());
});
// 文件上传
RateLimiter :: for ( 'uploads' , function ( Request $request ) {
return $request -> user () ?-> isPro ()
? Limit :: perHour ( 500 ) -> by ( $request -> user () -> id )
: Limit :: perHour ( 50 ) -> by ( $request -> user () ?-> id ?: $request -> ip ());
});
// 登录尝试
RateLimiter :: for ( 'login' , function ( Request $request ) {
return [
Limit :: perMinute ( 10 ) -> by ( $request -> ip ()),
Limit :: perMinute ( 5 ) -> by ( $request -> input ( 'email' )),
];
});
}
将中间件应用到路由
// routes/api.php
use Illuminate\Support\Facades\ Route ;
Route :: middleware ([ 'auth:sanctum' , 'throttle:api' ]) -> group ( function () {
Route :: get ( '/user' , [ \App\Http\Controllers\ UserController :: class , 'show' ]);
Route :: get ( '/posts' , [ \App\Http\Controllers\ PostController :: class , 'index' ]);
});
Route :: middleware ([ 'auth:sanctum' , 'throttle:uploads' ]) -> group ( function () {
Route :: post ( '/uploads' , [ \App\Http\Controllers\ UploadController :: class , 'store' ]);
});
Route :: middleware ([ 'throttle:login' ]) -> group ( function () {
Route :: post ( '/login' , [ \App\Http\Controllers\ AuthController :: class , 'login' ]);
});
响应头(X-RateLimit-*)机制
throttle 中间件会自动为响应加上限流信息头。
头 说明 X-RateLimit-Limit允许的请求数 X-RateLimit-Remaining剩余请求数 Retry-After下一次可请求前的秒数(仅 429 时) X-RateLimit-Reset限流重置的 UNIX 时间戳
HTTP / 1.1 429 Too Many Requests
X-RateLimit-Limit : 60
X-RateLimit-Remaining : 0
Retry-After : 45
X-RateLimit-Reset : 1717000000
Content-Type : application/json
{
"message" : "Too Many Requests."
}
自定义响应
RateLimiter :: for ( 'api' , function ( Request $request ) {
return Limit :: perMinute ( 60 )
-> by ( $request -> user () ?-> id ?: $request -> ip ())
-> response ( function ( Request $request , array $headers ) {
return response () -> json ([
'message' => '请求频率超出限制,请稍后重试。' ,
'retry_after' => $headers [ 'Retry-After' ],
], 429 , $headers );
});
});
使用 RateLimiter::attempt() 手动检查
若不使用 throttle 中间件,而想在代码任意位置检查限流,可以使用 RateLimiter::attempt()。
use Illuminate\Support\Facades\ RateLimiter ;
class SmsController extends Controller
{
public function send ( Request $request ) : \Illuminate\Http\ JsonResponse
{
$key = 'sms:' . $request -> user () -> id ;
$executed = RateLimiter :: attempt (
key : $key ,
maxAttempts : 5 ,
callback : function () use ( $request ) {
app ( SmsService :: class ) -> send (
$request -> user () -> phone ,
$request -> input ( 'message' )
);
},
decaySeconds : 3600 , // 1 小时
);
if ( ! $executed ) {
$seconds = RateLimiter :: availableIn ( $key );
return response () -> json ([
'message' => "SMS 发送已达上限,请 { $seconds } 秒后再试。" ,
], 429 );
}
return response () -> json ([ 'message' => '已发送 SMS。' ]);
}
}
查询与重置尝试次数
// 获取当前尝试次数
$hits = RateLimiter :: attempts ( $key );
// 到下次重置的秒数
$seconds = RateLimiter :: availableIn ( $key );
// 判断是否已达上限
$tooMany = RateLimiter :: tooManyAttempts ( $key , $maxAttempts = 5 );
// 手动重置计数器(例如登出后)
RateLimiter :: clear ( $key );
登录节流示例
public function login ( Request $request ) : mixed
{
$key = 'login:' . $request -> input ( 'email' );
if ( RateLimiter :: tooManyAttempts ( $key , 5 )) {
$seconds = RateLimiter :: availableIn ( $key );
throw ValidationException :: withMessages ([
'email' => "登录尝试次数过多,请 { $seconds } 秒后再试。" ,
]);
}
if ( ! Auth :: attempt ( $request -> only ( 'email' , 'password' ))) {
RateLimiter :: hit ( $key , 300 ); // 5 分钟内计数
throw ValidationException :: withMessages ([
'email' => '邮箱或密码不正确。' ,
]);
}
RateLimiter :: clear ( $key );
return redirect () -> intended ( '/dashboard' );
}
基于响应的限流
若只想统计特定响应,使用 after()。以下示例只统计 404,用来防止资源枚举攻击:
use Symfony\Component\HttpFoundation\ Response ;
RateLimiter :: for ( 'resource-lookup' , function ( Request $request ) {
return Limit :: perMinute ( 10 )
-> by ( $request -> user () ?-> id ?: $request -> ip ())
-> after ( function ( Response $response ) {
return $response -> getStatusCode () === 404 ;
});
});
基于 Redis 的限流
只要把默认缓存驱动改为 Redis,throttle 中间件也会自动使用 Redis。
Redis 驱动配置
// config/cache.php
'default' => env ( 'CACHE_DRIVER' , 'redis' ),
# .env
CACHE_DRIVER =redis
REDIS_HOST =127.0.0.1
REDIS_PORT =6379
使用 throttleWithRedis
若要使用 Redis 专用优化的节流中间件,请在 bootstrap/app.php 中调用 throttleWithRedis()。
use Illuminate\Foundation\ Application ;
return Application :: configure ( basePath : dirname ( __DIR__ ))
-> withRouting (
web : __DIR__ . '/../routes/web.php' ,
api : __DIR__ . '/../routes/api.php' ,
apiPrefix : 'api' ,
)
-> withMiddleware ( function ( \Illuminate\Foundation\Configuration\ Middleware $middleware ) : void {
$middleware -> throttleWithRedis ();
})
-> create ();
由此 throttle 中间件会映射到 ThrottleRequestsWithRedis 类,通过 Redis 的原子操作实现精确计数。
使用 throttleWithRedis() 前请确保 Redis 可用。若无法连接 Redis,可能导致所有请求被拒绝。
使用 Redis 的收益
可横向扩展 — 多个服务器实例可共享计数器
高精度 — 原子操作避免竞态
TTL 管理 — 利用 Redis 原生过期机制自动清理计数器
相关页面
缓存 了解包含 Redis 在内的 Laravel 缓存驱动的配置与用法。