5

我对 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?
}

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

谢谢!

4

3 回答 3

7

该类Illuminate\Cache\RateLimiter具有您可以像这样使用的方法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类。

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

注意:这些RateLimiter方法使用秒而不是分钟,因为 Laravel >= 5.8 并且在 v8.x 上得到了重大改进。

于 2020-06-29T16:03:36.567 回答
1

在这里,您需要共享计时器,例如控制传出请求的频率上限。创建一个类,它将成为 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;
        }
    }
}

现在,将这个类作为一个单例附加到 laravel 服务容器中。App\Providers\AppServiceProvider.php在's boot 方法中绑定单例。

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

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

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

如果你愿意,你可以在课堂上使用microtime()time() 。FrequencyCapper如果你想限制传入的请求到你自己的 api,你可以使用 laravel 的throttle中间件,

Route::group(['prefix' => 'api', 'middleware' => 'throttle:25,1'], function(){
    //define your routes here
});
于 2020-06-29T16:37:01.207 回答
1

对于 Laravel 8 和 PHP 7.4,我以https://stackoverflow.com/a/62642143/3145399为例

并制作此特征:

<?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 );
    }
}
于 2021-03-25T21:56:41.970 回答