You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 10:54:55