Files
SecMPS/doc/设计文档/网关自动注册机制检查报告20260603.md

6.0 KiB
Raw Blame History

网关 ↔ 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 注册后立即遍历适配器同步设备,整体流程改为:

// 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.100Vol.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 使用 FindAsIQueryableVol.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