• 你这个现象我已经定位并修了,核心原因是节点匹配和设备映射都有歧义。
已修改:
- 节点识别优先用 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 显示。
- 旧的历史日志如果之前已经按父节点入库,不会自动改名;新流量会按修复后的逻辑记录。
This commit is contained in:
@@ -46,8 +46,22 @@ class StatController extends Controller
|
|||||||
$deviceList = data_get($devices, 'devices', []);
|
$deviceList = data_get($devices, 'devices', []);
|
||||||
|
|
||||||
return collect($deviceList)
|
return collect($deviceList)
|
||||||
->filter(fn($item): bool => is_array($item) && !empty($item['ip']) && !empty($item['node_type']))
|
->filter(fn($item): bool => is_array($item) && !empty($item['ip']))
|
||||||
->groupBy(fn(array $item): string => strtolower((string) $item['node_type']))
|
->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)
|
->map(fn($items): array => collect($items)
|
||||||
->pluck('ip')
|
->pluck('ip')
|
||||||
->filter()
|
->filter()
|
||||||
|
|||||||
@@ -273,8 +273,22 @@ class StatController extends Controller
|
|||||||
$deviceList = data_get($devices, 'devices', []);
|
$deviceList = data_get($devices, 'devices', []);
|
||||||
|
|
||||||
return collect($deviceList)
|
return collect($deviceList)
|
||||||
->filter(fn($item): bool => is_array($item) && !empty($item['ip']) && !empty($item['node_type']))
|
->filter(fn($item): bool => is_array($item) && !empty($item['ip']))
|
||||||
->groupBy(fn(array $item): string => strtolower((string) $item['node_type']))
|
->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)
|
->map(fn($items): array => collect($items)
|
||||||
->pluck('ip')
|
->pluck('ip')
|
||||||
->filter()
|
->filter()
|
||||||
|
|||||||
@@ -100,15 +100,23 @@ class ServerService
|
|||||||
*/
|
*/
|
||||||
public static function getServer($serverId, ?string $serverType)
|
public static function getServer($serverId, ?string $serverType)
|
||||||
{
|
{
|
||||||
return Server::query()
|
$baseQuery = Server::query()
|
||||||
->when($serverType, function ($query) use ($serverType) {
|
->when($serverType, function ($query) use ($serverType) {
|
||||||
$query->where('type', Server::normalizeType($serverType));
|
$query->where('type', Server::normalizeType($serverType));
|
||||||
})
|
});
|
||||||
->where(function ($query) use ($serverId) {
|
|
||||||
$query->where('code', $serverId)
|
$serverIdValue = is_scalar($serverId) ? (string) $serverId : '';
|
||||||
->orWhere('id', $serverId);
|
$isCanonicalInt = preg_match('/^(0|[1-9][0-9]*)$/', $serverIdValue) === 1;
|
||||||
})
|
|
||||||
->orderByRaw('CASE WHEN code = ? THEN 0 ELSE 1 END', [$serverId])
|
if ($isCanonicalInt) {
|
||||||
|
$serverById = (clone $baseQuery)->whereKey((int) $serverIdValue)->first();
|
||||||
|
if ($serverById) {
|
||||||
|
return $serverById;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (clone $baseQuery)
|
||||||
|
->where('code', $serverIdValue)
|
||||||
->first();
|
->first();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
@@ -49,17 +50,26 @@ class UserOnlineService
|
|||||||
$devices = collect($data)
|
$devices = collect($data)
|
||||||
->filter(fn(mixed $item): bool => is_array($item) && isset($item['aliveips']))
|
->filter(fn(mixed $item): bool => is_array($item) && isset($item['aliveips']))
|
||||||
->flatMap(function (array $nodeData, string $nodeKey): array {
|
->flatMap(function (array $nodeData, string $nodeKey): array {
|
||||||
|
$parsedNode = self::parseNodeMeta((string) $nodeKey);
|
||||||
|
|
||||||
return collect($nodeData['aliveips'])
|
return collect($nodeData['aliveips'])
|
||||||
->mapWithKeys(function (string $ipNodeId) use ($nodeData, $nodeKey): array {
|
->map(function (string $ipNodeId) use ($nodeData, $parsedNode): array {
|
||||||
$ip = Str::before($ipNodeId, '_');
|
$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 [
|
return [
|
||||||
$ip => [
|
'ip' => $ip,
|
||||||
'ip' => $ip,
|
'last_seen' => (int) ($nodeData['lastupdateAt'] ?? 0),
|
||||||
'last_seen' => $nodeData['lastupdateAt'],
|
'node_type' => $nodeType,
|
||||||
'node_type' => Str::before($nodeKey, (string) $nodeData['lastupdateAt'])
|
'node_id' => $nodeId > 0 ? $nodeId : null,
|
||||||
]
|
'node_key' => $nodeKeyValue,
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
|
->filter(fn(array $item): bool => !empty($item['ip']))
|
||||||
|
->values()
|
||||||
->all();
|
->all();
|
||||||
})
|
})
|
||||||
->values()
|
->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"),
|
default => throw new \InvalidArgumentException("Invalid device limit mode: $mode"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user