Files
links/application/common/service/DomainDetectRateLimitService.php
T

68 lines
2.0 KiB
PHP
Raw Normal View History

2026-06-02 00:32:05 +08:00
<?php
declare(strict_types=1);
namespace app\common\service;
use think\Cache;
use think\Exception;
/**
* 域名检测频率限制
* 每分钟最多 2 次,超限后冷却 120 秒
*/
class DomainDetectRateLimitService
{
/** 统计窗口(秒) */
private const WINDOW_SECONDS = 60;
/** 窗口内最大检测次数 */
private const MAX_ATTEMPTS = 2;
/** 超限后冷却时间(秒) */
private const COOLDOWN_SECONDS = 120;
/**
* 校验是否允许检测,不允许时抛出异常
*
* @param int $adminId 管理员 ID
* @param int $domainId 域名记录 ID
* @throws Exception
*/
public function assertCanDetect(int $adminId, int $domainId): void
{
$now = time();
$baseKey = 'domain_detect:' . $adminId . ':' . $domainId;
$cooldownKey = $baseKey . ':cooldown';
$attemptsKey = $baseKey . ':attempts';
$cooldownUntil = (int)Cache::get($cooldownKey, 0);
if ($cooldownUntil > $now) {
$wait = $cooldownUntil - $now;
throw new Exception(sprintf('检测过于频繁,请 %d 秒后再试', $wait));
}
$timestamps = Cache::get($attemptsKey);
if (!is_array($timestamps)) {
$timestamps = [];
}
$timestamps = array_values(array_filter($timestamps, static function ($ts) use ($now): bool {
return is_int($ts) && ($now - $ts) < self::WINDOW_SECONDS;
}));
if (count($timestamps) >= self::MAX_ATTEMPTS) {
Cache::set($cooldownKey, $now + self::COOLDOWN_SECONDS, self::COOLDOWN_SECONDS);
Cache::set($attemptsKey, $timestamps, self::WINDOW_SECONDS + self::COOLDOWN_SECONDS);
throw new Exception(sprintf(
'一分钟最多检测 %d 次,请 %d 秒后再试',
self::MAX_ATTEMPTS,
self::COOLDOWN_SECONDS
));
}
$timestamps[] = $now;
Cache::set($attemptsKey, $timestamps, self::WINDOW_SECONDS + self::COOLDOWN_SECONDS);
}
}