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