6.0 KiB
网关 ↔ Vol.Pro 自动注册机制检查报告 2026-06-03
日期: 2026-06-03 检查范围:
gateway/src/IntegrationGateway.Host/Program.csA1 注册段 +VolPro gateway_nodesController.csA1-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 注册后立即遍历适配器同步设备,整体流程改为:
// 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
现状:
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:
BaseUrl = gwCfg["SelfUrl"] ?? $"http://localhost:{port}"
2.3 🟠 网关不调用 A2 心跳 — 无持续在线状态更新
现状: 网关只在 A1 注册时上报一次在线状态,之后不再调 A2 心跳。
后果: Vol.Pro 的 gateway_nodes.LastHeartbeat 停留在注册时刻,HeartbeatMonitorJob(每 15s)会在注册后 30s 将网关标记离线。
修复: 注册完成后启动后台心跳循环:
_ = 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 语法
现状:
var existing = _repository.DbContext.Queryable<gateway_nodes>()
.First(x => x.NodeCode == nodeCode);
DbContext.Queryable 是 SqlSugar 原始方式,项目中其他 Service 使用 FindAsIQueryable(Vol.Pro 封装)。
后果: 不影响功能但风格不一致。且 .First() 可能抛异常(找不到记录时),而 .FirstOrDefault() + null 检查更安全。
修复: 改为 FindAsIQueryable(x => x.NodeCode == nodeCode).FirstOrDefaultAsync()。
2.5 🟡 A1 返回结果未被网关使用
现状:
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 |