f39b0ee63f
# Conflicts: # app/common/utils/email/Emails.php
161 lines
4.1 KiB
PHP
161 lines
4.1 KiB
PHP
<?php
|
|
|
|
namespace app\common\utils;
|
|
|
|
use support\Log;
|
|
use support\Redis;
|
|
|
|
class RedisUidRotator
|
|
{
|
|
private string $key;
|
|
private string $setKey;
|
|
private string $lockKey;
|
|
private int $expireSeconds;
|
|
|
|
public function __construct(string $rotatorName, int $expireSeconds = 604800)
|
|
{
|
|
$this->key = "uid_rotator:{$rotatorName}:index";
|
|
$this->setKey = "uid_rotator:{$rotatorName}:uids";
|
|
$this->lockKey = "uid_rotator:{$rotatorName}:lock";
|
|
$this->expireSeconds = $expireSeconds;
|
|
}
|
|
|
|
public function setUids(array $uids, bool $resetIndex = true): bool
|
|
{
|
|
// 删除旧数据
|
|
Redis::del($this->setKey);
|
|
|
|
if (!empty($uids)) {
|
|
// 添加新UID集合
|
|
foreach ($uids as $uid) {
|
|
Redis::sAdd($this->setKey, $uid);
|
|
}
|
|
|
|
// 重置索引
|
|
if ($resetIndex) {
|
|
Redis::set($this->key, 0);
|
|
}
|
|
}
|
|
|
|
$this->touchExpire();
|
|
return true;
|
|
}
|
|
|
|
public function next(): ?int
|
|
{
|
|
$lockAcquired = false;
|
|
try {
|
|
// 获取锁(改用原生命令)
|
|
$lockAcquired = $this->acquireLock(5);
|
|
if (!$lockAcquired) {
|
|
throw new \RuntimeException("获取锁失败");
|
|
}
|
|
|
|
// 获取当前索引
|
|
$currentIndex = (int)Redis::get($this->key);
|
|
|
|
// 获取所有UID
|
|
$uids = Redis::sMembers($this->setKey);
|
|
if (empty($uids)) {
|
|
return null;
|
|
}
|
|
|
|
// 计算位置
|
|
$uid = $uids[$currentIndex % count($uids)];
|
|
$nextIndex = $currentIndex + 1;
|
|
|
|
// 更新索引
|
|
Redis::set($this->key, $nextIndex);
|
|
$this->touchExpire();
|
|
|
|
return is_numeric($uid) ? (int)$uid : $uid;
|
|
} catch (\Throwable $e) {
|
|
Log::info("UID分配错误: " . $e->getMessage());
|
|
return null;
|
|
} finally {
|
|
if ($lockAcquired) {
|
|
Redis::del($this->lockKey);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function acquireLock(int $timeout = 5): bool
|
|
{
|
|
$start = time();
|
|
$lockTimeout = $timeout + 1; // 锁过期时间比等待时间长
|
|
|
|
while (time() - $start < $timeout) {
|
|
// 使用SETNX实现锁(分两步)
|
|
if (Redis::setNx($this->lockKey, 1)) {
|
|
// 设置过期时间
|
|
Redis::expire($this->lockKey, $lockTimeout);
|
|
return true;
|
|
}
|
|
|
|
// 检查锁是否已过期但未被删除(处理死锁)
|
|
if (Redis::ttl($this->lockKey) < 0) {
|
|
Redis::del($this->lockKey);
|
|
}
|
|
|
|
usleep(100000); // 100ms重试间隔
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private function touchExpire(): void
|
|
{
|
|
Redis::expire($this->key, $this->expireSeconds);
|
|
Redis::expire($this->setKey, $this->expireSeconds);
|
|
}
|
|
|
|
public function getPosition(): int
|
|
{
|
|
return (int)Redis::get($this->key);
|
|
}
|
|
|
|
public function resetIndex(int $position = 0): bool
|
|
{
|
|
return (bool)Redis::set($this->key, max(0, $position));
|
|
}
|
|
|
|
public function getUids(): array
|
|
{
|
|
return Redis::sMembers($this->setKey) ?: [];
|
|
}
|
|
|
|
public function addUids($uids): int
|
|
{
|
|
$added = 0;
|
|
$uids = is_array($uids) ? $uids : [$uids];
|
|
|
|
foreach ($uids as $uid) {
|
|
$added += Redis::sAdd($this->setKey, $uid);
|
|
}
|
|
|
|
$this->touchExpire();
|
|
return $added;
|
|
}
|
|
|
|
public function removeUids($uids): int
|
|
{
|
|
$removed = 0;
|
|
$uids = is_array($uids) ? $uids : [$uids];
|
|
|
|
foreach ($uids as $uid) {
|
|
$removed += Redis::sRem($this->setKey, $uid);
|
|
}
|
|
|
|
return $removed;
|
|
}
|
|
|
|
public function count(): int
|
|
{
|
|
return (int)Redis::sCard($this->setKey);
|
|
}
|
|
|
|
public function destroy(): void
|
|
{
|
|
Redis::del([$this->key, $this->setKey, $this->lockKey]);
|
|
}
|
|
} |