相信技术的力量

Laravel throttle限流中间件

Laravel默认的限流是针对IP,频率为60次/分钟,经过测试发现,如果多个IP属于同一个局域网,比如连接同一个WI-FI,则会判定为同一个IP,进行限流。

也就是说如果同一个局域网下有20人在使用,那么每人刷新3次,就达到了限流标准。 就会返回429 Too Many Requests ,而且是以Laravel默认的错误页面渲染的方式返回,如果不加以处理,在特定场景下无法满足业务需求。

自定义限流记录中间件

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Cache\RateLimiter;
use Symfony\Component\HttpFoundation\Response;

class ThrottleRequests
{
        /**
         * The rate limiter instance.
         *
         * @var \Illuminate\Cache\RateLimiter
         */
        protected $limiter;

        /**
         * Create a new request throttler.
         *
         * @param  \Illuminate\Cache\RateLimiter $limiter
         */
        public function __construct(RateLimiter $limiter)
        {
                $this->limiter = $limiter;
        }

        /**
         * Handle an incoming request.
         *
         * @param  \Illuminate\Http\Request $request
         * @param  \Closure $next
         * @param  int $maxAttempts
         * @param  int $decayMinutes
         * @return mixed
         */
        public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1)
        {
                $key = $this->resolveRequestSignature($request);

                if ($this->limiter->tooManyAttempts($key, $maxAttempts, $decayMinutes)) {
                        return $this->buildResponse($key, $maxAttempts);
                }

                $this->limiter->hit($key, $decayMinutes);

                $response = $next($request);

                return $this->addHeaders(
                        $response, $maxAttempts,
                        $this->calculateRemainingAttempts($key, $maxAttempts)
                );
        }

        /**
         * Resolve request signature.
         *
         * @param  \Illuminate\Http\Request $request
         * @return string
         */
        protected function resolveRequestSignature($request)
        {
                return $request->fingerprint();
        }

        /**
         * Create a 'too many attempts' response.
         *
         * @param  string $key
         * @param  int $maxAttempts
         * @return \Illuminate\Http\Response
         */
        protected function buildResponse($key, $maxAttempts)
        {
                $response = response()->json([
                        'code' => 429,
                        'message' => '您的请求过于频繁, 请稍后再试',
                ]);

                $retryAfter = $this->limiter->availableIn($key);

                return $this->addHeaders(
                        $response,
                        $maxAttempts,
                        $this->calculateRemainingAttempts($key, $maxAttempts, $retryAfter),
                        $retryAfter
                );
        }

        /**
         * Add the limit header information to the given response.
         *
         * @param  \Symfony\Component\HttpFoundation\Response $response
         * @param  int $maxAttempts
         * @param  int $remainingAttempts
         * @param  int|null $retryAfter
         * @return \Illuminate\Http\Response
         */
        protected function addHeaders(Response $response, $maxAttempts, $remainingAttempts, $retryAfter = null)
        {
                $headers = [
                        'X-RateLimit-Limit' => $maxAttempts,
                        'X-RateLimit-Remaining' => $remainingAttempts,
                ];

                if (!is_null($retryAfter)) {
                        $headers['Retry-After'] = $retryAfter;
                        $headers['Content-Type'] = 'application/json';
                }

                $response->headers->add($headers);

                return $response;
        }

        /**
         * Calculate the number of remaining attempts.
         *
         * @param  string $key
         * @param  int $maxAttempts
         * @param  int|null $retryAfter
         * @return int
         */
        protected function calculateRemainingAttempts($key, $maxAttempts, $retryAfter = null)
        {
                if (!is_null($retryAfter)) {
                        return 0;
                }

                return $this->limiter->retriesLeft($key, $maxAttempts);
        }
}

替换默认的中间件

1.编辑 app/Http/Kernel.php文件

    protected $routeMiddleware = [
                ......
                //注释掉默认的中间件
                //'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
                //使用自定义的中间件
                'throttle' => \App\Http\Middleware\ThrottleRequests::class,
        ];

2.根据需求调整限流阈值

protected $middlewareGroups = [
    'web' => [
       ......
    ],

    'api' => [
        'throttle:60,1', //60次/每分钟,根据需求调整
        'bindings',
    ],
];

(完)

参考

https://blog.csdn.net/luohai859/article/details/82995613

⬆️