diff --git a/gateway/src/IntegrationGateway.Host/Program.cs b/gateway/src/IntegrationGateway.Host/Program.cs index 1760df1..930aba9 100644 --- a/gateway/src/IntegrationGateway.Host/Program.cs +++ b/gateway/src/IntegrationGateway.Host/Program.cs @@ -1,6 +1,178 @@ +using IntegrationGateway.Core.Abstractions; +using IntegrationGateway.Core.Infrastructure; +using IntegrationGateway.Core.Models; + var builder = WebApplication.CreateBuilder(args); + +// 注册 IHttpClientFactory +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 +}); + var app = builder.Build(); -app.MapGet("/", () => "Hello World!"); +// 读取配置 +var gwCfg = app.Configuration.GetSection("Gateway"); +var owlCfg = app.Configuration.GetSection("Owl"); +var mc4Cfg = app.Configuration.GetSection("MC4"); + +// 创建适配器注册中心 +var registry = new AdapterRegistry(); + +// 创建 VolPro 客户端工厂 +var volProUrl = gwCfg["VolProBaseUrl"] ?? "http://localhost:9100"; +var httpFactory = app.Services.GetRequiredService(); +var clientFactory = new GatewayClientFactory(httpFactory, volProUrl); + +// 适配器将在 Phase G2/G3 注册,此处预留 +Console.WriteLine("[Gateway] AdapterRegistry initialized, awaiting adapters..."); + +// ═══ B 组路由 ═══ + +// 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: 设备列表 +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 '{adapter}' not found or does not support flat devices" }); + return Results.Ok(await a.GetDevicesAsync(page, size, keyword)); +}); + +// B3: 对象树 +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 = $"Tree not supported by '{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 = $"Streams not supported by '{adapter}'" }); + var result = await a.GetLiveUrlAsync(deviceId); + return result.WsFlv == null && result.Hls == null + ? Results.Problem("No stream URL returned", statusCode: 502) + : Results.Ok(result); +}); + +// B6b: 回放 +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: PTZ +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: 实时点值 +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: 控制 +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)); +}); + +// 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 = "tree synced" }); + } + if (a is IHasFlatDevices flat) + { + var dev = await flat.GetDevicesAsync(1, 1000); + return Results.Ok(new { deviceCount = dev.Total, message = "devices synced" }); + } + return Results.Ok(new { message = "no sync needed" }); +}); app.Run(); + +// 请求 DTO +record PtzRequest(string? Direction, string Action, float Speed); +record ControlRequest(string? DeviceId, int PointIndex, double Value); diff --git a/gateway/src/IntegrationGateway.Host/appsettings.json b/gateway/src/IntegrationGateway.Host/appsettings.json index 10f68b8..7f9cbd8 100644 --- a/gateway/src/IntegrationGateway.Host/appsettings.json +++ b/gateway/src/IntegrationGateway.Host/appsettings.json @@ -5,5 +5,19 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" -} + "Owl": { + "BaseUrl": "http://localhost:15123", + "Username": "admin", + "Password": "your_owl_password" + }, + "MC4": { + "BaseUrl": "http://localhost:3000" + }, + "Gateway": { + "VolProBaseUrl": "http://localhost:9100", + "NodeCode": "gw-31ku", + "NodeToken": "changeme", + "HeartbeatIntervalSec": 15, + "AdapterInitTimeoutSec": 30 + } +} \ No newline at end of file