Files
SecMPS/gateway/src/IntegrationGateway.Host/Program.cs

325 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using IntegrationGateway.Core.Abstractions;
using IntegrationGateway.Core.Infrastructure;
using IntegrationGateway.Core.Models;
// ═══════════════════════════════════════════════════════════════
// IntegrationGateway 宿主启动程序
//
// 职责:
// 1. 注册 IHttpClientFactory连接池复用
// 2. 创建并注册 OwlAdapter + MC4Adapter
// 3. 并行初始化所有适配器
// 4. 注册 14 个 B 组 REST 端点
// ═══════════════════════════════════════════════════════════════
var builder = WebApplication.CreateBuilder(args);
// ── 注册 HttpClient 工厂 ──
// 命名客户端 "VolPro":用于调用 Vol.Pro A 组接口和适配器内部 HTTP 请求
// 连接池:最多 10 个并发连接5 分钟生命周期
builder.Services.AddHttpClient("VolPro", c =>
{
c.Timeout = TimeSpan.FromSeconds(30);
c.DefaultRequestHeaders.Add("Accept", "application/json");
}).ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
MaxConnectionsPerServer = 10
});
builder.Services.AddCors(o => o.AddDefaultPolicy(p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()));
var app = builder.Build();
app.UseCors();
// ── 读取配置 ──
var gwCfg = app.Configuration.GetSection("Gateway");
// ── 创建适配器注册中心 ──
var registry = new AdapterRegistry();
// ── 创建 Vol.Pro 客户端工厂(用于 A1-A4 回调) ──
var volProUrl = gwCfg["VolProBaseUrl"] ?? "http://localhost:9100";
var httpFactory = app.Services.GetRequiredService<IHttpClientFactory>();
var clientFactory = new GatewayClientFactory(httpFactory, volProUrl);
// ── 注册 OwlAdapter多实例遍历──
var owlList = app.Configuration.GetSection("Owl").Get<List<OwlConfig>>() ?? new();
foreach (var o in owlList)
{
var code = $"Owl:{o.InstanceName ?? "default"}";
var a = new IntegrationGateway.Adapters.Owl.OwlAdapter(code,
app.Services.GetRequiredService<IHttpClientFactory>().CreateClient("VolPro"),
o.BaseUrl, o.Username, o.Password);
registry.Register(a);
}
// ── 注册 KMSAdapter多实例遍历──
var kmsList = app.Configuration.GetSection("KMS").Get<List<KmsConfig>>() ?? new();
foreach (var k in kmsList)
{
var code = $"KMS:{k.InstanceName ?? "default"}";
var a = new IntegrationGateway.Adapters.Kms.KmsAdapter(code,
app.Services.GetRequiredService<IHttpClientFactory>().CreateClient("VolPro"),
k.BaseUrl, k.ClientId, k.ClientSecret);
registry.Register(a);
}
// ── 注册 MC4Adapter多实例遍历──
var mc4List = app.Configuration.GetSection("MC4").Get<List<Mc4Config>>() ?? new();
foreach (var m in mc4List)
{
var code = $"MC4:{m.InstanceName ?? "default"}";
var a = new IntegrationGateway.Adapters.MC4.Mc4Adapter(code,
app.Services.GetRequiredService<IHttpClientFactory>().CreateClient("VolPro"),
m.BaseUrl);
registry.Register(a);
}
// ── 动态拼接 AdapterTypes 字符串 ──
var adapterTypes = string.Join(",", registry.All.Select(a => a.AdapterCode));
// ── 并行初始化所有适配器 ──
await registry.InitializeAllAsync();
Console.WriteLine($"[Gateway] {registry.All.Count} 个适配器已注册: {adapterTypes}");
// ── A1: 向 Vol.Pro 注册当前网关节点 ──
var nodeCode = gwCfg["NodeCode"] ?? "gw-default";
var nodeToken = Environment.GetEnvironmentVariable("SECMPS_GATEWAY_TOKEN") ?? gwCfg["NodeToken"] ?? "";
try
{
var registerReq = new GatewayRegisterRequest
{
NodeCode = nodeCode, Token = nodeToken,
AdapterTypes = adapterTypes,
BaseUrl = $"http://localhost:{app.Urls.FirstOrDefault()?.Split(':').LastOrDefault() ?? "5100"}"
};
var registerResult = await clientFactory.RegisterAsync(registerReq);
Console.WriteLine($"[Gateway] A1 注册完成: nodeCode={nodeCode}, adapters={adapterTypes}");
}
catch (Exception ex) { Console.Error.WriteLine($"[Gateway] A1 注册失败: {ex.Message}"); }
// ═══════════════════════════════════════════════════════════════
// B 组路由(管理端 / Vol.Pro → 网关)
// 所有路由通过适配器编码查找对应适配器,按能力接口分发请求
// ═══════════════════════════════════════════════════════════════
// B1: 健康检查 — 返回所有适配器的健康状态和能力声明
app.MapGet("/api/gateway/health", async () =>
{
var results = new List<object>();
foreach (var a in registry.All)
{
bool healthy = false;
try { healthy = await a.HealthCheckAsync(); } catch { }
results.Add(new { a.AdapterCode, a.DisplayName, Healthy = healthy, a.Capabilities });
}
return Results.Ok(results);
});
// B2: 设备列表 — 分页获取扁平设备列表Owl/门禁/道闸)
app.MapGet("/api/gateway/devices", async (string adapter, int page, int size, string? keyword) =>
{
var a = registry.FindByCode<IHasFlatDevices>(adapter);
if (a == null) return Results.NotFound(new { error = "ADAPTER_NOT_FOUND", message = $"适配器 '{adapter}' 不存在或不支持扁平设备列表" });
return Results.Ok(await a.GetDevicesAsync(page, size, keyword));
});
// B3: 对象树 — 获取层级对象树MC4.0
app.MapGet("/api/gateway/tree", async (string adapter) =>
{
var a = registry.FindByCode<IHasOwnDeviceTree>(adapter);
if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED", message = $"适配器 '{adapter}' 不支持对象树" });
return Results.Ok(await a.GetObjectTreeAsync());
});
// B6a: 实时取流 — 获取视频通道的实时流地址
app.MapGet("/api/gateway/streams/{adapter}/{deviceId}/live", async (string adapter, string deviceId) =>
{
var a = registry.FindByCode<IHasStreams>(adapter);
if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED", message = $"适配器 '{adapter}' 不支持视频取流" });
var result = await a.GetLiveUrlAsync(deviceId);
return result.WsFlv == null && result.Hls == null
? Results.Problem("未获取到流地址", statusCode: 502)
: Results.Ok(result);
});
// B6b: 录像回放 — 获取历史录像 HLS 地址
app.MapGet("/api/gateway/streams/{adapter}/{deviceId}/playback", async (string adapter, string deviceId, DateTime start, DateTime end) =>
{
var a = registry.FindByCode<IHasStreams>(adapter);
if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED" });
return Results.Ok(await a.GetPlaybackUrlAsync(deviceId, start, end));
});
// 截图 — 获取通道实时截图
app.MapPost("/api/gateway/streams/{adapter}/{deviceId}/snapshot", async (string adapter, string deviceId) =>
{
var a = registry.FindByCode<IHasStreams>(adapter);
if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED" });
return Results.Ok(await a.GetSnapshotAsync(deviceId));
});
// B7: 云台控制 — continuous 方向移动 + stop
app.MapPost("/api/gateway/streams/{adapter}/{deviceId}/ptz", async (string adapter, string deviceId, PtzRequest req) =>
{
var a = registry.FindByCode<IHasStreams>(adapter);
if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED" });
if (req.Action == "stop") await a.PtzStopAsync(deviceId);
else await a.PtzControlAsync(deviceId, req.Direction ?? "stop", req.Speed);
return Results.Ok();
});
// B4: 实时点位值 — 获取 IoT 设备测点当前读数
app.MapGet("/api/gateway/realtime/{adapter}/{deviceId}", async (string adapter, string deviceId) =>
{
var a = registry.FindByCode<IHasPoints>(adapter);
if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED" });
return Results.Ok(await a.GetRealtimeValuesAsync(deviceId));
});
// B5: 设备控制 — 向 IoT 设备下发控制指令
app.MapPost("/api/gateway/realtime/{adapter}/control", async (string adapter, ControlRequest req) =>
{
var a = registry.FindByCode<IHasPoints>(adapter);
if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED" });
await a.SetPointValueAsync(req.DeviceId ?? "", req.PointIndex, req.Value);
return Results.Ok();
});
// B8: 告警查询 — 分页获取告警列表
app.MapGet("/api/gateway/alarms/{adapter}", async (string adapter, int page, int size, DateTime from, DateTime to, string? level, string? state) =>
{
var a = registry.FindByCode<IHasAlarms>(adapter);
if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED" });
return Results.Ok(await a.GetAlarmsAsync(page, size, from, to, level, state));
});
// B9: 告警确认 — 确认告警并写回子系统
app.MapPost("/api/gateway/alarms/{adapter}/{alarmId}/confirm", async (string adapter, string alarmId) =>
{
var a = registry.FindByCode<IHasAlarms>(adapter);
if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED" });
await a.ConfirmAlarmAsync(alarmId);
return Results.Ok();
});
// 告警结束 — 结束告警并写回子系统
app.MapPost("/api/gateway/alarms/{adapter}/{alarmId}/end", async (string adapter, string alarmId) =>
{
var a = registry.FindByCode<IHasAlarms>(adapter);
if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED" });
await a.EndAlarmAsync(alarmId);
return Results.Ok();
});
// 录像查询 — 分页获取录像文件列表
app.MapGet("/api/gateway/recordings/{adapter}/{deviceId}", async (string adapter, string deviceId, DateTime start, DateTime end, int page, int size) =>
{
var a = registry.FindByCode<IHasRecordings>(adapter);
if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED" });
return Results.Ok(await a.GetRecordingsAsync(deviceId, start, end, page, size));
});
// B10: 设备控制 — 下发控制指令(远程开门/抬杆/授权)
app.MapPost("/api/gateway/control/{adapter}", async (string adapter, GatewayControlRequest req) =>
{
var a = registry.FindByCode<IAcceptsControl>(adapter);
if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED" });
var result = await a.SendControlAsync(req.DeviceId ?? "", req.Command ?? "open", req.Parameters ?? new());
return result.Success ? Results.Ok(result) : Results.Problem(result.Message, statusCode: 502);
});
// B11: 业务记录查询 — 借还/交接/授权记录
app.MapGet("/api/gateway/logs/{adapter}", async (string adapter, string logType, DateTime? from, DateTime? to, int page, int size) =>
{
var a = registry.FindByCode<IHasBusinessLogs>(adapter);
if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED" });
return Results.Ok(await a.GetBusinessLogsAsync(logType, from, to, page, size));
});
// B12: 数据同步 — 向子系统写入数据(员工同步)
app.MapPost("/api/gateway/sync/{adapter}", async (string adapter, SyncRequest req) =>
{
var a = registry.FindByCode<IAcceptsDataSync>(adapter);
if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED" });
return Results.Ok(await a.SyncDataAsync(req.DataType ?? "staff", req.Items ?? new()));
});
// B13: 数据删除 — 从子系统删除数据
app.MapDelete("/api/gateway/sync/{adapter}", async (string adapter, SyncDeleteRequest req) =>
{
var a = registry.FindByCode<IAcceptsDataSync>(adapter);
if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED" });
return Results.Ok(await a.DeleteDataAsync(req.DataType ?? "staff", req.Ids ?? new()));
});
// B3: 手动同步 — 触发适配器全量设备同步
app.MapPost("/api/gateway/devices/sync", async (string adapter) =>
{
var a = registry.FindByCode<IGatewayAdapter>(adapter);
if (a == null) return Results.NotFound(new { error = "ADAPTER_NOT_FOUND" });
// 根据适配器能力触发对应同步逻辑
if (a is IHasOwnDeviceTree tree)
{
var obj = await tree.GetObjectTreeAsync();
return Results.Ok(new { nodeCount = obj.Count, message = "对象树同步完成" });
}
if (a is IHasFlatDevices flat)
{
var dev = await flat.GetDevicesAsync(1, 1000);
return Results.Ok(new { deviceCount = dev.Total, message = "设备列表同步完成" });
}
return Results.Ok(new { message = "无需同步" });
});
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 GatewayControlRequest(string? DeviceId, string? Command, Dictionary<string, object?>? Parameters);
record SyncRequest(string? DataType, List<object>? Items);
record SyncDeleteRequest(string? DataType, List<string>? Ids);