Laravel API限流需统一返回JSON:全局捕获ThrottleRequestsException并自定义响应;或按路由使用JsonThrottleResponse中间件;务必确保rate_limiter使用Redis等支持原子操作的缓存驱动。

当Laravel的throttle中间件触发请求超限时,默认抛出ThrottleRequestsException并返回429 HTML页面,但API场景下需要统一返回JSON格式、自定义状态码和错误结构,且不能影响其他异常处理流程。
全局异常处理器中捕获ThrottleRequestsException
打开app/Exceptions/Handler.php,在render方法顶部添加异常类型判断:
if ($exception instanceof \Illuminate\Http\Exceptions\ThrottleRequestsException) {
return response()->json([
'message' => 'Too many requests',
'code' => 429,
'retry_after' => $exception->getHeaders()['Retry-After'] ?? 0
], 429)->header('Content-Type', 'application/json');
}
这一步必须放在parent::render()调用之前,否则会被父类兜底处理为HTML响应。
【关键点】不要修改$exception->getHeaders()返回值——它由ThrottleRequests中间件内部生成,直接读取即可复用;手动构造Retry-After易与Redis窗口时间错位。
按路由粒度定制限流响应
若仅对部分API路由启用JSON限流响应,不希望全局生效:
第一步:新建中间件php artisan make:middleware JsonThrottleResponse
第二步:在handle方法中检查响应状态码是否为429,且当前请求是API类型:
if ($response->getStatusCode() === 429 && $request->expectsJson()) {
return response()->json([
'error' => 'rate_limited',
'limit' => (int) $response->headers->get('X-RateLimit-Limit'),
'remaining' => (int) $response->headers->get('X-RateLimit-Remaining')
], 429);
}
第三步:将该中间件注册到Kernel.php的$routeMiddleware数组,并在对应路由组中使用:
Route::middleware(['throttle:10,1', 'json.throttle'])->group(function () { ... });
注意:此中间件必须放在throttle之后,否则拿不到429响应。
避免缓存驱动失效导致限流不触发
本地开发常用file缓存,但ThrottleRequests依赖原子操作(如incr),file驱动无法保证计数准确性:
检查config/cache.php中'default'值是否为'redis'或'memcached';
确认config/cache.php里rate_limiter store配置存在且指向真实Redis连接;
运行php artisan tinker执行:
Cache::store('rate_limiter')->increment('test_key')
若返回null或0,说明rate_limiter store未生效,需检查CACHE_DRIVER环境变量及Redis连接配置。
【致命陷阱】生产环境CACHE_DRIVER=redis但REDIS_HOST为空时,Laravel会静默fallback到array驱动——限流完全失效且无日志报错。


















