/**
* Author: yuanjie
* Date: 2023/9/14
* Desc:访问频率限制
*/
class ActionLimitLib
{
private $actionClassName;
private $actionCurrentNum;
private $time;
private $key = '8kAe9O4GAQpmjmHr';
private $key2 = 'N4zP&zQb5L^D0dhq';
private $uniqid;
private $isSpider;
private $to302 = false;
private $to403 = false;
private $needLimit = false;
private $group = array(
'hospitalList' => 30,
'py_hospitalList' => 5,
);
private $actionGroup = array(
'Pc_Hospital_AreaListAction' => 'hospitalList',
'Pc_Hospital_DepartListAction' => 'hospitalList',
'Pc_Hospital_DiseaseListAction' => 'hospitalList',
'Wap_Hospital_AreaListAction' => 'hospitalList',
'Wap_Hospital_DepartListAction' => 'hospitalList',
'Wap_Hospital_DiseaseListAction' => 'hospitalList',
'Wap_Hos_IndexPageAction' => 'py_hospitalList',
'Wap_Hospital_DepartListAction' => 'py_hospitalList',
'Pc_Hos_IndexPageAction' => 'py_hospitalList',
'Pc_Hospital_DepartListAction' => 'py_hospitalList',
);
public function __construct($actionClassName)
{
$this->time = time();
$this->actionClassName = $actionClassName;
$this->isSpider = $this->checkIsSpider();
}
public function limit()
{
$this->actionIncr();
//按当前秒限制
$this->actionRequestLimitByCurrent();
//按平均数限制
// $this->actionRequestLimitByAvg();
$this->addActionRecord();
$this->actionHeader();
}
public function actionIncr()
{
$actionNumRedisModel = new ActionNumRedisModel();
$baseRedisLib = new BaseRedisLib();
$redis = $baseRedisLib->getRedis($actionNumRedisModel->getRedisWDb());
if (empty($redis)) {
return;
}
$actionGroup = $this->actionGroup;
//global 排除单个限制
$globalNum = 0;
if (!isset($actionGroup[$this->actionClassName])) {
$actionNumRedisModel->setRedisLastKey($this->time . 'globalAction');
$globalNum = $redis->incr($actionNumRedisModel->getRedisKey());
if ($globalNum <= 2) {
$redis->expire($actionNumRedisModel->getRedisKey(), $actionNumRedisModel->getRedisTty());
}
}
//单个action
$groupNum = 0;
if (isset($actionGroup[$this->actionClassName])) {
$actionNumRedisModel->setRedisLastKey($this->time . $actionGroup[$this->actionClassName]);
$groupNum = $redis->incr($actionNumRedisModel->getRedisKey());
if ($groupNum <= 2) {
$redis->expire($actionNumRedisModel->getRedisKey(), $actionNumRedisModel->getRedisTty());
}
}
$this->actionCurrentNum['globalNum'] = $globalNum;
$this->actionCurrentNum['groupNum'] = $groupNum;
}
private function actionHeader()
{
if ($this->needLimit === true && $this->to302 === true) {
$uniqid = !empty($this->uniqid) ? $this->uniqid : uniqid();
$data = time() . '_' . Utils::getClientIP() . '_' . md5($_SERVER['HTTP_USER_AGENT']) . '_' . $uniqid;
$encryptStr = openssl_encrypt($data, 'AES-128-ECB', $this->key);
// 设置 cookie
setcookie('ac_token', $encryptStr, time() + 60, '/');
//设置长时cookie, 用于校验ua是否变化
if (empty($_COOKIE['ac_sec'])) {
$data2 = time() . '_' . $uniqid . '_' . md5($_SERVER['HTTP_USER_AGENT']);
$encryptStr = openssl_encrypt($data2, 'AES-128-ECB', $this->key2);
setcookie('ac_sec', $encryptStr, time() + 600, '/');
}
// 302 跳转到自身链接
header('Location: ' . $_SERVER['REQUEST_URI']);
exit;
}
if ($this->needLimit === true && $this->to403 === true) {
header('HTTP/1.1 403 Forbidden');
exit;
}
}
private function actionRequestLimitByCurrent()
{
$group = $this->group;
$actionGroup = $this->actionGroup;
$isSpider = $this->isSpider;
if ($isSpider == true) {
$randNum = rand(0, 100);
if ($randNum < 30) {
// header("HTTP/1.1 429 Too Many Requests");
// header("HTTP/1.1 403 Forbidden");
// exit;
}
}
//取当前s的值
if ($this->actionCurrentNum['globalNum'] > 150 && $isSpider == false
|| ($this->actionCurrentNum['groupNum'] > intval($group[$actionGroup[$this->actionClassName]]) && $isSpider == false)
) {
$this->needLimit = true;
//先校验长时token, uniqid以长时token为准
//长时token校验
$actionSec = $_COOKIE['ac_sec'];
if (empty($actionSec)) {
$this->to302 = true;
return;
}
//有token 解密验证
$decryptStr = openssl_decrypt($actionSec, 'AES-128-ECB', $this->key2);
//解密失败
if ($decryptStr === false) {
$this->to403 = true;
return;
}
$decryptArr = explode('_', $decryptStr);
$checkSecTime = $decryptArr[0];
$checkSecUniqid = $decryptArr[1];
$checkSecMd5 = $decryptArr[2];
//维持unqid, 用长时间为准
$this->uniqid = $checkSecUniqid;
//短时token, 设置token值, 跳转到本身
$actionToken = $_COOKIE['ac_token'];
if (empty($actionToken)) {
$this->to302 = true;
return;
}
//有token 解密验证
$decryptStr = openssl_decrypt($actionToken, 'AES-128-ECB', $this->key);
//解密失败
if ($decryptStr === false) {
$this->to403 = true;
return;
}
$decryptArr = explode('_', $decryptStr);
$checkTime = $decryptArr[0];
$checkIp = $decryptArr[1];
$checkMd5 = $decryptArr[2];
$checkUniqid = $decryptArr[3];
//超出2分
if (time() - $checkTime > 120) {
$this->to403 = true;
return;
}
//如果ip变更,再跳一次302
if ($checkIp !== Utils::getClientIP()) {
$this->to302 = true;
return;
}
//如果ua变更403
if ($checkMd5 !== md5($_SERVER['HTTP_USER_AGENT'])) {
$this->to403 = true;
return;
}
//超出10分
if (time() - $checkSecTime > 600) {
$this->to403 = true;
return;
}
//如果ua变更403
if ($checkSecMd5 !== md5($_SERVER['HTTP_USER_AGENT'])) {
$this->to403 = true;
return;
}
//如果长短unqid不一致, 403
if ($checkUniqid !== $checkSecUniqid) {
$this->to403 = true;
$this->uniqid = '';
return;
}
//访问频率过高
$actionUniqIdRedisModel = new ActionUniqIdRedisModel();
$baseRedisLib = new BaseRedisLib();
$redis = $baseRedisLib->getRedis($actionUniqIdRedisModel->getRedisWDb());
if (empty($redis)) {
return;
}
$actionUniqIdRedisModel->setRedisLastKey($this->time . $this->uniqid);
$uniqIdNum = $redis->incr($actionUniqIdRedisModel->getRedisKey());
if ($uniqIdNum <= 2) {
$redis->expire($actionUniqIdRedisModel->getRedisKey(), $actionUniqIdRedisModel->getRedisTty());
}
if ($uniqIdNum > 5) {
$this->to403 = true;
return;
}
}
}
private function actionRequestLimitByAvg()
{
$actionNumRedisModel = new ActionNumRedisModel();
$baseRedisLib = new BaseRedisLib();
$group = $this->group;
$actionGroup = $this->actionGroup;
//global 排除单个限制
if (!isset($actionGroup[$this->actionClassName])) {
$actionNumRedisModel->setRedisHashKeys([
$this->time - 1 . 'globalAction',
$this->time - 2 . 'globalAction'
]);
//不检测空
$actionNumRedisModel->setIntactCheck(false);
$requestNumArr = $baseRedisLib->redisSelect($actionNumRedisModel);
//出现错误兼容
if (!is_array($requestNumArr) || empty($requestNumArr)) {
$requestNumArr = [0, 0];
}
$requestNumArr = array_map(function ($item) {
return empty($item) ? 0 : intval($item);
}, $requestNumArr);
$globalNumArr = array_merge(array_slice($requestNumArr, 0, 2), [$this->actionCurrentNum['globalNum']]);
$groupNumArr = [0, 0, 0];
} else {
$groupName = $actionGroup[$this->actionClassName];
$actionNumRedisModel->setRedisHashKeys([
$this->time - 1 . 'globalAction',
$this->time - 2 . 'globalAction',
$this->time - 1 . $groupName,
$this->time - 2 . $groupName,
]);
//不检测空
$actionNumRedisModel->setIntactCheck(false);
$requestNumArr = $baseRedisLib->redisSelect($actionNumRedisModel);
//出现错误兼容
if (!is_array($requestNumArr) || empty($requestNumArr)) {
$requestNumArr = [0, 0, 0, 0];
}
$requestNumArr = array_map(function ($item) {
return empty($item) ? 0 : intval($item);
}, $requestNumArr);
$globalNumArr = array_merge(array_slice($requestNumArr, 0, 2), [$this->actionCurrentNum['globalNum']]);
$groupNumArr = array_merge(array_slice($requestNumArr, 2, 2), [$this->actionCurrentNum['groupNum']]);
}
$isSpider = $this->isSpider;
//取3秒钟的最大值
if (max($globalNumArr) > 150 && $isSpider == false
|| (max($groupNumArr) > intval($group[$actionGroup[$this->actionClassName]]) && $isSpider == false)
) {
$this->needLimit = true;
}
}
private function addActionRecord()
{
$baseRedisLib = new BaseRedisLib();
//5分钟访问量统计
$actionRecordRedisModel = new ActionRecordRedisModel();
$min5 = intval($this->time / 300) * 300;
$actionRecordRedisModel->setRedisKeyPad($min5);
$recodeRedis = $baseRedisLib->getRedis($actionRecordRedisModel->getRedisWDb());
if (empty($recodeRedis)) {
return;
}
$pipRedis = $recodeRedis->pipeline();
$actionRocordRedisKey = $actionRecordRedisModel->getRedisKey();
$pipRedis->zIncrBy($actionRocordRedisKey, 1, 'globalAction');
$pipRedis->zIncrBy($actionRocordRedisKey, 1, $this->actionClassName);
if ($this->to302 === true) {
$pipRedis->zIncrBy($actionRocordRedisKey, 1, '302Action');
}
if ($this->to403 === true) {
$pipRedis->zIncrBy($actionRocordRedisKey, 1, '403Action');
}
if ($this->isSpider === true) {
$pipRedis->zIncrBy($actionRocordRedisKey, 1, 'spiderAction');
}
if (!empty($_COOKIE['ac_token']) && $this->to302 === false && $this->to403 === false) {
$pipRedis->zIncrBy($actionRocordRedisKey, 1, 'tokenAction');
}
$ret = $pipRedis->exec();
$globalRecordNum = intval($ret[0]);
if ($globalRecordNum <= 2) {
$recodeRedis->expire($actionRocordRedisKey, $actionRecordRedisModel->getRedisTty());
}
}
private function getActionRecord($num)
{
$baseRedisLib = new BaseRedisLib();
//5分钟访问量统计
$actionRecordRedisModel = new ActionRecordRedisModel();
$min5 = intval($this->time / 300) * 300;
$actionRecordRedisModel->setRedisKeyPad($min5);
$recodeRedis = $baseRedisLib->getRedis($actionRecordRedisModel->getRedisWDb());
$tempActionRank = $recodeRedis->zRevRange($actionRecordRedisModel->getRedisKey(), 0, $num, true);
return $tempActionRank;
}
//检测是否爬虫
public function checkIsSpider()
{
$isSpider = false;
$userAgent = strtolower($_SERVER['HTTP_USER_AGENT']);
if (strlen($userAgent) > strlen(str_replace(['spider', 'yisouspider', 'apachebench', 'sogou', 'requestcore', 'bot'], '', $userAgent))) {
$isSpider = true;
}
$this->isSpider = $isSpider;
return $isSpider;
}
}
Fatal error: Class 'ActionLimitLib' not found in /fh21_data/fh21_web/bohe_hospital/hospital/action/WebBaseAction.class.php on line 37