laravel ratelimiter需满足三个前提:缓存驱动必须为redis并验证连通性、限流键须稳定唯一且by()返回纯字符串、命名策略须在appserviceprovider中注册并在路由中显式引用;cdn后失效、vip与游客共用计数器等问题均源于未落实这三点。

接口防刷必备:Laravel RateLimiter核心用法总结【开发技巧】——你正在为高频API加防护,但发现throttle中间件在CDN后全失效、VIP用户和游客共用一个计数器、验证码接口被连点刷爆、Redis配置对了却还是限流不生效。这些问题不是配置少写了一个参数,而是没踩准RateLimiter的三个执行前提:缓存驱动真实可用、限流键稳定唯一、策略注册与路由引用严格匹配。
确认缓存驱动已指向Redis(不可跳过)
打开.env文件,将CACHE_DRIVER=redis;若仍为array或file,多进程下每个worker维护独立计数器,限流彻底失效。
运行php artisan tinker,执行cache()->store('redis')->put('rate_test', 'ok', 10),返回true才算Redis连通成功。
【必须验证】config/cache.php中'default'值必须与中间件实际使用的store一致——比如中间件走redis,但配置里default是memcached,限流会静默失败。
注册命名限流策略(非匿名写法)
在app/Providers/AppServiceProvider.php的boot()方法中,调用RateLimiter::for()定义策略:
方法一:按登录态区分基础配额
RateLimiter::for('api', function (Request $request) {
$key = $request->user()?->id ?? 'guest_'.($request->ip() ?: 'unknown');
return $request->user() ? Limit::perMinute(100)->by($key) : Limit::perMinute(10)->by($key);
});
方法二:强制业务标识隔离(推荐用于App/小程序)
RateLimiter::for('api-by-device', function (Request $request) {
$device = $request->header('X-Device-ID');
if (!$device) {
abort(400, 'Missing X-Device-ID header');
}
return Limit::perMinute(30)->by(str_slug($device));
});
【关键细节】by()返回值必须是纯字符串,含空格或斜杠会导致Redis key写入失败,且不会报错——请求直接绕过限流。
在路由中正确引用策略
第一步:确保路由组已启用throttle中间件支持
检查app/Http/Kernel.php,$middlewareGroups['api']中必须包含\Illuminate\Routing\Middleware\ThrottleRequests::class。
第二步:路由定义时使用命名策略名,而非参数列表
✅ 正确写法:Route::middleware('throttle:api-by-device')->group(function () { ... });
❌ 错误写法:Route::middleware('throttle:30,1,by=X-Device-ID')->group(...) —— 这种写法触发的是匿名策略,不会调用你注册的for()闭包逻辑。
第三步:Web路由需额外声明
Laravel默认只对api路由组启用RateLimiter,若要在web.php中限流,必须在RouteServiceProvider::configureRateLimiting()中显式注册策略,并确保web中间件组包含ThrottleRequests。
手动调用限流器(验证码/登录等敏感操作)
在SendVerificationCodeController@store中,不依赖路由中间件,直接控制手机号维度:
$key = 'send-code-'.request('phone');
if (RateLimiter::tooManyAttempts($key, 3)) {
$seconds = RateLimiter::availableIn($key);
return response()->json(['message' => "发送超限,请{$seconds}秒后重试"], 429);
}
RateLimiter::hit($key, 60); // 计数+1,有效期60秒
这一步必须放在短信网关调用前——否则网关失败但计数已增加,用户无法重试。











