From 4f84034814a9f83339e86fc2a68e7415ee0fb7c2 Mon Sep 17 00:00:00 2001 From: yinjianm Date: Thu, 26 Feb 2026 05:13:19 +0800 Subject: [PATCH] =?UTF-8?q?=E2=80=A2=20=E4=BD=A0=E8=BF=99=E4=B8=AA?= =?UTF-8?q?=E7=8E=B0=E8=B1=A1=E6=88=91=E5=B7=B2=E7=BB=8F=E5=AE=9A=E4=BD=8D?= =?UTF-8?q?=E5=B9=B6=E4=BF=AE=E4=BA=86=EF=BC=8C=E6=A0=B8=E5=BF=83=E5=8E=9F?= =?UTF-8?q?=E5=9B=A0=E6=98=AF=E8=8A=82=E7=82=B9=E5=8C=B9=E9=85=8D=E5=92=8C?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=98=A0=E5=B0=84=E9=83=BD=E6=9C=89=E6=AD=A7?= =?UTF-8?q?=E4=B9=89=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 已修改: - 节点识别优先用 id,只有找不到才回退 code,避免子节点被匹配成父节点 ServerService.php (/E:/code/php/Xboard-new/app/Services/ServerService.php#L101) - 在线设备解析补全 node_key/node_id,并按真实节点键归类 UserOnlineService.php (/E:/code/php/Xboard-new/app/Services/UserOnlineService.php#L43) - 用户端流量日志设备映射改为按 node_key 精确匹配 StatController.php (/E:/code/php/Xboard-new/app/Http/Controllers/V1/User/StatController.php#L43) - 管理端同样改为按 node_key 映射 StatController.php (/E:/code/php/Xboard-new/app/Http/Controllers/V2/Admin/StatController.php#L270) 说明: - 这次不涉及数据库结构变更,不需要 migrate。 - 对管理后台现有统计接口兼容,不会破坏原有 u/d/record_at/server_rate 显示。 - 旧的历史日志如果之前已经按父节点入库,不会自动改名;新流量会按修复后的逻辑记录。 --- .../Controllers/V1/User/StatController.php | 18 +++++- .../Controllers/V2/Admin/StatController.php | 18 +++++- app/Services/ServerService.php | 22 ++++--- app/Services/UserOnlineService.php | 63 ++++++++++++++++--- 4 files changed, 103 insertions(+), 18 deletions(-) diff --git a/app/Http/Controllers/V1/User/StatController.php b/app/Http/Controllers/V1/User/StatController.php index fb0985d..f3bc707 100644 --- a/app/Http/Controllers/V1/User/StatController.php +++ b/app/Http/Controllers/V1/User/StatController.php @@ -46,8 +46,22 @@ class StatController extends Controller $deviceList = data_get($devices, 'devices', []); return collect($deviceList) - ->filter(fn($item): bool => is_array($item) && !empty($item['ip']) && !empty($item['node_type'])) - ->groupBy(fn(array $item): string => strtolower((string) $item['node_type'])) + ->filter(fn($item): bool => is_array($item) && !empty($item['ip'])) + ->map(function (array $item): array { + $nodeKey = strtolower((string) ($item['node_key'] ?? '')); + if ($nodeKey === '') { + $nodeType = strtolower((string) ($item['node_type'] ?? '')); + $nodeId = (int) ($item['node_id'] ?? 0); + $nodeKey = ($nodeType !== '' && $nodeId > 0) ? "{$nodeType}{$nodeId}" : $nodeType; + } + + return [ + 'node_key' => $nodeKey, + 'ip' => (string) ($item['ip'] ?? ''), + ]; + }) + ->filter(fn(array $item): bool => $item['node_key'] !== '' && $item['ip'] !== '') + ->groupBy(fn(array $item): string => $item['node_key']) ->map(fn($items): array => collect($items) ->pluck('ip') ->filter() diff --git a/app/Http/Controllers/V2/Admin/StatController.php b/app/Http/Controllers/V2/Admin/StatController.php index d16362f..cfe25f6 100644 --- a/app/Http/Controllers/V2/Admin/StatController.php +++ b/app/Http/Controllers/V2/Admin/StatController.php @@ -273,8 +273,22 @@ class StatController extends Controller $deviceList = data_get($devices, 'devices', []); return collect($deviceList) - ->filter(fn($item): bool => is_array($item) && !empty($item['ip']) && !empty($item['node_type'])) - ->groupBy(fn(array $item): string => strtolower((string) $item['node_type'])) + ->filter(fn($item): bool => is_array($item) && !empty($item['ip'])) + ->map(function (array $item): array { + $nodeKey = strtolower((string) ($item['node_key'] ?? '')); + if ($nodeKey === '') { + $nodeType = strtolower((string) ($item['node_type'] ?? '')); + $nodeId = (int) ($item['node_id'] ?? 0); + $nodeKey = ($nodeType !== '' && $nodeId > 0) ? "{$nodeType}{$nodeId}" : $nodeType; + } + + return [ + 'node_key' => $nodeKey, + 'ip' => (string) ($item['ip'] ?? ''), + ]; + }) + ->filter(fn(array $item): bool => $item['node_key'] !== '' && $item['ip'] !== '') + ->groupBy(fn(array $item): string => $item['node_key']) ->map(fn($items): array => collect($items) ->pluck('ip') ->filter() diff --git a/app/Services/ServerService.php b/app/Services/ServerService.php index 30a883d..1ff023b 100644 --- a/app/Services/ServerService.php +++ b/app/Services/ServerService.php @@ -100,15 +100,23 @@ class ServerService */ public static function getServer($serverId, ?string $serverType) { - return Server::query() + $baseQuery = Server::query() ->when($serverType, function ($query) use ($serverType) { $query->where('type', Server::normalizeType($serverType)); - }) - ->where(function ($query) use ($serverId) { - $query->where('code', $serverId) - ->orWhere('id', $serverId); - }) - ->orderByRaw('CASE WHEN code = ? THEN 0 ELSE 1 END', [$serverId]) + }); + + $serverIdValue = is_scalar($serverId) ? (string) $serverId : ''; + $isCanonicalInt = preg_match('/^(0|[1-9][0-9]*)$/', $serverIdValue) === 1; + + if ($isCanonicalInt) { + $serverById = (clone $baseQuery)->whereKey((int) $serverIdValue)->first(); + if ($serverById) { + return $serverById; + } + } + + return (clone $baseQuery) + ->where('code', $serverIdValue) ->first(); } } diff --git a/app/Services/UserOnlineService.php b/app/Services/UserOnlineService.php index 42ec854..7aad786 100644 --- a/app/Services/UserOnlineService.php +++ b/app/Services/UserOnlineService.php @@ -3,6 +3,7 @@ namespace App\Services; +use App\Models\Server; use Illuminate\Support\Collection; use Illuminate\Support\Str; @@ -49,17 +50,26 @@ class UserOnlineService $devices = collect($data) ->filter(fn(mixed $item): bool => is_array($item) && isset($item['aliveips'])) ->flatMap(function (array $nodeData, string $nodeKey): array { + $parsedNode = self::parseNodeMeta((string) $nodeKey); + return collect($nodeData['aliveips']) - ->mapWithKeys(function (string $ipNodeId) use ($nodeData, $nodeKey): array { + ->map(function (string $ipNodeId) use ($nodeData, $parsedNode): array { $ip = Str::before($ipNodeId, '_'); + $payloadNodeId = (int) Str::after($ipNodeId, '_'); + $nodeType = $parsedNode['node_type'] ?? ''; + $nodeId = $payloadNodeId > 0 ? $payloadNodeId : ((int) ($parsedNode['node_id'] ?? 0)); + $nodeKeyValue = self::buildNodeKey($nodeType, $nodeId); + return [ - $ip => [ - 'ip' => $ip, - 'last_seen' => $nodeData['lastupdateAt'], - 'node_type' => Str::before($nodeKey, (string) $nodeData['lastupdateAt']) - ] + 'ip' => $ip, + 'last_seen' => (int) ($nodeData['lastupdateAt'] ?? 0), + 'node_type' => $nodeType, + 'node_id' => $nodeId > 0 ? $nodeId : null, + 'node_key' => $nodeKeyValue, ]; }) + ->filter(fn(array $item): bool => !empty($item['ip'])) + ->values() ->all(); }) ->values() @@ -71,6 +81,45 @@ class UserOnlineService ]; } + /** + * Parse node metadata from cache key such as "shadowsocks12". + * + * @return array{node_type: string, node_id: int} + */ + private static function parseNodeMeta(string $nodeKey): array + { + $normalizedKey = strtolower(trim($nodeKey)); + if ($normalizedKey === '') { + return ['node_type' => '', 'node_id' => 0]; + } + + $types = collect(Server::VALID_TYPES) + ->map(fn(string $type): string => strtolower($type)) + ->sortByDesc(fn(string $type): int => strlen($type)) + ->values(); + + foreach ($types as $type) { + if (!str_starts_with($normalizedKey, $type)) { + continue; + } + + $idPart = substr($normalizedKey, strlen($type)); + $nodeId = ctype_digit($idPart) ? (int) $idPart : 0; + return ['node_type' => $type, 'node_id' => $nodeId]; + } + + return ['node_type' => '', 'node_id' => 0]; + } + + private static function buildNodeKey(string $nodeType, int $nodeId): ?string + { + if ($nodeType === '' || $nodeId <= 0) { + return null; + } + + return "{$nodeType}{$nodeId}"; + } + /** * 批量获取用户在线设备数 @@ -120,4 +169,4 @@ class UserOnlineService default => throw new \InvalidArgumentException("Invalid device limit mode: $mode"), }; } -} \ No newline at end of file +}