Laravel限流:不同请求次数设不同延迟及IP封禁问题排查
Laravel限流中间件问题排查与修复
核心问题分析
你的代码存在三个关键错误:
- 尝试次数判断时机错误:在调用
hit()前获取attempts(),此时的次数是历史请求数,导致冷却时间逻辑完全不匹配需求(比如第一次请求时attempts()返回0,错误进入10分钟冷却分支)。 - 限流逻辑偏差:
tooManyAttempts()是判断"请求次数超过阈值",但你的需求是"每次请求后,下一次必须等待对应时间",应该检查是否处于冷却期而非次数超限。 - 未实现高频封禁规则:原代码完全没处理"1分钟10次请求封禁3小时"的需求。
修复后的完整代码
public function handle($request, Closure $next) { $limiter = app(RateLimiter::class); $ip = $request->ip(); $progressKey = sha1($ip . '_progress'); // 递进延迟的独立限流标识 $banKey = sha1($ip . '_ban'); // 高频封禁的独立限流标识 // 先处理高频封禁规则:1分钟内10次请求封禁3小时 if ($limiter->tooManyAttempts($banKey, 10)) { $limiter->hit($banKey, 3 * 60); // 延长封禁时间至3小时(单位:分钟) return response('Your IP has been banned for 3 hours due to excessive requests.', 429); } $limiter->hit($banKey, 1); // 记录本次请求,冷却时间1分钟 // 处理递进延迟规则 $currentAttempts = $limiter->attempts($progressKey); // 根据当前请求次数设置下一次的冷却时间(单位:秒) if ($currentAttempts === 0) { $coolDownSeconds = 30; // 第1次请求后,下一次需等30秒 } elseif ($currentAttempts === 1) { $coolDownSeconds = 60; // 第2次请求后,下一次需等60秒 } else { $coolDownSeconds = 120; // 第3次及以后请求后,下一次需等120秒 } // 检查是否处于冷却期 if ($limiter->availableIn($progressKey) > 0) { $retryAfter = $limiter->availableIn($progressKey); return response("Please wait {$retryAfter} seconds before your next request.", 429); } // 处理请求并设置冷却时间(hit的第二个参数是分钟,需转换单位) $limiter->hit($progressKey, $coolDownSeconds / 60); $response = $next($request); return $response; }
关键修改说明
- 拆分限流标识:用两个独立的key分别处理递进延迟和高频封禁,避免规则互相干扰。
- 高频封禁逻辑:
- 用
$banKey统计1分钟内的请求次数,阈值设为10次。 - 达到阈值后,直接返回封禁提示并设置3小时冷却,确保封禁期间无法发起请求。
- 用
- 递进延迟逻辑:
- 根据当前已有的请求次数,动态设置下一次请求的冷却时间。
- 用
availableIn($key)检查是否处于冷却期,只要返回值大于0,就拒绝当前请求。
- 修正时间单位:Laravel的
hit()方法第二个参数是分钟,所以需要将秒数转换为分钟传入(比如30秒=0.5分钟)。
内容的提问来源于stack exchange,提问作者Mimi
相关产品推荐
相关产品推荐

