MC4整改M1-M4: 认证修复(login+MD5)+批量点位+历史告警+B4-batch优化+恢复Config/DTOS
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 缓存 7h(MC4.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);
|
||||
resp.EnsureSuccessStatusCode();
|
||||
var json = await resp.Content.ReadAsStringAsync();
|
||||
var result = JsonSerializer.Deserialize<Mc4AuthResponse>(json);
|
||||
_token = result?.Token ?? "";
|
||||
_tokenExpiry = DateTime.UtcNow.AddHours(8);
|
||||
return _token!;
|
||||
// 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<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) };
|
||||
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; } }
|
||||
}
|
||||
|
||||
@@ -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; } = ""; }
|
||||
|
||||
Reference in New Issue
Block a user