php
<?php
class FreqControl
{
private $redis;
private $max_freq;
public function __construct($redis_client, $max_freq)
{
$this->redis = $redis_client;
$this->max_freq = $max_freq;
}
public function get_freq($key_name, $time_unit = 'minute')
{
// 构建Redis键名
$redis_key = "{$key_name}:{$time_unit}";
// 尝试从Redis获取值
$value = $this->redis->get($redis_key);
// 如果值不存在或为null,返回0;否则返回转换成整数的值
return $value !== null ? (int)$value : 0;
}
public function increment_freq($key_name, $time_unit = 'minute')
{
$key = "{$key_name}:{$time_unit}";
$pipe = $this->redis->multi();
$pipe->incr($key);
if ($time_unit === 'minute') {
$pipe->expire($key, 60);
} elseif ($time_unit === 'hour') {
$pipe->expire($key, 3600);
}
return $pipe->exec();
}
public function set_freq($key_name, $value, $time_unit = 'minute')
{
$key = "{$key_name}:{$time_unit}";
$ttl = $this->redis->ttl($key);
// 使用 MULTI/EXEC 块来确保原子性
$pipe = $this->redis->multi();
$pipe->set($key, $value);
// 如果键存在并有TTL,则保留它
if ($ttl > 0) {
$pipe->expire($key, $ttl);
} else {
// 为新键设置默认的60秒TTL
$pipe->expire($key, 60);
}
return $pipe->exec();
}
public function is_freq_exceeded($key_name, $time_unit = 'minute')
{
$current_freq = $this->get_freq($key_name, $time_unit);
$max_freq = (int)($this->max_freq ?? 0);
return $current_freq >= $max_freq;
}
public function control_freq($key_name, $time_unit = 'minute')
{
if ($this->is_freq_exceeded($key_name, $time_unit)) {
return false; // 超过频率限制,不允许访问
} else {
$current_freq = $this->get_freq($key_name, $time_unit);
if (!$current_freq) {
$this->increment_freq($key_name, $time_unit);
} else {
$this->set_freq($key_name, ($current_freq + 1), $time_unit);
}
return true; // 允许访问
}
}
}
使用方法
php
$redis = new Redis();
$redis->connect('127.0.0.1');
// 选择数据库,例如数据库3
$redis->select(4);
$max_freq = 130;
$freqControl = new FreqControl($redis, $max_freq);
$key_name = $tongjiIp;
if ($WXAPPOPENID) {
$key_name = $tongjiIp . '_' . $WXAPPOPENID;
}
if ($freqControl->control_freq($key_name) == false) {
json('请求频繁,请稍后再试', 200, 'error');
}