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="adapterCode">适配器编码</param>
|
||||||
/// <param name="http">HttpClient 实例</param>
|
/// <param name="http">HttpClient 实例</param>
|
||||||
/// <param name="baseUrl">MC4.0 服务地址</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;
|
AdapterCode = adapterCode;
|
||||||
_http = http;
|
_http = http;
|
||||||
_auth = new Mc4AuthHelper(http, baseUrl);
|
_auth = new Mc4AuthHelper(http, baseUrl, account, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>初始化适配器:获取 MC4.0 Token</summary>
|
/// <summary>初始化适配器:获取 MC4.0 Token</summary>
|
||||||
@@ -202,6 +202,65 @@ public class Mc4Adapter : IHasOwnDeviceTree, IHasPoints, IHasAlarms
|
|||||||
{
|
{
|
||||||
1 => "未确认", 2 => "已确认", 3 => "已结束", _ => "未确认"
|
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 double? Value { get; set; }
|
||||||
public string? TypeName { 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;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace IntegrationGateway.Adapters.MC4;
|
namespace IntegrationGateway.Adapters.MC4;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// MC4.0 子系统的 Token 认证辅助类。
|
/// MC4.0 Token 认证辅助类。
|
||||||
///
|
///
|
||||||
/// 认证流程:
|
/// 认证流程:
|
||||||
/// 1. POST /api/central/auth/conf/get 获取临时 Token
|
/// 1. POST /api/central/auth/conf/get → { "encrypt": true/false }
|
||||||
/// 2. Token 有效期约 8 小时,缓存在内存中
|
/// 2. 若 encrypt=true → 密码用 MD5 加密
|
||||||
/// 3. 后续请求在 header["token"] 中携带 Token
|
/// 3. POST /api/central/auth/login { account, password } → { token, id, account, name }
|
||||||
///
|
/// 4. Token 缓存 7h(MC4.0 约 8h 有效期)
|
||||||
/// 注意:MC4.0 使用自定义 header "token" 而非标准 Authorization 头。
|
/// 5. 后续请求 header["token"] = token
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Mc4AuthHelper
|
public class Mc4AuthHelper
|
||||||
{
|
{
|
||||||
private readonly HttpClient _http;
|
private readonly HttpClient _http;
|
||||||
private readonly string _baseUrl;
|
private readonly string _baseUrl;
|
||||||
/// <summary>缓存的认证 Token</summary>
|
private readonly string _account;
|
||||||
|
private readonly string _password;
|
||||||
private string? _token;
|
private string? _token;
|
||||||
/// <summary>Token 过期时间(UTC),默认 8 小时</summary>
|
|
||||||
private DateTime _tokenExpiry = DateTime.MinValue;
|
private DateTime _tokenExpiry = DateTime.MinValue;
|
||||||
|
private bool? _needMd5;
|
||||||
|
|
||||||
/// <summary>创建 MC4.0 认证辅助</summary>
|
public Mc4AuthHelper(HttpClient http, string baseUrl, string account = "admin", string password = "admin")
|
||||||
/// <param name="http">HttpClient 实例</param>
|
|
||||||
/// <param name="baseUrl">MC4.0 服务地址</param>
|
|
||||||
public Mc4AuthHelper(HttpClient http, string baseUrl)
|
|
||||||
{
|
{
|
||||||
_http = http; _baseUrl = baseUrl.TrimEnd('/');
|
_http = http;
|
||||||
|
_baseUrl = baseUrl.TrimEnd('/');
|
||||||
|
_account = account;
|
||||||
|
_password = password;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>获取有效的 Token。缓存有效则直接返回,否则重新获取。</summary>
|
|
||||||
public async Task<string> GetTokenAsync()
|
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();
|
resp.EnsureSuccessStatusCode();
|
||||||
var json = await resp.Content.ReadAsStringAsync();
|
var json = await resp.Content.ReadAsStringAsync();
|
||||||
var result = JsonSerializer.Deserialize<Mc4AuthResponse>(json);
|
var result = JsonSerializer.Deserialize<Mc4LoginResponse>(json)
|
||||||
_token = result?.Token ?? "";
|
?? throw new Exception("MC4 登录响应为空");
|
||||||
_tokenExpiry = DateTime.UtcNow.AddHours(8);
|
if (string.IsNullOrEmpty(result.Token))
|
||||||
return _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()
|
public async Task<HttpClient> GetAuthenticatedClientAsync()
|
||||||
{
|
{
|
||||||
var token = await GetTokenAsync();
|
var token = await GetTokenAsync();
|
||||||
var client = new HttpClient { BaseAddress = new Uri(_baseUrl) };
|
var client = new HttpClient { BaseAddress = new Uri(_baseUrl) };
|
||||||
client.DefaultRequestHeaders.Add("token", token);
|
if (!string.IsNullOrEmpty(token))
|
||||||
|
client.DefaultRequestHeaders.Add("token", token);
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>强制清除缓存 Token</summary>
|
|
||||||
public void Invalidate() => _token = null;
|
public void Invalidate() => _token = null;
|
||||||
|
|
||||||
/// <summary>MC4.0 认证响应</summary>
|
private static string ComputeMd5(string input)
|
||||||
public class Mc4AuthResponse { public string? Token { get; set; } }
|
{
|
||||||
|
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));
|
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) =>
|
app.MapPost("/api/gateway/realtime/{adapter}/batch", async (string adapter, BatchRealtimeRequest req) =>
|
||||||
{
|
{
|
||||||
var a = registry.FindByCode<IHasPoints>(adapter);
|
var a = registry.FindByCode<IHasPoints>(adapter);
|
||||||
if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED" });
|
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>>();
|
var results = new Dictionary<string, List<PointValue>>();
|
||||||
foreach (var deviceId in req.DeviceIds ?? new())
|
foreach (var deviceId in req.DeviceIds ?? new())
|
||||||
try { results[deviceId] = await a.GetRealtimeValuesAsync(deviceId); } catch { }
|
try { results[deviceId] = await a.GetRealtimeValuesAsync(deviceId); } catch { }
|
||||||
@@ -368,8 +376,22 @@ app.MapPost("/api/gateway/devices/sync", async (string adapter) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
record PtzRequest(string? Direction, string Action, float Speed);
|
||||||
record ControlRequest(string? DeviceId, int PointIndex, double Value);
|
record ControlRequest(string? DeviceId, int PointIndex, double Value);
|
||||||
record BatchRealtimeRequest(List<string>? DeviceIds);
|
record BatchRealtimeRequest(List<string>? DeviceIds);
|
||||||
record GatewayControlRequest(string? DeviceId, string? Command, Dictionary<string, object?>? Parameters);
|
record GatewayControlRequest(string? DeviceId, string? Command, Dictionary<string, object?>? Parameters);
|
||||||
record SyncRequest(string? DataType, List<object>? Items);
|
record SyncRequest(string? DataType, List<object>? Items);
|
||||||
record SyncDeleteRequest(string? DataType, List<string>? Ids);
|
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