<?php declare(strict_types=1); // +---------------------------------------------------------------------- // | CatchAdmin [Just Like ~ ] // +---------------------------------------------------------------------- // | Copyright (c) 2017~2020 http://catchadmin.com All rights reserved. // +---------------------------------------------------------------------- // | Licensed ( https://github.com/yanwenwu/catch-admin/blob/master/LICENSE.txt ) // +---------------------------------------------------------------------- // | Author: JaguarJack [ njphper@gmail.com ] // +---------------------------------------------------------------------- namespace catcher\library\rate; use catcher\exceptions\FailedException; class RateLimiter { use Redis; protected $key; /** * 令牌容量 * * @var int */ protected $capacity = 5; /** * 每次添加 token 的数量 * * @var int */ protected $eachTokens = 5; /** * 添加 token 的时间 * * @var string */ protected $addTokenTimeKey = '_add_token'; /** * 添加 token 的时间间隔 /s * * @var int */ protected $interval = 5; /** * RateLimiter constructor. * @param $key */ public function __construct($key) { $this->key = $key; } /** * 处理 * * @time 2020年07月02日 * @return void */ public function overflow() { // 添加 token if ($this->canAddToken()) { $this->addTokens(); } if (!$this->tokens()) { throw new FailedException('访问限制'); } // 每次请求拿走一个 token $this->removeToken(); } /** * * * @time 2020年07月02日 d * @return void */ protected function addTokens() { $leftTokens = $this->capacity - $this->tokens(); $tokens = array_fill(0, $leftTokens < $this->eachTokens ? $leftTokens : $this->eachTokens, 1); $this->getRedis()->lPush($this->key, ...$tokens); $this->rememberAddTokenTime(); } /** * 拿走一个 token * * @time 2020年07月02日 * @return void */ protected function removeToken() { $this->getRedis()->rPop($this->key); } /** * 设置令牌桶数量 * * @time 2020年07月02日 * @param $capacity * @return $this */ public function setCapacity($capacity) { $this->capacity = $capacity; return $this; } /** * 剩余的 token 数量 * * @time 2020年07月02日 * @return bool|int */ protected function tokens() { return $this->getRedis()->lLen($this->key); } /** * 设置时间间隔 * * @time 2020年07月02日 * @param $seconds * @return $this */ public function setInterval($seconds) { $this->interval = $seconds; return $this; } /** * 是否可以添加 token * * @time 2020年07月02日 * @return bool */ protected function canAddToken() { $currentTime = \time(); $lastAddTokenTime = $this->getRedis()->get($this->key. $this->addTokenTimeKey); // 如果是满的 则不添加 if ($this->tokens() == $this->capacity) { return false; } return ($currentTime - $lastAddTokenTime) > $this->interval; } /** * 记录添加 token 的时间 * * @time 2020年07月02日 * @return void */ protected function rememberAddTokenTime() { $this->getRedis()->set($this->key. $this->addTokenTimeKey, time()); } }