Laravel中如何实现RateLimiter的请求次数递减功能?
Laravel限流:验证失败时回退请求计数的解决方案
问题背景
你在开发Laravel票务预订系统时,为防止恶意用户反复点击锁定所有座位,给银行API路由加了RateLimiter限流,但遇到两个问题:
- 请求验证失败时,限流计数仍会递增,导致用户因输入错误被误封禁6分钟
- 你自定义的
unHit方法会把计数减到负数,而且没有维护缓存的过期时间
优化后的unHit方法
下面是修复了这两个问题的实现,处理计数边界并保留原限流的过期时间:
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\RateLimiter; public function unHit($key) { $currentCount = Cache::get($key); // 避免计数变成负数:如果当前计数为0或缓存不存在,直接返回 if ($currentCount === null || $currentCount <= 0) { return 0; } $newCount = Cache::decrement($key); // 计数归0时删除缓存,避免残留无效的0值缓存 if ($newCount <= 0) { Cache::forget($key); return 0; } // 重置缓存过期时间,和原限流规则的过期时间保持一致 $expiration = RateLimiter::availableIn($key) ?: 360; // 360秒对应6分钟封禁时间 Cache::put($key, $newCount, $expiration); return $newCount; }
如何在验证失败时调用
在请求验证失败的逻辑里触发unHit,比如在自定义请求类的failedValidation方法中:
use Illuminate\Contracts\Validation\Validator; use Illuminate\Http\Exceptions\HttpResponseException; use Illuminate\Foundation\Http\FormRequest; class PaymentRequest extends FormRequest { // 这里定义你的验证规则 public function rules() { return [ 'card_number' => 'required|numeric', 'expiry_date' => 'required|date_format:m/y', // 其他验证规则... ]; } protected function failedValidation(Validator $validator) { // 这里的限流键要和你路由中使用的一致,比如基于当前用户ID $limitKey = 'payment_api:' . auth()->id(); // 调用自定义的unHit回退计数 RateLimiter::unHit($limitKey); throw new HttpResponseException(response()->json([ 'message' => '输入信息有误', 'errors' => $validator->errors() ], 422)); } }
全局宏定义(可选)
可以把unHit注册成RateLimiter的全局宏,方便在项目各处调用:
// 在app/Providers/AppServiceProvider.php的boot方法中 public function boot() { RateLimiter::macro('unHit', function ($key) { $currentCount = Cache::get($key); if ($currentCount === null || $currentCount <= 0) { return 0; } $newCount = Cache::decrement($key); if ($newCount <= 0) { Cache::forget($key); return 0; } $expiration = RateLimiter::availableIn($key) ?: 360; Cache::put($key, $newCount, $expiration); return $newCount; }); }
内容的提问来源于stack exchange,提问作者Abw
相关产品推荐
相关产品推荐

