简介

Laravel 内置了一个易于使用的速率限制抽象,它与应用的 缓存 结合,提供了一种简便的方式,可在指定的时间窗口内限制任意操作。

如果您对限制传入的 HTTP 请求感兴趣,请查阅速率限制中间件文档。

缓存配置

通常,速率限制器会使用应用 cache 配置文件中 default 键所定义的默认应用缓存。不过,您也可以通过在应用的 cache 配置文件中定义 limiter 键,来指定速率限制器应使用的缓存驱动:


'default' => env('CACHE_STORE', 'database'),

'limiter' => 'redis',

基本用法

可以使用 Illuminate\Support\Facades\RateLimiter 门面与速率限制器交互。速率限制器提供的最简单方法是 attempt 方法,该方法会对给定回调函数在指定的秒数内进行速率限制。

当回调函数没有剩余可用尝试次数时,attempt 方法会返回 false;否则,attempt 方法将返回回调函数的执行结果或 true。attempt 方法接受的第一个参数是速率限制器的“键”,该键可以是您自行选择的任意字符串,用于表示要进行速率限制的操作:

use Illuminate\Support\Facades\RateLimiter;

$executed = RateLimiter::attempt(
    'send-message:'.$user->id,
    $perMinute = 5,
    function() {
        // Send message...
    }
);

if (! $executed) {
    return 'Too many messages sent!';
}

必要时,您可以为 attempt 方法提供第四个参数,即“衰减速率”,也就是可用尝试次数重置前的秒数。例如,我们可以修改上述示例,使其每两分钟允许五次尝试:

$executed = RateLimiter::attempt(
    'send-message:'.$user->id,
    $perTwoMinutes = 5,
    function() {
        // Send message...
    },
    $decayRate = 120,
);

手动递增尝试次数

如果您希望手动与速率限制器交互,还提供了多种其他方法。例如,您可以调用 tooManyAttempts 方法,判断给定的速率限制器键是否已超过每分钟允许的最大尝试次数:

use Illuminate\Support\Facades\RateLimiter;

if (RateLimiter::tooManyAttempts('send-message:'.$user->id, $perMinute = 5)) {
    return 'Too many attempts!';
}

RateLimiter::increment('send-message:'.$user->id);

// Send message...

当对可能接收大量并发请求的端点进行速率限制时,您可能希望检查 increment 方法返回的值,而不是将 tooManyAttempts 和 increment 作为独立操作使用。当使用 redis、memcached 或 database 缓存存储时,该值会以原子方式递增,确保每个并发请求都能获得唯一的计数:

use Illuminate\Support\Facades\RateLimiter;

$perMinute = 5;

if (RateLimiter::increment('send-message:'.$user->id) > $perMinute) {
    return 'Too many attempts!';
}

// Send message...

或者,您可以使用 remaining 方法获取给定键剩余的尝试次数。如果给定键还有剩余重试次数,您可以调用 increment 方法来增加总尝试次数:

use Illuminate\Support\Facades\RateLimiter;

if (RateLimiter::remaining('send-message:'.$user->id, $perMinute = 5)) {
    RateLimiter::increment('send-message:'.$user->id);

    // Send message...
}

如果您想将给定速率限制器键对应的值增加超过 1 的次数,可以向 increment 方法传入期望的数量:

RateLimiter::increment('send-message:'.$user->id, amount: 5);

判断限制器可用性

当某个键没有剩余尝试次数时,availableIn 方法会返回距离可再次尝试的剩余秒数:

use Illuminate\Support\Facades\RateLimiter;

if (RateLimiter::tooManyAttempts('send-message:'.$user->id, $perMinute = 5)) {
    $seconds = RateLimiter::availableIn('send-message:'.$user->id);

    return 'You may try again in '.$seconds.' seconds.';
}

RateLimiter::increment('send-message:'.$user->id);

// Send message...

清除尝试次数

您可以使用 clear 方法重置给定速率限制器键的尝试次数。例如,当接收者读取某条消息时,您可以重置尝试次数:

use App\Models\Message;
use Illuminate\Support\Facades\RateLimiter;

/**
 * Mark the message as read.
 */
public function read(Message $message): Message
{
    $message->markAsRead();

    RateLimiter::clear('send-message:'.$message->user_id);

    return $message;
}