Laravel自定义throttle节流中间件返回布尔值参数的实现方案
自定义Laravel限流中间件实现方案
前置说明
你预期的直接给Route::group闭包传$tooManyAttempts参数的写法无法实现:路由组闭包在项目启动、路由注册阶段执行,而中间件逻辑在实际收到用户请求阶段才运行,二者运行时机完全不同,参数无法跨阶段传递。
正确方案是将限流状态写入请求实例的属性,在路由/控制器中直接从请求对象读取即可。
具体实现步骤
1. 新建自定义限流中间件
不要修改vendor目录下的原生源码,在app/Http/Middleware目录下新建CustomThrottle.php,继承原生ThrottleRequests类,重写handleRequest方法:
<?php namespace App\Http\Middleware; use Illuminate\Routing\Middleware\ThrottleRequests as BaseThrottleRequests; use Closure; use Illuminate\Http\Request; class CustomThrottle extends BaseThrottleRequests { /** * 重写限流处理逻辑,不抛异常,将限流状态写入请求属性 */ protected function handleRequest($request, Closure $next, array $limits) { $tooManyAttempts = false; $triggeredLimit = null; $retryAfter = 0; foreach ($limits as $limit) { if ($this->limiter->tooManyAttempts($limit->key, $limit->maxAttempts)) { $tooManyAttempts = true; $triggeredLimit = $limit; $retryAfter = $this->getTimeUntilNextRetry($limit->key); break; } // 保留原有的计数逻辑 $this->limiter->hit($limit->key, $limit->decayMinutes * 60); } // 将限流相关参数写入请求属性 $request->attributes->set('tooManyAttempts', $tooManyAttempts); if ($tooManyAttempts) { $request->attributes->set('maxAttempts', $triggeredLimit->maxAttempts); $request->attributes->set('retryAfter', $retryAfter); $request->attributes->set('remainingAttempts', 0); } else { $firstLimit = $limits[0]; $request->attributes->set('maxAttempts', $firstLimit->maxAttempts); $request->attributes->set('remainingAttempts', $this->calculateRemainingAttempts($firstLimit->key, $firstLimit->maxAttempts)); } $response = $next($request); // 保留原有限流响应头添加逻辑 foreach ($limits as $limit) { $response = $this->addHeaders( $response, $limit->maxAttempts, $this->calculateRemainingAttempts($limit->key, $limit->maxAttempts, $tooManyAttempts ? $retryAfter : null) ); } return $response; } }
2. 注册中间件
打开app/Http/Kernel.php,在$routeMiddleware数组中新增注册项:
protected $routeMiddleware = [ // 其他原有中间件... 'customthrottle' => \App\Http\Middleware\CustomThrottle::class, ];
3. 使用示例
路由定义时直接从请求对象读取相关参数即可:
Route::middleware('customthrottle:10,1')->group(function () { Route::get('/test/throttle', function(\Illuminate\Http\Request $request) { // 读取限流状态 if ($request->attributes->get('tooManyAttempts')) { $retryAfter = $request->attributes->get('retryAfter'); return response("My custom 'Too many attempts' page only for this route,{$retryAfter}秒后重试", 200) ->header('Content-Type', 'text/html'); } else { $remaining = $request->attributes->get('remainingAttempts'); return response("You are good to go yet, he-he,剩余尝试次数:{$remaining}", 200) ->header('Content-Type', 'text/html'); } }); });
内容的提问来源于stack exchange,提问作者mr.incredible
相关产品推荐
相关产品推荐

