diff --git a/backend/Directory.Packages.props b/backend/Directory.Packages.props index 64504a9..109e280 100644 --- a/backend/Directory.Packages.props +++ b/backend/Directory.Packages.props @@ -28,7 +28,7 @@ - + diff --git a/backend/src/PnvPanel.Application/Common/Interfaces/IXuiPanelGateway.cs b/backend/src/PnvPanel.Application/Common/Interfaces/IXuiPanelGateway.cs index 351c02f..f2e271d 100644 --- a/backend/src/PnvPanel.Application/Common/Interfaces/IXuiPanelGateway.cs +++ b/backend/src/PnvPanel.Application/Common/Interfaces/IXuiPanelGateway.cs @@ -112,8 +112,11 @@ public interface IXuiPanelGateway ); /// - /// Трафик по клиентам инбаунда, ключ — ClientEmail. ThreeXui.Net не даёт типизированного метода - /// для этого — извлекается из сырого clientStats[] в RawInboundJson (стандартное поле 3x-ui API). + /// Трафик по клиентам инбаунда, ключ — ClientEmail. Реализация — через + /// ThreeXui.Net's IXuiClient.GetInboundClientTrafficAsync (1.0.3+), а не + /// GetInboundAsync: на части версий 3x-ui (подтверждено на v2.8.11) одиночный + /// эндпоинт инбаунда не подгружает clientStats, из-за чего трафик молча не + /// синхронизировался бы никогда. /// Task>> GetClientTrafficAsync( Node node, diff --git a/backend/src/PnvPanel.Infrastructure/Xui/XuiPanelGateway.cs b/backend/src/PnvPanel.Infrastructure/Xui/XuiPanelGateway.cs index 1cb160e..8a62c98 100644 --- a/backend/src/PnvPanel.Infrastructure/Xui/XuiPanelGateway.cs +++ b/backend/src/PnvPanel.Infrastructure/Xui/XuiPanelGateway.cs @@ -294,15 +294,20 @@ internal sealed class XuiPanelGateway( try { var client = GetClient(node); - var remoteInbound = await client.GetInboundAsync(inboundRemoteId, cancellationToken); - if (remoteInbound is null) - { - return Result.Failure>( - Error.Failure("Xui.InboundNotFound", "Inbound не найден на панели.") - ); - } + // GetInboundClientTrafficAsync (не GetInboundAsync!) — на 3x-ui v2.8.11 одиночный + // /panel/api/inbounds/get/{id} не подгружает clientStats (нет Preload на этом пути в + // самой панели), из-за чего трафик молча не синхронизировался бы вообще. Список + // инбаундов подгружает clientStats всегда — см. ThreeXui.Net CHANGELOG 1.0.3. + var traffic = await client.GetInboundClientTrafficAsync( + inboundRemoteId, + cancellationToken + ); - return Result.Success(ParseClientStats(remoteInbound.RawInboundJson)); + var result = new Dictionary(traffic.Count); + foreach (var entry in traffic) + result[entry.Email] = new ClientTrafficInfo(entry.UpBytes, entry.DownBytes); + + return Result.Success>(result); } catch (Exception ex) { @@ -312,53 +317,6 @@ internal sealed class XuiPanelGateway( } } - /// - /// ThreeXui.Net типизированно не отдаёт трафик по клиентам — достаём его из сырого JSON инбаунда: - /// стандартное поле 3x-ui API "clientStats": [{ "email": "...", "up": N, "down": N }, ...]. - /// Формат форка может отличаться — при ошибке парсинга просто возвращаем пусто, не валим синхронизацию. - /// - private static IReadOnlyDictionary ParseClientStats( - string? rawInboundJson - ) - { - var result = new Dictionary(); - if (string.IsNullOrWhiteSpace(rawInboundJson)) - return result; - - try - { - using var doc = JsonDocument.Parse(rawInboundJson); - if ( - !doc.RootElement.TryGetProperty("clientStats", out var clientStats) - || clientStats.ValueKind != JsonValueKind.Array - ) - return result; - - foreach (var stat in clientStats.EnumerateArray()) - { - if ( - !stat.TryGetProperty("email", out var emailProp) - || emailProp.ValueKind != JsonValueKind.String - ) - continue; - - var email = emailProp.GetString(); - if (string.IsNullOrEmpty(email)) - continue; - - var up = stat.TryGetProperty("up", out var upProp) ? upProp.GetInt64() : 0; - var down = stat.TryGetProperty("down", out var downProp) ? downProp.GetInt64() : 0; - result[email] = new ClientTrafficInfo(up, down); - } - } - catch (JsonException) - { - // Не удалось распарсить — возвращаем пусто, вызывающий код просто пропустит синк для этого инбаунда. - } - - return result; - } - public void InvalidateClient(Guid nodeId) { if (_clients.TryRemove(nodeId, out var lazy) && lazy.IsValueCreated)