# 网关 ↔ Vol.Pro 自动注册机制检查报告 2026-06-03 > **日期**: 2026-06-03 > **检查范围**: `gateway/src/IntegrationGateway.Host/Program.cs` A1 注册段 + `VolPro gateway_nodesController.cs` A1-A4 + 相关 Service > **方法**: 逐行比对网关发送体 ↔ Vol.Pro 接收体 ↔ 数据库 Schema --- ## 1. 数据流追踪 ``` 网关 Program.cs (line 82-105) ├─ InitializeAllAsync() ← 适配器初始化 ├─ RegisterAsync(registerReq) ← A1: POST /api/gateway/register └─ (无后续调用) ← A2/A3 未触发 Vol.Pro gateway_nodesController ├─ [POST] /api/gateway/register ← RegisterGateway → RegisterNodeAsync ├─ [POST] /api/gateway/heartbeat ← GatewayHeartbeat → UpdateHeartbeatAsync ├─ [POST] /api/gateway/sync/devices ← SyncDevices → SyncDevicesAsync └─ [POST] /api/gateway/sync/alarms ← SyncAlarms → UpsertAlarmAsync ``` --- ## 2. 发现的问题 ### 2.1 🔴 网关不调用 A3 设备同步 — 注册后设备列表为空 **现状**: 网关 Program.cs 在 A1 注册后**不调用** `clientFactory.SyncDevicesAsync()`。Vol.Pro 的 `RegisterGateway` 返回设备列表时调用 `GetDevicesByGatewayNodeAsync(node.NodeId)`,查询 `WHERE NodeId=xxx AND ParentDeviceId IS NULL`。 **后果**: 首次注册返回的设备列表永远为空(数据库尚无此网关的设备记录)。设备必须等 Vol.Pro 的 Quartz `SyncDevicesJob`(每 5 分钟触发一次)才有机会同步。 **修复**: A1 注册后立即遍历适配器同步设备,整体流程改为: ```csharp // A1 注册 var registerResult = await clientFactory.RegisterAsync(registerReq); // A3 同步设备(Owl → GetDevicesAsync, MC4 → GetObjectTreeAsync) foreach (var adapter in registry.All) { var devices = adapter switch { IHasFlatDevices f => (await f.GetDevicesAsync(1, 1000)).Items, IHasOwnDeviceTree t => FlattenTree(await t.GetObjectTreeAsync()), _ => new() }; if (devices.Any()) await clientFactory.SyncDevicesAsync(nodeCode, nodeToken, devices.Select(d => new { ... }).ToList()); } ``` ### 2.2 🔴 网关 A1 BaseUrl 硬编码 `localhost` **现状**: ```csharp BaseUrl = $"http://localhost:{app.Urls.FirstOrDefault()?.Split(':').LastOrDefault() ?? "5100"}" ``` **后果**: 网关部署在 `192.168.1.100` 时,Vol.Pro 存的 BaseUrl 仍是 `http://localhost:5100`。Vol.Pro 端的 GatewayClient 和 Quartz Job 用此地址回调网关时全部失败。 **修复**: 改为读取配置或使用 `app.Configuration["Gateway:SelfUrl"]`,不填时降级为 `localhost`: ```csharp BaseUrl = gwCfg["SelfUrl"] ?? $"http://localhost:{port}" ``` ### 2.3 🟠 网关不调用 A2 心跳 — 无持续在线状态更新 **现状**: 网关只在 A1 注册时上报一次在线状态,之后不再调 A2 心跳。 **后果**: Vol.Pro 的 `gateway_nodes.LastHeartbeat` 停留在注册时刻,`HeartbeatMonitorJob`(每 15s)会在注册后 30s 将网关标记离线。 **修复**: 注册完成后启动后台心跳循环: ```csharp _ = Task.Run(async () => { while (true) { await Task.Delay(TimeSpan.FromSeconds(15)); try { await clientFactory.HeartbeatAsync(new GatewayHeartbeatRequest { NodeCode = nodeCode, Token = nodeToken }); } catch { Console.Error.WriteLine("[Gateway] 心跳失败"); } } }); ``` ### 2.4 🟠 Vol.Pro — RegisterNodeAsync 使用旧 Queryable 语法 **现状**: ```csharp var existing = _repository.DbContext.Queryable() .First(x => x.NodeCode == nodeCode); ``` `DbContext.Queryable` 是 SqlSugar 原始方式,项目中其他 Service 使用 `FindAsIQueryable`(Vol.Pro 封装)。 **后果**: 不影响功能但风格不一致。且 `.First()` 可能抛异常(找不到记录时),而 `.FirstOrDefault()` + null 检查更安全。 **修复**: 改为 `FindAsIQueryable(x => x.NodeCode == nodeCode).FirstOrDefaultAsync()`。 ### 2.5 🟡 A1 返回结果未被网关使用 **现状**: ```csharp var registerResult = await clientFactory.RegisterAsync(registerReq); // registerResult 未使用 ``` `RegisterGateway` 返回 `{ nodeId, devices: [...] }`,网关不读取也不使用。 **后果**: 网关不知道自己的 NodeId,后续 A2/A3 需要 nodeCode + token 而非 nodeId。GatewayClientFactory 的 A2/A3 方法也用的是 nodeCode + token,所以不依赖 nodeId。 **评估**: **不需要修复** — 当前设计合理(nodeCode 是天然业务主键)。 ### 2.6 🟡 base_deviceService 与 gateway_nodesService 重复实现设备同步 **现状**: - `gateway_nodesService.SyncDevicesAsync` — 完整的设备同步(新增+更新) - `base_deviceService.UpsertDeviceAsync` — 单设备 Upsert(被 Controller 调用但实际未被使用) `gateway_nodesController.SyncDevices` 调的是 `gateway_nodesService.SyncDevicesAsync` 而非 `base_deviceService.UpsertDeviceAsync`。 **后果**: `base_deviceService.UpsertDeviceAsync` 是死代码。 **修复**: 保留 `gateway_nodesService.SyncDevicesAsync`(批量处理更高效),移除或标记 `base_deviceService.UpsertDeviceAsync` 为 `[Obsolete]`。 --- ## 3. 调用链完整性矩阵 | 接口 | Vol.Pro 端 | 网关调用 | 状态 | |------|:---:|:---:|:--:| | A1 /api/gateway/register | ✅ RegisterGateway | ✅ 已调用 | ⚠️ BaseUrl=localhost / 不返设备 | | A2 /api/gateway/heartbeat | ✅ GatewayHeartbeat | ❌ 未调用 | 🔴 30秒后被标记离线 | | A3 /api/gateway/sync/devices | ✅ SyncDevices | ❌ 未调用 | 🔴 首次注册设备列表为空 | | A4 /api/gateway/sync/alarms | ✅ SyncAlarms | ❌ 未调用 | ⚪ 告警由 Vol.Pro Quartz 拉取 | --- ## 4. 修复优先级 | 编号 | 问题 | 严重度 | 预计 | |:---:|------|:---:|:---:| | 1 | A3 设备同步未触发 | 🔴 | 30min | | 2 | A1 BaseUrl=localhost | 🔴 | 10min | | 3 | A2 心跳未循环 | 🟠 | 15min | | 4 | RegisterNodeAsync 语法不一致 | 🟠 | 5min | | 5 | 重复的设备 Upsert 逻辑 | 🟡 | 10min |