From 85984d1e94d1e6dd77452c982af65c5a101738f7 Mon Sep 17 00:00:00 2001 From: g82tt Date: Thu, 4 Jun 2026 00:02:11 +0800 Subject: [PATCH] =?UTF-8?q?MC4=E6=95=B4=E6=94=B9M1-M4:=20=E8=AE=A4?= =?UTF-8?q?=E8=AF=81=E4=BF=AE=E5=A4=8D(login+MD5)+=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E7=82=B9=E4=BD=8D+=E5=8E=86=E5=8F=B2=E5=91=8A=E8=AD=A6+B4-batc?= =?UTF-8?q?h=E4=BC=98=E5=8C=96+=E6=81=A2=E5=A4=8DConfig/DTOS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Mc4Adapter.cs | 73 +++++++++++++++- .../Mc4AuthHelper.cs | 86 +++++++++++++------ .../src/IntegrationGateway.Host/Program.cs | 24 +++++- 3 files changed, 152 insertions(+), 31 deletions(-) diff --git a/gateway/src/IntegrationGateway.Adapters.MC4/Mc4Adapter.cs b/gateway/src/IntegrationGateway.Adapters.MC4/Mc4Adapter.cs index 2caff4c..2eedb64 100644 --- a/gateway/src/IntegrationGateway.Adapters.MC4/Mc4Adapter.cs +++ b/gateway/src/IntegrationGateway.Adapters.MC4/Mc4Adapter.cs @@ -38,11 +38,11 @@ public class Mc4Adapter : IHasOwnDeviceTree, IHasPoints, IHasAlarms /// 适配器编码 /// HttpClient 实例 /// MC4.0 服务地址 - public Mc4Adapter(string adapterCode, HttpClient http, string baseUrl) + public Mc4Adapter(string adapterCode, HttpClient http, string baseUrl, string account = "admin", string password = "admin") { AdapterCode = adapterCode; _http = http; - _auth = new Mc4AuthHelper(http, baseUrl); + _auth = new Mc4AuthHelper(http, baseUrl, account, password); } /// 初始化适配器:获取 MC4.0 Token @@ -202,6 +202,65 @@ public class Mc4Adapter : IHasOwnDeviceTree, IHasPoints, IHasAlarms { 1 => "未确认", 2 => "已确认", 3 => "已结束", _ => "未确认" }; + + // ═══════════════════════════════════════════ + // M2: 批量点位查询 — MC4.0 原生 multi/value/get + // ═══════════════════════════════════════════ + + /// 批量获取多个设备的实时点位值 + public async Task>> GetMultiRealtimeValuesAsync(List deviceIds) + { + await _limiter.WaitAsync(); + var client = await _auth.GetAuthenticatedClientAsync(); + var body = JsonSerializer.Serialize(new { ids = deviceIds }); + var resp = await client.PostAsync("/api/central/point/multi/value/get", + new StringContent(body, Encoding.UTF8, "application/json")); + resp.EnsureSuccessStatusCode(); + var json = await resp.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize>>(json)!; + } + + // ═══════════════════════════════════════════ + // M3: 历史告警查询 — MC4.0 his_alarm/query + // ═══════════════════════════════════════════ + + /// 查询 MC4.0 历史告警(已恢复的告警) + public async Task> GetHisAlarmsAsync(int page, int size, DateTime from, DateTime to) + { + await _limiter.WaitAsync(); + var client = await _auth.GetAuthenticatedClientAsync(); + var body = JsonSerializer.Serialize(new Mc4HisAlarmQuery + { + From = from.ToString("yyyy-MM-dd HH:mm:ss"), + To = to.ToString("yyyy-MM-dd HH:mm:ss"), + Skip = (page - 1) * size, + Limit = size, + Sort = 1 + }); + var resp = await client.PostAsync("/api/central/his_alarm/query", + new StringContent(body, Encoding.UTF8, "application/json")); + resp.EnsureSuccessStatusCode(); + var json = await resp.Content.ReadAsStringAsync(); + var result = JsonSerializer.Deserialize(json)!; + return new PagedResult + { + Items = (result.List ?? new()).Select(MapAlarmItem).ToList(), + Total = result.Total + }; + } + + private StandardAlarm MapAlarmItem(Mc4AlarmItem a) => new() + { + AlarmId = a.Id ?? "", + AdapterCode = AdapterCode, + DeviceId = a.Sid?.ToString(), + Level = MapAlarmLevel(a.Level), + Title = a.Desc ?? "", + OccurTime = DateTime.TryParse(a.Stime, out var st) ? st : DateTime.MinValue, + Status = MapAlarmState(a.State), + ActualValue = a.Soption?.Value, + ThresholdValue = a.Eoption?.Value + }; } // ═══════════════════════════════════════════ @@ -279,3 +338,13 @@ public class Mc4Option public double? Value { get; set; } public string? TypeName { get; set; } } + +/// MC4.0 历史告警查询请求 +public class Mc4HisAlarmQuery +{ + public string From { get; set; } = ""; + public string To { get; set; } = ""; + public int Skip { get; set; } + public int Limit { get; set; } + public int Sort { get; set; } = 1; +} diff --git a/gateway/src/IntegrationGateway.Adapters.MC4/Mc4AuthHelper.cs b/gateway/src/IntegrationGateway.Adapters.MC4/Mc4AuthHelper.cs index 51a650e..c527e2a 100644 --- a/gateway/src/IntegrationGateway.Adapters.MC4/Mc4AuthHelper.cs +++ b/gateway/src/IntegrationGateway.Adapters.MC4/Mc4AuthHelper.cs @@ -1,62 +1,92 @@ +using System.Security.Cryptography; +using System.Text; using System.Text.Json; namespace IntegrationGateway.Adapters.MC4; /// -/// MC4.0 子系统的 Token 认证辅助类。 +/// MC4.0 Token 认证辅助类。 /// -/// 认证流程: -/// 1. POST /api/central/auth/conf/get 获取临时 Token -/// 2. Token 有效期约 8 小时,缓存在内存中 -/// 3. 后续请求在 header["token"] 中携带 Token -/// -/// 注意:MC4.0 使用自定义 header "token" 而非标准 Authorization 头。 +/// 认证流程: +/// 1. POST /api/central/auth/conf/get → { "encrypt": true/false } +/// 2. 若 encrypt=true → 密码用 MD5 加密 +/// 3. POST /api/central/auth/login { account, password } → { token, id, account, name } +/// 4. Token 缓存 7h(MC4.0 约 8h 有效期) +/// 5. 后续请求 header["token"] = token /// public class Mc4AuthHelper { private readonly HttpClient _http; private readonly string _baseUrl; - /// 缓存的认证 Token + private readonly string _account; + private readonly string _password; private string? _token; - /// Token 过期时间(UTC),默认 8 小时 private DateTime _tokenExpiry = DateTime.MinValue; + private bool? _needMd5; - /// 创建 MC4.0 认证辅助 - /// HttpClient 实例 - /// MC4.0 服务地址 - public Mc4AuthHelper(HttpClient http, string baseUrl) + public Mc4AuthHelper(HttpClient http, string baseUrl, string account = "admin", string password = "admin") { - _http = http; _baseUrl = baseUrl.TrimEnd('/'); + _http = http; + _baseUrl = baseUrl.TrimEnd('/'); + _account = account; + _password = password; } - /// 获取有效的 Token。缓存有效则直接返回,否则重新获取。 public async Task GetTokenAsync() { - if (!string.IsNullOrEmpty(_token) && DateTime.UtcNow < _tokenExpiry) return _token; + if (!string.IsNullOrEmpty(_token) && DateTime.UtcNow < _tokenExpiry) + return _token; - var resp = await _http.PostAsync($"{_baseUrl}/api/central/auth/conf/get", null); + // 1. 获取加密配置 + if (!_needMd5.HasValue) + { + try + { + var confResp = await _http.PostAsync($"{_baseUrl}/api/central/auth/conf/get", null); + if (confResp.IsSuccessStatusCode) + { + var confJson = await confResp.Content.ReadAsStringAsync(); + var conf = JsonSerializer.Deserialize(confJson); + _needMd5 = conf?.Encrypt ?? false; + } + else { _needMd5 = false; } + } + catch { _needMd5 = false; } + } + + // 2. 登录获取 Token + var pwd = _needMd5 == true ? ComputeMd5(_password) : _password; + var loginBody = JsonSerializer.Serialize(new { account = _account, password = pwd }); + var resp = await _http.PostAsync($"{_baseUrl}/api/central/auth/login", + new StringContent(loginBody, Encoding.UTF8, "application/json")); resp.EnsureSuccessStatusCode(); var json = await resp.Content.ReadAsStringAsync(); - var result = JsonSerializer.Deserialize(json); - _token = result?.Token ?? ""; - _tokenExpiry = DateTime.UtcNow.AddHours(8); - return _token!; + var result = JsonSerializer.Deserialize(json) + ?? throw new Exception("MC4 登录响应为空"); + if (string.IsNullOrEmpty(result.Token)) + throw new Exception("MC4 登录失败: Token 为空"); + _token = result.Token; + _tokenExpiry = DateTime.UtcNow.AddHours(7); + return _token; } - /// - /// 创建一个已认证的 HttpClient,自动在 header["token"] 中附带 Token。 - /// public async Task GetAuthenticatedClientAsync() { var token = await GetTokenAsync(); var client = new HttpClient { BaseAddress = new Uri(_baseUrl) }; - client.DefaultRequestHeaders.Add("token", token); + if (!string.IsNullOrEmpty(token)) + client.DefaultRequestHeaders.Add("token", token); return client; } - /// 强制清除缓存 Token public void Invalidate() => _token = null; - /// MC4.0 认证响应 - public class Mc4AuthResponse { public string? Token { get; set; } } + private static string ComputeMd5(string input) + { + var bytes = MD5.HashData(Encoding.UTF8.GetBytes(input)); + return Convert.ToHexString(bytes).ToLowerInvariant(); + } + + private class Mc4ConfResponse { public bool Encrypt { get; set; } } + private class Mc4LoginResponse { public string? Token { get; set; } public int Id { get; set; } public string? Account { get; set; } } } diff --git a/gateway/src/IntegrationGateway.Host/Program.cs b/gateway/src/IntegrationGateway.Host/Program.cs index 72a55f1..689a438 100644 --- a/gateway/src/IntegrationGateway.Host/Program.cs +++ b/gateway/src/IntegrationGateway.Host/Program.cs @@ -261,11 +261,19 @@ app.MapGet("/api/gateway/realtime/{adapter}/{deviceId}", async (string adapter, return Results.Ok(await a.GetRealtimeValuesAsync(deviceId)); }); -// B4-batch: 批量实时点位值 — 一次请求返回多个设备的值 +// B4-batch: 批量实时点位值 — MC4 原生批量接口,其他适配器 fallback app.MapPost("/api/gateway/realtime/{adapter}/batch", async (string adapter, BatchRealtimeRequest req) => { var a = registry.FindByCode(adapter); if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED" }); + + if (a is IntegrationGateway.Adapters.MC4.Mc4Adapter mc4 && req.DeviceIds?.Count > 0) + { + var intIds = req.DeviceIds.Select(int.Parse).ToList(); + var multi = await mc4.GetMultiRealtimeValuesAsync(intIds); + return Results.Ok(multi); + } + var results = new Dictionary>(); foreach (var deviceId in req.DeviceIds ?? new()) try { results[deviceId] = await a.GetRealtimeValuesAsync(deviceId); } catch { } @@ -368,8 +376,22 @@ app.MapPost("/api/gateway/devices/sync", async (string adapter) => }); app.Run(); +record PtzRequest(string? Direction, string Action, float Speed); record ControlRequest(string? DeviceId, int PointIndex, double Value); record BatchRealtimeRequest(List? DeviceIds); record GatewayControlRequest(string? DeviceId, string? Command, Dictionary? Parameters); record SyncRequest(string? DataType, List? Items); record SyncDeleteRequest(string? DataType, List? Ids); + +// ═══════════════════════════════════════════ +// 配置 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; } = ""; public string Username { get; set; } = "admin"; public string Password { get; set; } = "admin"; } + +/// KMS 适配器配置项 +public class KmsConfig { public string? InstanceName { get; set; } public string BaseUrl { get; set; } = ""; public string ClientId { get; set; } = ""; public string ClientSecret { get; set; } = ""; }