From 521d4e3ac5175c6a07830d3e9124d662615c49fa Mon Sep 17 00:00:00 2001 From: xboard Date: Sat, 18 Apr 2026 02:57:55 +0800 Subject: [PATCH] fix: dedup device IPs, reset stale online_count on disconnect and scheduled cleanup (#886) --- app/Console/Commands/CleanupOnlineStatus.php | 27 ++++++++++++++++++++ app/Console/Kernel.php | 2 ++ app/Services/DeviceStateService.php | 20 +++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 app/Console/Commands/CleanupOnlineStatus.php diff --git a/app/Console/Commands/CleanupOnlineStatus.php b/app/Console/Commands/CleanupOnlineStatus.php new file mode 100644 index 0000000..dd33004 --- /dev/null +++ b/app/Console/Commands/CleanupOnlineStatus.php @@ -0,0 +1,27 @@ +', 0) + ->where(function ($query) { + $query->where('last_online_at', '<', now()->subMinutes(10)) + ->orWhereNull('last_online_at'); + }) + ->update(['online_count' => 0]); + + if ($affected > 0) { + $this->info("Reset online_count for {$affected} stale users."); + } + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 77b1810..447440a 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -42,6 +42,8 @@ class Kernel extends ConsoleKernel $schedule->command('send:remindMail', ['--force'])->dailyAt('11:30')->onOneServer(); // horizon metrics $schedule->command('horizon:snapshot')->everyFiveMinutes()->onOneServer(); + // cleanup stale online_count (GC for Redis TTL expiration) + $schedule->command('cleanup:online-status')->everyFiveMinutes()->onOneServer(); // backup Timing // if (env('ENABLE_AUTO_BACKUP_AND_UPDATE', false)) { // $schedule->command('backup:database', ['true'])->daily()->onOneServer(); diff --git a/app/Services/DeviceStateService.php b/app/Services/DeviceStateService.php index 09e106c..8247197 100644 --- a/app/Services/DeviceStateService.php +++ b/app/Services/DeviceStateService.php @@ -32,6 +32,9 @@ class DeviceStateService $this->removeNodeDevices($nodeId, $userId); + // Normalize: strip port suffix and deduplicate + $ips = array_values(array_unique(array_map([self::class, 'normalizeIP'], $ips))); + if (!empty($ips)) { $fields = []; foreach ($ips as $ip) { @@ -98,6 +101,7 @@ class DeviceStateService Redis::hdel($key, $field); } } + $this->notifyUpdate($userId); } return array_keys($oldDevices); @@ -166,6 +170,22 @@ class DeviceStateService return $result; } + /** + * Strip port from IP address: "1.2.3.4:12345" → "1.2.3.4", "[::1]:443" → "::1" + */ + private static function normalizeIP(string $ip): string + { + // [IPv6]:port + if (preg_match('/^\[(.+)\]:\d+$/', $ip, $m)) { + return $m[1]; + } + // IPv4:port + if (preg_match('/^(\d+\.\d+\.\d+\.\d+):\d+$/', $ip, $m)) { + return $m[1]; + } + return $ip; + } + /** * notify update (throttle control) */