PHP用redis实现计数器功能从而实现限流
admin 47 2019-12-01 16:11:34
<?php
/**
* Base on Redis component
* User: yangzhen
* Date: 2019/10/30
* Time: 下午5:01
*/
namespace mysoft\helpers;
use mysoft\base\Exception;
use yii\helpers\StringHelper;
class Counter
{
/**
* 前缀
*/
const PREFIX = "cnt";
/**
* 当前计数器值
* @var int
*/
private $currentCount = 0;
/**
* counter限制
* @var bool|int
*/
private $maxLimit = false;
/**
* 过期时间
* @var bool|int
*/
private $expireSeconds = false;
/**
* 成功回调函数
* @var bool|callable func
*/
private $sucFunc = false;
/**
* 失败回调函数
* @var bool|callable func
*/
private $failFunc = false;
/**
* 延迟过期偏移量
* @var int
*/
private $delayOffset = 5;
/**
* 后缀
* @var string
*/
private $suffix = '';
public function __construct()
{
if (!\Yii::$app->has('redis')) {
throw new CounterException("component of redis not found for counter");
}
}
/**
* 设置过期时间
* @param $seconds int 单位秒
* @return $this
*/
public function withExpire($seconds)
{
$this->expireSeconds = $seconds;
return $this;
}
/**
* 设置最大限制
* @param $max int
* @return $this
*/
public function withMaxLimit($max)
{
$this->maxLimit = (int)$max;
return $this;
}
/**
* 设置未超越限制的回调
* @param callable $func
* @return $this
* @throws \Exception
*/
public function withUnExceedCalFunc(callable $func)
{
if (!is_callable($func)) {
throw new CounterException("withUnExceedCalFunc is not callable func");
}
$this->sucFunc = $func;
return $this;
}
/**
* 设置超越限制的回调
* @param callable $func
* @return $this
* @throws \Exception
*/
public function withExceedCalFunc(callable $func)
{
if (!is_callable($func)) {
throw new \Exception("withExceedCalFunc is not callable func");
}
$this->failFunc = $func;
return $this;
}
/**
* 执行计数器
* @return mixed
*/
private function doIncr($key)
{
$this->currentCount = $count = \Yii::$app->redis->incr($key);
if ($this->expireSeconds != false && \Yii::$app->redis->ttl($key) <= 0) { //如果设置过期时间则设置
\Yii::$app->redis->expire($key, $this->expireSeconds);//设置过期时间,便于清理数据
}
if ($this->maxLimit === false) { //没有设置Limit则直接返回当前增长到的count数
return $count;
}
if ($count <= $this->maxLimit) { //没有超过限制
if ($this->sucFunc != false) {
return call_user_func($this->sucFunc, $count); //没有超过限制的回调方法
}
} else {//失败
if ($this->failFunc != false) {
return call_user_func($this->failFunc, $count);//超过限制的回调方法
}
}
return count; //没有设置回调函数统一返回当前count数
}
/**
* 构建counter的key
* @param $key
* @return string
* @throws CounterException
*/
private function buildKey($key)
{
if (is_string($key)) {
if ($this->suffix) $key .= '_' . $this->convertSuffix($this->suffix);
$key = ctype_alnum($key) && StringHelper::byteLength($key) <= 32 ? $key : md5($key);
} else {
if ($this->suffix) $key[] = $this->convertSuffix($this->suffix);
$key = md5(json_encode($key));
}
return static::PREFIX . ':' . $key;
}
/**
* suffix的转换方法
* @param $suffix string|array
* string - 普通模式,说明传入为已经定义好的字符串后缀,直接返回
* array - 函数模式,第一个元素为方法名,第二个开始就是参数列表值,格式如 [$func,$arg1[,...]],最终返回为String
* @return string
* @throws CounterException
*/
private function convertSuffix($suffix)
{
//处理后缀为函数变量的方法 ,格式为[$func,$arg1,$arg2 ...]
if (is_array($suffix)) {
$func = array_shift($suffix);//弹出第一个为命名方法
$result = call_user_func_array($func, $suffix);
if (!is_string($result)) {
throw new CounterException('suffix must be a string,please check callable func return');
}
return $result;
}
return $suffix;
}
/**
* 按每秒控制频次,即 'X次/秒'
* usage:
* (new Counter())->withSucessCallBack(callable func)->withFailCallBack(callable func)->limitBySecond()
*
* @param $limit
* @return mixed
*/
public function limitBySecond()
{
$this->suffix = ['date', 'YmdHis'];// date('YmdHis');
$this->withExpire(2);
return $this;
}
/**
* 按每分钟控制频次
* 用法参照按秒控制
* @return mixed
*/
public function limitByMinute()
{
$this->suffix = ['date', 'YmdHi'];//date('YmdHi')
$this->withExpire(60 + $this->delayOffset);
return $this;
}
/**
* 按每小时控制频次
* 用法参照秒控制
* @return mixed
*/
public function limitByHour()
{
$this->suffix = ['date', 'YmdH'];// date('YmdH')
$this->withExpire(3600 + $this->delayOffset);
return $this;
}
//用户自定义方式限制
public function limitByUserDefine(callable $func, array $params, $duration = false)
{
//TODO:场景待实现
return $this;
}
/**
* 获取当前的计数器key完整值
* @return mixed
* @throws \Exception
*/
public function getCounterKey($rawKey)
{
return $this->buildKey($rawKey);
}
/**
* 获取当前计数器数值,必须先执行Run
* @return int
*/
public function getCurrentCount()
{
return $this->currentCount;
}
//这里是最终的执行方法
public function Run($rawKey)
{
return $this->doIncr($this->getCounterKey($rawKey));
}
}
/**
* 自定义Counter的异常处理类
* Class CounterException
* @package mysoft\helpers
*/
class CounterException extends Exception
{
public function __construct($message, $code = -1)
{
parent::__construct($message, $code);
}
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return 'Counter Exception';
}
}
To share this paste please copy this url and send to your friends
预览
还没有评论.
最新分享
- PHP用redis实现计数器功能从而实现限流
PHP | 47 | 2周前
- PHP批量下载QQ空间相册照片链接
PHP | 40 | 2周前
- PHP对一个接口进行请求次数限制
PHP | 30 | 2周前
- PHP汉字转拼音类文件
PHP | 32 | 2周前
- laravel 表单验证 api自定义错误信息返回json 与 路由别名场景验证
PHP | 35 | 2周前
- QQ或微信内打开网站提示用浏览器打开代码
PHP | 55 | 3周前
- 简易防CC攻击刷新跳转代码
PHP | 53 | 3周前