fix: resolve device sync issues and refactor WebSocket server

This commit is contained in:
xboard
2026-03-26 03:33:01 +08:00
parent 420521d90a
commit 74b5ef0b27
5 changed files with 467 additions and 387 deletions
+71 -32
View File
@@ -12,6 +12,15 @@ class DeviceStateService
private const TTL = 300; // device state ttl
private const DB_THROTTLE = 10; // update db throttle
/**
* 移除 Redis key 的前缀
*/
private function removeRedisPrefix(string $key): string
{
$prefix = config('database.redis.options.prefix', '');
return $prefix ? substr($key, strlen($prefix)) : $key;
}
/**
* 批量设置设备
* 用于 HTTP /alive 和 WebSocket report.devices
@@ -21,7 +30,7 @@ class DeviceStateService
$key = self::PREFIX . $userId;
$timestamp = time();
$this->clearNodeDevices($nodeId, $userId);
$this->removeNodeDevices($nodeId, $userId);
if (!empty($ips)) {
$fields = [];
@@ -36,49 +45,80 @@ class DeviceStateService
}
/**
* clear node devices
* - only nodeId: clear all devices of the node
* - userId and nodeId: clear specific user's specific node device
* 获取某节点的所有设备数据
* 返回: {userId: [ip1, ip2, ...], ...}
*/
public function clearNodeDevices(int $nodeId, ?int $userId = null): void
public function getNodeDevices(int $nodeId): array
{
if ($userId !== null) {
$key = self::PREFIX . $userId;
$prefix = "{$nodeId}:";
foreach (Redis::hkeys($key) as $field) {
if (str_starts_with($field, $prefix)) {
Redis::hdel($key, $field);
}
}
return;
}
$keys = Redis::keys(self::PREFIX . '*');
$prefix = "{$nodeId}:";
$result = [];
foreach ($keys as $key) {
foreach (Redis::hkeys($key) as $field) {
$actualKey = $this->removeRedisPrefix($key);
$uid = (int) substr($actualKey, strlen(self::PREFIX));
$data = Redis::hgetall($actualKey);
foreach ($data as $field => $timestamp) {
if (str_starts_with($field, $prefix)) {
Redis::hdel($key, $field);
$ip = substr($field, strlen($prefix));
$result[$uid][] = $ip;
}
}
}
return $result;
}
/**
* 删除某节点某用户的设备
*/
public function removeNodeDevices(int $nodeId, int $userId): void
{
$key = self::PREFIX . $userId;
$prefix = "{$nodeId}:";
foreach (Redis::hkeys($key) as $field) {
if (str_starts_with($field, $prefix)) {
Redis::hdel($key, $field);
}
}
}
/**
* get user device count (filter expired data)
* 清除节点所有设备数据(用于节点断开连接)
*/
public function clearAllNodeDevices(int $nodeId): array
{
$oldDevices = $this->getNodeDevices($nodeId);
$prefix = "{$nodeId}:";
foreach ($oldDevices as $userId => $ips) {
$key = self::PREFIX . $userId;
foreach (Redis::hkeys($key) as $field) {
if (str_starts_with($field, $prefix)) {
Redis::hdel($key, $field);
}
}
}
return array_keys($oldDevices);
}
/**
* get user device count (deduplicated by IP, filter expired data)
*/
public function getDeviceCount(int $userId): int
{
$data = Redis::hgetall(self::PREFIX . $userId);
$now = time();
$count = 0;
$ips = [];
foreach ($data as $field => $timestamp) {
// if ($now - $timestamp <= self::TTL) {
$count++;
// }
if ($now - $timestamp <= self::TTL) {
$ips[] = substr($field, strpos($field, ':') + 1);
}
}
return $count;
return count(array_unique($ips));
}
/**
@@ -108,15 +148,14 @@ class DeviceStateService
{
$result = [];
$now = time();
foreach ($userIds as $userId) {
$data = Redis::hgetall(self::PREFIX . $userId);
if (!empty($data)) {
$ips = [];
foreach ($data as $field => $timestamp) {
// if ($now - $timestamp <= self::TTL) {
if ($now - $timestamp <= self::TTL) {
$ips[] = substr($field, strpos($field, ':') + 1);
// }
}
}
if (!empty($ips)) {
$result[$userId] = array_unique($ips);
@@ -130,12 +169,12 @@ class DeviceStateService
/**
* notify update (throttle control)
*/
private function notifyUpdate(int $userId): void
public function notifyUpdate(int $userId): void
{
$dbThrottleKey = "device:db_throttle:{$userId}";
if (Redis::setnx($dbThrottleKey, 1)) {
Redis::expire($dbThrottleKey, self::DB_THROTTLE);
// if (Redis::setnx($dbThrottleKey, 1)) {
// Redis::expire($dbThrottleKey, self::DB_THROTTLE);
User::query()
->whereKey($userId)
@@ -143,6 +182,6 @@ class DeviceStateService
'online_count' => $this->getDeviceCount($userId),
'last_online_at' => now(),
]);
}
// }
}
}