Laravel 速率限制器

Laravel RateLimiter

我是 Laravel 的新手,目前使用的 API 限制为每分钟 25 个请求。 我有一个控制器方法 sendRequest(),所有方法都使用它向 API 发送请求,所以我认为这是放置速率限制器的地方,它检查当前请求是否可以添加到如果尚未达到限制,请排队。

我在想这样的事情:

protected function sendRequest(){
    if ($this->allowRequest()) {
        //proceed to api call
    }
}

protected function allowRequest() {
    $allow = false;
    //probably a do-while loop to check if the limit has been reached within the timeframe?
}

我发现这个 class Illuminate\Cache\RateLimiter 我认为可能有用,但还不知道如何使用它。谁能用这个直接指出我正确的方向?所以基本上请求应该“等待”并仅在未达到 25 requests/minute 限制时执行。

谢谢!

Illuminate\Cache\RateLimiter class 有 hittooManyAttempts 方法,您可以像这样使用:

use Illuminate\Cache\RateLimiter;
use Illuminate\Http\Request;

protected function sendRequest()
{
    if ($this->hasTooManyRequests()) {
        // wait
        sleep(
            $this->limiter()
                ->availableIn($this->throttleKey()) + 1 // <= optional plus 1 sec to be on safe side
        );

        // Call this function again.
        return $this->sendRequest();
    }
    
    //proceed to api call
    $response = apiCall();

    // Increment the attempts
    $this->limiter()->hit(
        $this->throttleKey(), 60 // <= 60 seconds
    );

    return $response;
}

/**
 * Determine if we made too many requests.
 *
 * @return bool
 */
protected function hasTooManyRequests()
{
    return $this->limiter()->tooManyAttempts(
        $this->throttleKey(), 25 // <= max attempts per minute
    );
}

/**
 * Get the rate limiter instance.
 *
 * @return \Illuminate\Cache\RateLimiter
 */
protected function limiter()
{
    return app(RateLimiter::class);
}

/**
 * Get the throttle key for the given request.
 *
 * @return string
 */
protected function throttleKey()
{
    return 'custom_api_request';
}

有关更多可用方法,请参阅 Illuminate\Cache\RateLimiter class。

您也可以查看 Illuminate\Foundation\Auth\ThrottlesLogins 作为示例,了解如何使用 Illuminate\Cache\RateLimiter class。

注意RateLimiter 方法使用秒而不是分钟,因为 Laravel >= 5.8 并在 [=31 上获得了 major improvement =].

在这里您将需要共享计时器,例如控制传出请求的频率上限。创建一个 class,它将成为 Laravel 应用程序的单例,并且可以在请求中共享。

class FrequencyCapper{
    protected $start, $call, $request_frequency, $limit_interval;
    
    public function __construct($frequency, $interval_in_minute){
        $this->start = time();
        $this->call = 0;
        $this->request_frequency = frequency; // frequency of call
        $this->limit_interval = $interval_in_minute; // in minutes
    } 

    protected function allowRequest(){
        $diff = time() - $this->start;
        if($diff >= 60 * $this->limit_interval){
            $this->start = time();
            $this->call = 0;
        }
    
        return $diff <  60 * $this->limit_interval && $this->call < $this->request_frequency){
            $this->call++;
            return true;
        }else{
            return false;
        }
    }
}

现在,将此 class 作为单例附加到 laravel 服务容器中。在 App\Providers\AppServiceProvider.php 的引导方法中绑定单例。

$this->app->singleton('FrequencyCapper', function ($app) {
     return new FrequencyCapper(25, 1); //max 25 request per minute
});

现在,这个 class 将作为依赖项对所有控制器可用。您可以将 FrequencyCapper 依赖项注入给定的任何控制器方法,

class MyController extends Controller{
 
    protected function sendRequest(FrequencyCapper $capper){
        if($capper->allowRequest()){
            //you can call the api
        }
    }
}

如果你愿意,你可以在FrequencyCapperclass中使用microtime()代替time()。如果您想将传入请求限制为您自己的 api,您可以使用 laravel 的 throttle 中间件,

Route::group(['prefix' => 'api', 'middleware' => 'throttle:25,1'], function(){
    //define your routes here
});

对于Laravel 8和PHP 7.4我以

为例

并使这个特征:

<?php

namespace App\Traits;

use Closure;
use Illuminate\Cache\RateLimiter;

trait CustomRateLimiter {

    protected string $throttleKey = 'GeneralRateLimit';

    /**
     * Determine if we made too many requests.
     *
     * @param int $maxAttempts
     *
     * @return bool
     */
    protected function hasTooManyRequests( $maxAttempts = 10 ): bool {
        return $this->limiter()->tooManyAttempts(
            $this->throttleKey(), $maxAttempts // <= max attempts per minute
        );
    }

    /**
     * Get the rate limiter instance.
     *
     * @return RateLimiter
     */
    protected function limiter(): RateLimiter {
        return app( RateLimiter::class );
    }

    /**
     * Get the throttle key for the given request.
     *
     * @param string $key
     *
     * @return string
     */
    protected function throttleKey( $key = 'GeneralRateLimit' ): string {
        return $this->throttleKey ?? $key;
    }

    /**
     * @param Closure $callback Anonymous function to be executed - example: function(){ return realFunction();}
     * @param int $maxAttempts Maximum number of hits before process sleeps
     * @param string $throttleKey If you have different Apis, change this key to a single key.
     * @param int $decaySeconds Time that will sleep when the condition of $maxAttempts is fulfilled
     * @param int $optionalSecond Optional plus secs to be on safe side
     *
     * @return mixed
     */
    protected function sendRequest( Closure $callback, $maxAttempts = 10, $throttleKey = 'GeneralRateLimit', $decaySeconds = 1, $optionalSecond = 1 ) {
        $this->throttleKey = $throttleKey;

        if ( $this->hasTooManyRequests( $maxAttempts ) ) {
            // wait
            sleep( $this->limiter()->availableIn( $this->throttleKey() ) + $optionalSecond );

            // Call this function again.
            return $this->sendRequest( $callback, $maxAttempts, $throttleKey, $decaySeconds, $optionalSecond );
        }

        //proceed to api call
        $response = $callback();

        // Increment the attempts
        $this->limiter()->hit(
            $this->throttleKey(), $decaySeconds // <= 1 seconds
        );

        return $response;
    }

}

如何使用?

use App\Traits\CustomRateLimiter;
class MyClass {
    use CustomRateLimiter;
    public function realApiToCall($var1){
     // custom logic
    }

    public function apiCall($var1){
     $apiResponse = $this->sendRequest( function () use ( $var1 ) {
            return $this->realApiToCall($var1);
        }, 4, 'customKey1', 1 );
    }
}