Laravel :API 请求频率限制(Throttle中间件),自定义返回JSON类型,自定义时间

Laravel 自带了一个 Throttle 中间件,默认的设置是 1 分钟内请求超过 60 次就会触发这个,然后服务器就会返回 429 Too Many Requests

这个默认配置可以在 app\Http\Kernel.php 中看到

QQ截图20191007154542.png

限流原理

  • 获取唯一请求来源,进行唯一标识(key)
  • 获取该请求请求次数 (hits)
  • 判断是否超过最大限制
  • 若达到上限,进入5。未达到,则进入6
  • 丢出访问次数限制异常,结束请求。
  • 首先判断hits 是否达到限制,若未达到,进入7。若达到,进入8。
  • hits 进行计数 + 1,更新到缓存中。 若是第一次,则需要 hits = 1(次数), 并添加访问标识 key
    (1分钟)到缓存中,以标记请求周期。
  • 请求次数已达到上限(hits >= 60),此时需要判断是否在周期范围内(1分钟),若在周期内,进入9;不在周期内,进入10.
  • 此时请求处在 “1分钟内请求次数达到60次”,即达到限制,返回 false 。
  • 此时请求处在 “不在1分钟内请求次数达到60次”,即不在周期内,需要重新计算周期。

更多参考文档:https://www.cnblogs.com/toughlife/p/10601069.html

自定义返回的类型

Laravel 默认 返回的是一个 429 的 html 页面,做 api 的话这样不太好
我们新建一个中间件,来替换掉原来的中间件

artisan 命令新建一个中间件:php artisan make:middleware ThrottleRequests

编写代码:

继承原来的ThrottleRequests ,稍微修改一下就可以了。

如果限制时间要修改成秒的话, 请查看 :$this->limiter->hit($key,&decayMinutes * 60)

$decayMinutes * 60 ,如果 decayMinutes = 1 的话, 那么就是限制一分钟, 1*60

那么修改成 $this->limiter->hit($key,&decayMinutes),就成限制秒了

<?php

namespace App\Http\Middleware;


use Closure;
use Illuminate\Http\Response;

class ThrottleRequests extends \Illuminate\Routing\Middleware\ThrottleRequests
{


    public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1)
    {
        $key = $this->resolveRequestSignature($request);

        $maxAttempts = $this->resolveMaxAttempts($request, $maxAttempts);

        if ($this->limiter->tooManyAttempts($key, $maxAttempts)) {
            return $this->buildException($key, $maxAttempts);
          //throw $this->buildException($key, $maxAttempts);
         // 原来的是抛出异常,修改成直接返回
        }
       //去掉 `* 60` 限制秒级,加上去限制分钟,要限制其他单位,可以自己算的
        $this->limiter->hit($key, $decayMinutes);
        //$this->limiter->hit($key, $decayMinutes * 60);

        $response = $next($request);

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

    protected function buildException($key, $maxAttempts)
    {
        $retryAfter = $this->limiter->availableIn($key);

       //要返回的数据
        $message = json_encode([
            'code' => 429,
            'data' => null,
            'msg' => '您的请求太频繁,已被限制请求',
            'retryAfter' => $retryAfter,
        ], 320);

        $response = new Response($message, 200);

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

    }

    protected function addHeaders(\Symfony\Component\HttpFoundation\Response $response, $maxAttempts, $remainingAttempts, $retryAfter = null)
    {
     // 添加 `response` 头 为 `json`
        $response->headers->add(
            ['Content-Type' => 'application/json;charset=utf-8']
        );
        return parent::addHeaders($response, $maxAttempts, $remainingAttempts, $retryAfter);
    }
}

有话要说