2020-07-02 18:41:47 +08:00
|
|
|
|
<?php
|
2020-11-29 09:29:14 +08:00
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
2020-07-02 18:41:47 +08:00
|
|
|
|
// +----------------------------------------------------------------------
|
|
|
|
|
// | 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());
|
|
|
|
|
}
|
|
|
|
|
}
|