G1-G2: A1-A3自注册+BaseUrl修复+心跳重试+语法规范化+废弃标记

This commit is contained in:
2026-06-03 23:47:43 +08:00
parent faf8930de4
commit 5467f0c0e2
3 changed files with 86 additions and 52 deletions

View File

@@ -59,7 +59,8 @@ namespace Warehouse.Services
/// <param name="d">同步设备条目</param>
/// <param name="gatewayNodeId">网关节点ID</param>
/// <param name="existingIds">已有设备映射表 (AdapterCode, SourceId) → DeviceId</param>
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);

View File

@@ -49,8 +49,8 @@ namespace Warehouse.Services
/// </summary>
public async Task<gateway_nodes> RegisterNodeAsync(string nodeCode, string token, string adapterTypes, string baseUrl)
{
var existing = _repository.DbContext.Queryable<gateway_nodes>()
.First(x => x.NodeCode == nodeCode);
var existing = await _repository.FindAsIQueryable<gateway_nodes>()
.FirstOrDefaultAsync(x => x.NodeCode == nodeCode);
gateway_nodes entity;
if (existing != null)
@@ -91,8 +91,8 @@ namespace Warehouse.Services
/// </summary>
public async Task UpdateHeartbeatAsync(string nodeCode, string token)
{
var entity = _repository.DbContext.Queryable<gateway_nodes>()
.First(x => x.NodeCode == nodeCode && x.NodeToken == token);
var entity = _repository.FindAsIQueryable<gateway_nodes>()
.FirstOrDefaultAsync(x => x.NodeCode == nodeCode && x.NodeToken == token);
if (entity == null)
throw new UnauthorizedAccessException("认证失败NodeCode 或 Token 无效");

View File

@@ -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<object>();
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<object> devices, List<DeviceTreeNode> 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
// ═══════════════════════════════════════════════
/// <summary>Owl 适配器配置项</summary>
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";
}
/// <summary>MC4.0 适配器配置项</summary>
public class Mc4Config
{
public string? InstanceName { get; set; }
public string BaseUrl { get; set; } = "";
}
/// <summary>KMS 适配器配置项</summary>
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
// ═══════════════════════════════════════════════
/// <summary>云台控制请求</summary>
/// <param name="Direction">方向up/down/left/right/zoom_in/zoom_out/stop</param>
/// <param name="Action">动作类型continuous 或 stop</param>
/// <param name="Speed">速度 0.0-1.0</param>
record PtzRequest(string? Direction, string Action, float Speed);
/// <summary>设备控制请求</summary>
/// <param name="DeviceId">目标设备 SourceId</param>
/// <param name="PointIndex">点位索引</param>
/// <param name="Value">目标值</param>
record ControlRequest(string? DeviceId, int PointIndex, double Value);
record BatchRealtimeRequest(List<string>? DeviceIds);
record GatewayControlRequest(string? DeviceId, string? Command, Dictionary<string, object?>? Parameters);