/** * 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