G1-G2: A1-A3自注册+BaseUrl修复+心跳重试+语法规范化+废弃标记
This commit is contained in:
@@ -59,6 +59,7 @@ namespace Warehouse.Services
|
||||
/// <param name="d">同步设备条目</param>
|
||||
/// <param name="gatewayNodeId">网关节点ID</param>
|
||||
/// <param name="existingIds">已有设备映射表 (AdapterCode, SourceId) → DeviceId</param>
|
||||
[Obsolete("已迁移至 gateway_nodesService.SyncDevicesAsync")]
|
||||
public async Task UpsertDeviceAsync(SyncDeviceItem d, int gatewayNodeId, Dictionary<(string, string), int> existingIds)
|
||||
{
|
||||
var db = _repository.DbContext;
|
||||
|
||||
@@ -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 无效");
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user