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(); var clientFactory = new GatewayClientFactory(httpFactory, volProUrl); // ── 注册 OwlAdapter(多实例遍历)── var owlList = app.Configuration.GetSection("Owl").Get>() ?? new(); foreach (var o in owlList) { var code = $"Owl:{o.InstanceName ?? "default"}"; var a = new IntegrationGateway.Adapters.Owl.OwlAdapter(code, app.Services.GetRequiredService().CreateClient("VolPro"), o.BaseUrl, o.Username, o.Password); registry.Register(a); } // ── 注册 KMSAdapter(多实例遍历)── var kmsList = app.Configuration.GetSection("KMS").Get>() ?? new(); foreach (var k in kmsList) { var code = $"KMS:{k.InstanceName ?? "default"}"; var a = new IntegrationGateway.Adapters.Kms.KmsAdapter(code, app.Services.GetRequiredService().CreateClient("VolPro"), k.BaseUrl, k.ClientId, k.ClientSecret); registry.Register(a); } // ── 注册 MC4Adapter(多实例遍历)── var mc4List = app.Configuration.GetSection("MC4").Get>() ?? new(); foreach (var m in mc4List) { var code = $"MC4:{m.InstanceName ?? "default"}"; var a = new IntegrationGateway.Adapters.MC4.Mc4Adapter(code, app.Services.GetRequiredService().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(); 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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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 // ═══════════════════════════════════════════════ /// Owl 适配器配置项 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"; } /// MC4.0 适配器配置项 public class Mc4Config { public string? InstanceName { get; set; } public string BaseUrl { get; set; } = ""; } /// KMS 适配器配置项 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 // ═══════════════════════════════════════════════ /// 云台控制请求 /// 方向:up/down/left/right/zoom_in/zoom_out/stop /// 动作类型:continuous 或 stop /// 速度 0.0-1.0 record PtzRequest(string? Direction, string Action, float Speed); /// 设备控制请求 /// 目标设备 SourceId /// 点位索引 /// 目标值 record ControlRequest(string? DeviceId, int PointIndex, double Value); record GatewayControlRequest(string? DeviceId, string? Command, Dictionary? Parameters); record SyncRequest(string? DataType, List? Items); record SyncDeleteRequest(string? DataType, List? Ids);