From 5467f0c0e2a34f05e35ce66fc1cdb3401bdd5b7e Mon Sep 17 00:00:00 2001 From: g82tt Date: Wed, 3 Jun 2026 23:47:43 +0800 Subject: [PATCH] =?UTF-8?q?G1-G2:=20A1-A3=E8=87=AA=E6=B3=A8=E5=86=8C+BaseU?= =?UTF-8?q?rl=E4=BF=AE=E5=A4=8D+=E5=BF=83=E8=B7=B3=E9=87=8D=E8=AF=95+?= =?UTF-8?q?=E8=AF=AD=E6=B3=95=E8=A7=84=E8=8C=83=E5=8C=96+=E5=BA=9F?= =?UTF-8?q?=E5=BC=83=E6=A0=87=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Partial/base_deviceService.cs | 3 +- .../Partial/gateway_nodesService.cs | 8 +- .../src/IntegrationGateway.Host/Program.cs | 127 +++++++++++------- 3 files changed, 86 insertions(+), 52 deletions(-) diff --git a/api_sqlsugar/Warehouse/Services/device_manager/Partial/base_deviceService.cs b/api_sqlsugar/Warehouse/Services/device_manager/Partial/base_deviceService.cs index fc7a46b..290a12a 100644 --- a/api_sqlsugar/Warehouse/Services/device_manager/Partial/base_deviceService.cs +++ b/api_sqlsugar/Warehouse/Services/device_manager/Partial/base_deviceService.cs @@ -59,7 +59,8 @@ namespace Warehouse.Services /// 同步设备条目 /// 网关节点ID /// 已有设备映射表 (AdapterCode, SourceId) → DeviceId - public async Task UpsertDeviceAsync(SyncDeviceItem d, int gatewayNodeId, Dictionary<(string, string), int> existingIds) + [Obsolete("已迁移至 gateway_nodesService.SyncDevicesAsync")] + public async Task UpsertDeviceAsync(SyncDeviceItem d, int gatewayNodeId, Dictionary<(string, string), int> existingIds) { var db = _repository.DbContext; var key = (d.AdapterCode, d.SourceId); diff --git a/api_sqlsugar/Warehouse/Services/device_manager/Partial/gateway_nodesService.cs b/api_sqlsugar/Warehouse/Services/device_manager/Partial/gateway_nodesService.cs index 4147785..b601647 100644 --- a/api_sqlsugar/Warehouse/Services/device_manager/Partial/gateway_nodesService.cs +++ b/api_sqlsugar/Warehouse/Services/device_manager/Partial/gateway_nodesService.cs @@ -49,8 +49,8 @@ namespace Warehouse.Services /// public async Task RegisterNodeAsync(string nodeCode, string token, string adapterTypes, string baseUrl) { - var existing = _repository.DbContext.Queryable() - .First(x => x.NodeCode == nodeCode); + var existing = await _repository.FindAsIQueryable() + .FirstOrDefaultAsync(x => x.NodeCode == nodeCode); gateway_nodes entity; if (existing != null) @@ -91,8 +91,8 @@ namespace Warehouse.Services /// public async Task UpdateHeartbeatAsync(string nodeCode, string token) { - var entity = _repository.DbContext.Queryable() - .First(x => x.NodeCode == nodeCode && x.NodeToken == token); + var entity = _repository.FindAsIQueryable() + .FirstOrDefaultAsync(x => x.NodeCode == nodeCode && x.NodeToken == token); if (entity == null) throw new UnauthorizedAccessException("认证失败:NodeCode 或 Token 无效"); diff --git a/gateway/src/IntegrationGateway.Host/Program.cs b/gateway/src/IntegrationGateway.Host/Program.cs index b4141e7..72a55f1 100644 --- a/gateway/src/IntegrationGateway.Host/Program.cs +++ b/gateway/src/IntegrationGateway.Host/Program.cs @@ -92,22 +92,99 @@ Console.WriteLine($"[Gateway] {registry.All.Count} 个适配器已注册: {adapt // ── A1: 向 Vol.Pro 注册当前网关节点 ── var nodeCode = gwCfg["NodeCode"] ?? "gw-default"; var nodeToken = Environment.GetEnvironmentVariable("SECMPS_GATEWAY_TOKEN") ?? gwCfg["NodeToken"] ?? ""; +var port = app.Urls.FirstOrDefault()?.Split(':').LastOrDefault() ?? "5100"; +var selfUrl = gwCfg["SelfUrl"] ?? $"http://localhost:{port}"; try { var registerReq = new GatewayRegisterRequest { NodeCode = nodeCode, Token = nodeToken, AdapterTypes = adapterTypes, - BaseUrl = $"http://localhost:{app.Urls.FirstOrDefault()?.Split(':').LastOrDefault() ?? "5100"}" + BaseUrl = selfUrl }; var registerResult = await clientFactory.RegisterAsync(registerReq); Console.WriteLine($"[Gateway] A1 注册完成: nodeCode={nodeCode}, adapters={adapterTypes}"); } catch (Exception ex) { Console.Error.WriteLine($"[Gateway] A1 注册失败: {ex.Message}"); } +// ── A3: 同步所有适配器设备到 Vol.Pro ── +await SyncAllDevicesAsync(nodeCode, nodeToken, selfUrl); +Console.WriteLine("[Gateway] A3 设备同步完成"); + +// ── A2: 心跳 + 自动重注册 ── +var heartbeatInterval = int.TryParse(gwCfg["HeartbeatIntervalSec"], out var hs) ? hs : 15; +var failCount = 0; var maxFails = 3; +_ = Task.Run(async () => +{ + using var timer = new PeriodicTimer(TimeSpan.FromSeconds(heartbeatInterval)); + while (await timer.WaitForNextTickAsync()) + { + try + { + await clientFactory.HeartbeatAsync(new GatewayHeartbeatRequest { NodeCode = nodeCode, Token = nodeToken }); + failCount = 0; + } + catch + { + failCount++; + Console.Error.WriteLine($"[Gateway] A2 心跳失败 ({failCount}/{maxFails})"); + if (failCount >= maxFails) + { + Console.WriteLine("[Gateway] 心跳连续失败, 尝试重新注册..."); + try + { + await clientFactory.RegisterAsync(new GatewayRegisterRequest { NodeCode = nodeCode, Token = nodeToken, AdapterTypes = adapterTypes, BaseUrl = selfUrl }); + await SyncAllDevicesAsync(nodeCode, nodeToken, selfUrl); + failCount = 0; + Console.WriteLine("[Gateway] 重新注册成功"); + } + catch (Exception re) { Console.Error.WriteLine($"[Gateway] 重新注册失败: {re.Message}"); } + } + } + } +}); +Console.WriteLine($"[Gateway] A2 心跳已启动 ({heartbeatInterval}s)"); + // ═══════════════════════════════════════════════════════════════ -// B 组路由(管理端 / Vol.Pro → 网关) -// 所有路由通过适配器编码查找对应适配器,按能力接口分发请求 +// A 组辅助函数 +// ═══════════════════════════════════════════════════════════════ + +async Task SyncAllDevicesAsync(string nc, string nt, string baseUrl) +{ + var allDevices = new List(); + foreach (var adapter in registry.All) + { + try + { + if (adapter is IHasFlatDevices flat) + { + var result = await flat.GetDevicesAsync(1, 1000); + foreach (var item in result.Items) + allDevices.Add(new { AdapterCode = item.AdapterCode, SourceId = item.SourceId, Name = item.Name, Category = item.Category, Group = item.Group, IsParent = item.IsParent, ParentSourceId = item.ParentSourceId, IsOnline = item.IsOnline, IpAddress = item.IpAddress, Port = item.Port, ExtraDataJson = item.Extra != null ? System.Text.Json.JsonSerializer.Serialize(item.Extra) : null }); + } + else if (adapter is IHasOwnDeviceTree tree) + { + var nodes = await tree.GetObjectTreeAsync(); + FlattenTree(allDevices, nodes, adapter.AdapterCode, null); + } + } + catch { } + } + if (allDevices.Any()) + await clientFactory.SyncDevicesAsync(nc, nt, allDevices); +} + +void FlattenTree(List devices, List nodes, string ac, string? parentSourceId) +{ + foreach (var n in nodes) + { + devices.Add(new { AdapterCode = ac, SourceId = n.SourceId, Name = n.Name ?? n.SourceId, Category = n.Tag ?? "IoT设备", Group = "IoT设备", IsParent = n.Type == 1, ParentSourceId = parentSourceId, IsOnline = true, IpAddress = (string?)null, Port = (int?)null, ExtraDataJson = n.Option != null ? System.Text.Json.JsonSerializer.Serialize(n.Option) : null }); + if (n.Children?.Count > 0) FlattenTree(devices, n.Children, ac, n.SourceId); + } +} + +// ═══════════════════════════════════════════════════════════════ +// B 组路由(管理端/ Vol.Pro → 网关) // ═══════════════════════════════════════════════════════════════ // B1: 健康检查 — 返回所有适配器的健康状态和能力声明 @@ -291,50 +368,6 @@ app.MapPost("/api/gateway/devices/sync", async (string adapter) => }); app.Run(); - -// ═══════════════════════════════════════════════ -// 配置 POCO -// ═══════════════════════════════════════════════ - -/// Owl 适配器配置项 -public class OwlConfig -{ - public string? InstanceName { get; set; } - public string BaseUrl { get; set; } = ""; - public string Username { get; set; } = "admin"; - public string Password { get; set; } = "admin"; -} - -/// MC4.0 适配器配置项 -public class Mc4Config -{ - public string? InstanceName { get; set; } - public string BaseUrl { get; set; } = ""; -} - -/// KMS 适配器配置项 -public class KmsConfig -{ - public string? InstanceName { get; set; } - public string BaseUrl { get; set; } = ""; - public string ClientId { get; set; } = ""; - public string ClientSecret { get; set; } = ""; -} - -// ═══════════════════════════════════════════════ -// B 组请求 DTO -// ═══════════════════════════════════════════════ - -/// 云台控制请求 -/// 方向:up/down/left/right/zoom_in/zoom_out/stop -/// 动作类型:continuous 或 stop -/// 速度 0.0-1.0 -record PtzRequest(string? Direction, string Action, float Speed); - -/// 设备控制请求 -/// 目标设备 SourceId -/// 点位索引 -/// 目标值 record ControlRequest(string? DeviceId, int PointIndex, double Value); record BatchRealtimeRequest(List? DeviceIds); record GatewayControlRequest(string? DeviceId, string? Command, Dictionary? Parameters);