From d30c958dc491507b40640976a64d8eef09df6252 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Wed, 15 Jul 2026 15:53:47 +0300 Subject: [PATCH] Refactor VPN config creation logic to improve node status handling - Removed the dependency on `Node.Status` for VPN config creation, addressing potential false negatives due to cached health-check data. - Updated documentation to clarify that `Node.Status` is a diagnostic indicator and not a gate for config creation, ensuring accurate understanding of node availability checks. - Enhanced comments in the code to explain the rationale behind the changes, improving maintainability and clarity for future developers. --- .../Create/CreateVpnConfigCommandHandler.cs | 7 +++++-- docs/domain-model.md | 14 ++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/backend/src/PnvPanel.Application/Configs/Create/CreateVpnConfigCommandHandler.cs b/backend/src/PnvPanel.Application/Configs/Create/CreateVpnConfigCommandHandler.cs index 15f9115..aaf2703 100644 --- a/backend/src/PnvPanel.Application/Configs/Create/CreateVpnConfigCommandHandler.cs +++ b/backend/src/PnvPanel.Application/Configs/Create/CreateVpnConfigCommandHandler.cs @@ -4,7 +4,6 @@ using PnvPanel.Application.Common.Interfaces; using PnvPanel.Application.Common.Messaging; using PnvPanel.Application.Common.Models; using PnvPanel.Domain.Configs; -using PnvPanel.Domain.Nodes; namespace PnvPanel.Application.Configs.Create; @@ -40,7 +39,11 @@ public sealed class CreateVpnConfigCommandHandler( var node = await dbContext .Nodes.AsNoTracking() .FirstOrDefaultAsync(n => n.Id == inbound.NodeId, cancellationToken); - if (node is null || !node.IsEnabled || node.Status == NodeStatus.Offline) + // Node.Status — это кэш периодического health-check'а (раз в 2 минуты), а не проверка + // в реальном времени: блокировать по нему создание конфига значит ловить ложные отказы на + // временных сетевых сбоях пробника. Реальную недоступность ловит AddClientAsync ниже — + // тот бьёт в панель прямо сейчас и возвращает честную ошибку с компенсацией. + if (node is null || !node.IsEnabled) return Result.Failure(ConfigErrors.NodeDisabled); var config = VpnConfig.Create(userId, inbound.Id, inbound.Protocol, command.Label); diff --git a/docs/domain-model.md b/docs/domain-model.md index 229b8f1..98fa046 100644 --- a/docs/domain-model.md +++ b/docs/domain-model.md @@ -65,10 +65,16 @@ AppUser | `DisplayName` | `string?` | Витринное имя для пользователя, напр. «Германия (Trojan)» | | `LastSyncAt` | `DateTimeOffset?` | | -Инварианты: конфиг можно создать только если `IsPublished && Node.IsEnabled && Node.Status != -Offline`, и **роль пользователя входит в `AllowedRoles`**. Публикация инбаунда админом включает -выбор `AllowedRoles` (напр. «Германия (Trojan)» → роли `user`, `vip`). Лимита числа клиентов на -инбаунд нет — квота ограничивается только на уровне пользователя (`AppRole.MaxConfigs`). +Инварианты: конфиг можно создать только если `IsPublished && Node.IsEnabled`, и **роль пользователя +входит в `AllowedRoles`**. Публикация инбаунда админом включает выбор `AllowedRoles` (напр. +«Германия (Trojan)» → роли `user`, `vip`). Лимита числа клиентов на инбаунд нет — квота +ограничивается только на уровне пользователя (`AppRole.MaxConfigs`). + +> `Node.Status` (health-check раз в 2 минуты, см. `NodeHealthCheckService`) — это диагностический +> индикатор для админа, не гейт для создания конфига: он кэшированный и может ложно показывать +> `Offline` из-за временного сбоя пробника. Реальную недоступность ноды ловит вызов +> `IXuiPanelGateway.AddClientAsync` в момент создания — с честной ошибкой и компенсацией +> зарезервированной квоты, а не заранее закэшированным статусом. > **Пользователю показываем только `DisplayName` + протокол.** Адрес/хост ноды, `RemoteInboundId`, > `Port` и прочие детали 3x-ui в пользовательские DTO не попадают (только в админские).