MC4整改M1-M4: 认证修复(login+MD5)+批量点位+历史告警+B4-batch优化+恢复Config/DTOS

This commit is contained in:
2026-06-04 00:02:11 +08:00
parent 5467f0c0e2
commit 85984d1e94
3 changed files with 152 additions and 31 deletions

View File

@@ -38,11 +38,11 @@ public class Mc4Adapter : IHasOwnDeviceTree, IHasPoints, IHasAlarms
/// <param name="adapterCode">适配器编码</param>
/// <param name="http">HttpClient 实例</param>
/// <param name="baseUrl">MC4.0 服务地址</param>
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);
}
/// <summary>初始化适配器:获取 MC4.0 Token</summary>
@@ -202,6 +202,65 @@ public class Mc4Adapter : IHasOwnDeviceTree, IHasPoints, IHasAlarms
{
1 => "未确认", 2 => "已确认", 3 => "已结束", _ => "未确认"
};
// ═══════════════════════════════════════════
// M2: 批量点位查询 — MC4.0 原生 multi/value/get
// ═══════════════════════════════════════════
/// <summary>批量获取多个设备的实时点位值</summary>
public async Task<Dictionary<int, List<Mc4PointValue>>> GetMultiRealtimeValuesAsync(List<int> 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<Dictionary<int, List<Mc4PointValue>>>(json)!;
}
// ═══════════════════════════════════════════
// M3: 历史告警查询 — MC4.0 his_alarm/query
// ═══════════════════════════════════════════
/// <summary>查询 MC4.0 历史告警(已恢复的告警)</summary>
public async Task<PagedResult<StandardAlarm>> 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<Mc4AlarmQueryResult>(json)!;
return new PagedResult<StandardAlarm>
{
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; }
}
/// <summary>MC4.0 历史告警查询请求</summary>
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;
}

View File

@@ -1,62 +1,92 @@
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
namespace IntegrationGateway.Adapters.MC4;
/// <summary>
/// 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 缓存 7hMC4.0 约 8h 有效期)
/// 5. 后续请求 header["token"] = token
/// </summary>
public class Mc4AuthHelper
{
private readonly HttpClient _http;
private readonly string _baseUrl;
/// <summary>缓存的认证 Token</summary>
private readonly string _account;
private readonly string _password;
private string? _token;
/// <summary>Token 过期时间UTC默认 8 小时</summary>
private DateTime _tokenExpiry = DateTime.MinValue;
private bool? _needMd5;
/// <summary>创建 MC4.0 认证辅助</summary>
/// <param name="http">HttpClient 实例</param>
/// <param name="baseUrl">MC4.0 服务地址</param>
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;
}
/// <summary>获取有效的 Token。缓存有效则直接返回否则重新获取。</summary>
public async Task<string> 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<Mc4ConfResponse>(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<Mc4AuthResponse>(json);
_token = result?.Token ?? "";
_tokenExpiry = DateTime.UtcNow.AddHours(8);
return _token!;
var result = JsonSerializer.Deserialize<Mc4LoginResponse>(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;
}
/// <summary>
/// 创建一个已认证的 HttpClient自动在 header["token"] 中附带 Token。
/// </summary>
public async Task<HttpClient> 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;
}
/// <summary>强制清除缓存 Token</summary>
public void Invalidate() => _token = null;
/// <summary>MC4.0 认证响应</summary>
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; } }
}

View File

@@ -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<IHasPoints>(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<string, List<PointValue>>();
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<string>? DeviceIds);
record GatewayControlRequest(string? DeviceId, string? Command, Dictionary<string, object?>? Parameters);
record SyncRequest(string? DataType, List<object>? Items);
record SyncDeleteRequest(string? DataType, List<string>? Ids);
// ═══════════════════════════════════════════
// 配置 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; } = ""; public string Username { get; set; } = "admin"; public string Password { get; set; } = "admin"; }
/// <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; } = ""; }