令牌桶限流,亲测有效

namespace app\common\logic;

use think\Cache;

class RateLimitLogic {

	public $ts;

	public $cap;

	public $limitAct;//限制的动作

	public $result;

	public function __construct() {
		$this->result   = ['errno' => 0, 'errmsg' => ''];
		$this->ts       = time();
		$this->cap      = C('rate_limit')['cap'];
		$this->limitAct = C('rate_limit')['limit_act'];
		foreach ($this->limitAct as &$v) {
			$v > $this->cap && $v = $this->cap;
		}
	}

	public function run($reqAct) {

		if (!C('rate_limit')['on'] || !array_key_exists($reqAct, $this->limitAct)) {
			return $this->result;
		}

		if (!file_exists(WWW_PATH.'/watching.log')) {
			return ['errno' => 1, 'errmsg' => 'watching file gone away'];
		}

		$watching = file_get_contents(WWW_PATH.'/watching.log');
		$arr      = explode(" ", $watching);
		$arrFilt  = array_filter($arr, function ($item) {
				return $item != "";
			});
		$apiRate = 0;

		foreach ($arrFilt as $k => $v) {
			if (strrpos($v, $reqAct) > 0) {
				$apiRate = ceil($arrFilt[$k-1]/60);//设置60秒 脚本一分钟一次
				break;
			}
		}

		if ($apiRate > $this->limitAct[$reqAct]) {//该api每秒速率
			Cache::watch($reqAct);
			$limit = Cache::get($reqAct);
			if (!empty($limit)) {
				$token = min($this->cap, ($limit['num']-1)+($this->limitAct[$reqAct]*($this->ts-$limit['time'])));
				if ($token < 0) {
					plog('ratelimit.log', 'limit success', 'empty tokens');
					return ['errno' => 1, 'errmsg' => '商城火爆,可稍等几分钟避开高峰期再试'];
				}
				$res = ['num' => $token, 'time' => $this->ts];
			} else {
				$res = ['num' => $this->cap, 'time' => time()];
			}

			Cache::multi();
			Cache::set($reqAct, $res);
			$end = Cache::exec();
			if (!$end) {
				plog('ratelimit.log', 'limit success', 'access too fast');
				$this->result = ['errno' => 1, 'errmsg' => '访问频次过多'];
			}
		}
		return $this->result;
	}

}

你可能感兴趣的:(php)