Files
SecMPS/gateway/src/IntegrationGateway.Adapters.Kms/KmsAdapter.cs

338 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using IntegrationGateway.Core.Abstractions;
using IntegrationGateway.Core.Infrastructure;
using IntegrationGateway.Core.Models;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
namespace IntegrationGateway.Adapters.Kms;
/// <summary>
/// KMS 智能钥匙柜适配器。
/// 实现: IHasFlatDevices + IHasAlarms。
///
/// 设备模型:柜体为父设备(IsParent=是),锁孔为子设备(ParentSourceId=柜体SourceId)。
/// AdapterCode: "KMS:{InstanceName}"。
/// 限流5 QPS。
///
/// 按设计文档 §6 KmsAdapter 完整实现。
/// </summary>
public class KmsAdapter : IHasFlatDevices, IHasAlarms, IAcceptsControl, IHasBusinessLogs, IAcceptsDataSync
{
private readonly HttpClient _http;
private readonly KmsAuthHelper _auth;
private readonly RateLimiter _limiter = new(5);
/// <summary>适配器编码,格式 "KMS:{实例名}"</summary>
public string AdapterCode { get; }
/// <summary>人类可读的适配器名称</summary>
public string DisplayName => $"KMS ({AdapterCode})";
/// <summary>适配器能力声明</summary>
public AdapterCapabilities Capabilities => new() { HasFlatDevices = true, HasAlarms = true };
/// <summary>创建 KmsAdapter 实例</summary>
/// <param name="adapterCode">适配器编码</param>
/// <param name="http">HttpClient 实例</param>
/// <param name="baseUrl">KMS 服务地址</param>
/// <param name="clientId">KMS 客户端 ID</param>
/// <param name="clientSecret">KMS 客户端密钥</param>
public KmsAdapter(string adapterCode, HttpClient http, string baseUrl, string clientId, string clientSecret)
{
AdapterCode = adapterCode;
_http = http;
_auth = new KmsAuthHelper(http, baseUrl, clientId, clientSecret);
}
/// <summary>初始化适配器:获取 KMS Token</summary>
public async Task InitializeAsync() => await _auth.GetTokenAsync();
// ═══════════════════════════════════════════
// IGatewayAdapter — 健康检查2.18.1 心跳)
// ═══════════════════════════════════════════
/// <summary>调用 KMS 心跳接口确认可达性</summary>
public async Task<bool> HealthCheckAsync()
{
try
{
var client = await _auth.GetAuthenticatedClientAsync();
var resp = await client.GetAsync("/prod-api/heartBeat");
return resp.IsSuccessStatusCode;
}
catch { return false; }
}
// ═══════════════════════════════════════════
// IHasFlatDevices — 设备列表2.18.4 柜体+钥匙)
// ═══════════════════════════════════════════
/// <summary>
/// 获取 KMS 所有柜体及其锁孔,映射为 StandardDevice 列表。
/// 柜体为父设备IsParent=是锁孔为子设备ParentSourceId=柜体SourceId
/// </summary>
public async Task<PagedResult<StandardDevice>> GetDevicesAsync(int page, int size, string? keyword = null)
{
await _limiter.WaitAsync();
var client = await _auth.GetAuthenticatedClientAsync();
var resp = await client.PostAsync("/prod-api/getOpenerList",
new StringContent("{}", Encoding.UTF8, "application/json"));
resp.EnsureSuccessStatusCode();
var data = await resp.Content.ReadFromJsonAsync<KmsOpenerListResponse>()!;
var devices = new List<StandardDevice>();
foreach (var locker in data.Rows ?? new())
{
devices.Add(MapLockerToDevice(locker));
if (locker.LockholeList != null)
devices.AddRange(locker.LockholeList.Select(h => MapLockholeToDevice(h, locker.LockerId)));
}
return new PagedResult<StandardDevice> { Items = devices, Total = devices.Count };
}
/// <summary>KMS 柜体 → StandardDevice父设备</summary>
private static StandardDevice MapLockerToDevice(KmsLocker locker) => new()
{
SourceId = $"locker_{locker.LockerId}",
Name = locker.LockerName ?? $"柜体{locker.LockerId}",
Category = "智能钥匙柜",
Group = "门禁设备",
IsParent = true,
IsOnline = true,
Extra = new Dictionary<string, object?>
{
["lockerCode"] = locker.LockerCode,
["lockholeCount"] = locker.LockholeList?.Count ?? 0
}
};
/// <summary>KMS 锁孔 → StandardDevice子设备</summary>
private static StandardDevice MapLockholeToDevice(KmsLockhole hole, int lockerId) => new()
{
SourceId = $"lockhole_{lockerId}_{hole.LockholeSort}",
Name = hole.OpenerName ?? $"锁孔{hole.LockholeSort}",
Category = "钥匙位",
Group = "门禁设备",
IsParent = false,
IsOnline = hole.OpenerState == "在位",
ParentSourceId = $"locker_{lockerId}",
Extra = new Dictionary<string, object?>
{
["openerId"] = hole.OpenerId,
["openerType"] = hole.OpenerType,
["openerState"] = hole.OpenerState
}
};
// ═══════════════════════════════════════════
// IHasAlarms — 告警2.18.7 告警列表)
// ═══════════════════════════════════════════
/// <summary>分页查询 KMS 告警列表,映射到 StandardAlarm</summary>
public async Task<PagedResult<StandardAlarm>> GetAlarmsAsync(
int page, int size, DateTime from, DateTime to, string? level = null, string? state = null)
{
await _limiter.WaitAsync();
var client = await _auth.GetAuthenticatedClientAsync();
var resp = await client.PostAsync("/prod-api/getWarningList",
new StringContent("{}", Encoding.UTF8, "application/json"));
resp.EnsureSuccessStatusCode();
var data = await resp.Content.ReadFromJsonAsync<KmsWarningListResponse>()!;
var alarms = (data.Rows ?? new()).Select(w => new StandardAlarm
{
AlarmId = w.Uuid ?? "",
AdapterCode = AdapterCode,
Level = "普通", // KMS 不区分告警等级,统一"普通"
Title = $"{w.LockerName} 锁孔{w.LockholeSort}: {w.OpenerName}",
Content = w.Remark,
OccurTime = DateTime.TryParse(w.WarningTime, out var t) ? t : DateTime.MinValue,
Status = w.Type == 1 ? "未确认" : "已结束"
}).ToList();
return new PagedResult<StandardAlarm> { Items = alarms, Total = data.Total };
}
/// <summary>确认告警(调 KMS 标准告警确认接口)</summary>
public async Task ConfirmAlarmAsync(string alarmId)
{
await _limiter.WaitAsync();
var client = await _auth.GetAuthenticatedClientAsync();
await client.PostAsync($"/prod-api/kms/warning/confirm/{alarmId}", null);
}
/// <summary>结束告警KMS 第三方接口不提供,留空实现)</summary>
public Task EndAlarmAsync(string alarmId)
{
// KMS 第三方接口 (2.18.7) 不提供告警结束 API
return Task.CompletedTask;
}
// ═══════════════════════════════════════════
// 扩展方法 — 2.18 第三方接口全覆盖
// ═══════════════════════════════════════════
/// <summary>2.18.6 查询借还记录列表</summary>
public async Task<PagedResult<KmsRecord>> GetBorrowRecordsAsync(DateTime? from = null, DateTime? to = null)
{
await _limiter.WaitAsync();
var client = await _auth.GetAuthenticatedClientAsync();
var body = "{}"; // 联调时加入时间范围参数
var resp = await client.PostAsync("/prod-api/getRecordList",
new StringContent(body, Encoding.UTF8, "application/json"));
resp.EnsureSuccessStatusCode();
var data = await resp.Content.ReadFromJsonAsync<KmsRecordListResponse>()!;
return new PagedResult<KmsRecord> { Items = data.Rows ?? new(), Total = data.Total };
}
/// <summary>2.18.5 查询授权记录列表</summary>
public async Task<PagedResult<KmsPermission>> GetPermissionListAsync(DateTime? from = null, DateTime? to = null)
{
await _limiter.WaitAsync();
var client = await _auth.GetAuthenticatedClientAsync();
var body = "{}"; // 联调时加入时间范围
var resp = await client.PostAsync("/prod-api/getPermissionList",
new StringContent(body, Encoding.UTF8, "application/json"));
resp.EnsureSuccessStatusCode();
var data = await resp.Content.ReadFromJsonAsync<KmsPermissionListResponse>()!;
return new PagedResult<KmsPermission> { Items = data.Rows ?? new(), Total = data.Total };
}
/// <summary>2.18.3 从 Vol.Pro 向 KMS 批量同步员工</summary>
public async Task BatchSyncStaffAsync(List<KmsStaff> staffList)
{
await _limiter.WaitAsync();
var client = await _auth.GetAuthenticatedClientAsync();
var resp = await client.PostAsJsonAsync("/prod-api/batchSyncStaff", new { staff = staffList });
resp.EnsureSuccessStatusCode();
}
/// <summary>2.18.2 从 Vol.Pro 向 KMS 批量删除员工</summary>
public async Task BatchDeleteStaffAsync(List<string> staffUuids)
{
await _limiter.WaitAsync();
var client = await _auth.GetAuthenticatedClientAsync();
var resp = await client.PostAsJsonAsync("/prod-api/batchDeleteStaff", staffUuids);
resp.EnsureSuccessStatusCode();
}
/// <summary>2.4.3 远程授权开门</summary>
public async Task RemoteAuthorizeAsync(KmsRemotePermissionRequest request)
{
await _limiter.WaitAsync();
var client = await _auth.GetAuthenticatedClientAsync();
var resp = await client.PostAsJsonAsync("/prod-api/kms/permission/remote", request);
resp.EnsureSuccessStatusCode();
}
/// <summary>2.18.8 代理 KMS 第三方登录/事件记录</summary>
public async Task<string?> ThirdPlatLoginAsync(string username)
{
await _limiter.WaitAsync();
var client = await _auth.GetAuthenticatedClientAsync();
var resp = await client.PostAsync($"/thirdPlatlogin?username={Uri.EscapeDataString(username)}", null);
if (resp.StatusCode == System.Net.HttpStatusCode.Redirect)
return resp.Headers.Location?.ToString();
resp.EnsureSuccessStatusCode();
return await resp.Content.ReadAsStringAsync();
}
// ═══════════════════════════════════════════
// IAcceptsControl — 设备控制(远程开门)
// ═══════════════════════════════════════════
/// <summary>向设备下发控制指令(如远程开门)</summary>
public async Task<ControlResult> SendControlAsync(string sourceDeviceId, string command, Dictionary<string, object?> parameters)
{
await _limiter.WaitAsync();
try
{
if (command == "open" || command == "authorize")
{
var req = new KmsRemotePermissionRequest
{
StaffIds = parameters.TryGetValue("staffIds", out var s) && s is List<int> sl ? sl : null,
OpenerIds = parameters.TryGetValue("lockholeSort", out var lh) ? new List<int> { (int)(long)lh! } : null,
Type = command == "authorize" ? 2 : 1
};
await RemoteAuthorizeAsync(req);
}
return new ControlResult { Success = true };
}
catch (Exception ex)
{
return new ControlResult { Success = false, Message = ex.Message };
}
}
// ═══════════════════════════════════════════
// IHasBusinessLogs — 业务记录查询
// ═══════════════════════════════════════════
/// <summary>按类型查询业务记录</summary>
public async Task<PagedResult<BusinessLogEntry>> GetBusinessLogsAsync(
string logType, DateTime? from, DateTime? to, int page, int size, Dictionary<string, string>? filters = null)
{
if (logType == "borrow" || logType == "handover")
{
var records = await GetBorrowRecordsAsync(from, to);
return new PagedResult<BusinessLogEntry>
{
Items = records.Items.Select(r => new BusinessLogEntry
{
LogId = r.Uuid ?? "", LogType = logType,
DeviceSourceId = $"lockhole_{r.LockerName}_{r.LockholeSort}",
StaffName = r.StaffName, Description = r.OpenerName,
CreatedAt = DateTime.TryParse(r.BorrowTime, out var bt) ? bt : null
}).ToList(),
Total = records.Total
};
}
if (logType == "permission")
{
var perms = await GetPermissionListAsync(from, to);
return new PagedResult<BusinessLogEntry>
{
Items = perms.Items.Select(p => new BusinessLogEntry
{
LogId = p.Uuid ?? "", LogType = "permission",
StaffName = p.LendStaffName, Description = p.OpenerCnName,
CreatedAt = DateTime.TryParse(p.ApplyTime, out var at) ? at : null
}).ToList(),
Total = perms.Total
};
}
return new PagedResult<BusinessLogEntry> { Items = new(), Total = 0 };
}
// ═══════════════════════════════════════════
// IAcceptsDataSync — 数据同步写入
// ═══════════════════════════════════════════
/// <summary>向 KMS 批量同步数据</summary>
public async Task<SyncResult> SyncDataAsync(string dataType, List<object> items)
{
if (dataType != "staff") return new SyncResult { SuccessCount = 0, FailCount = 0, Message = $"不支持的数据类型: {dataType}" };
try
{
var staffList = items.Cast<KmsStaff>().ToList();
await BatchSyncStaffAsync(staffList);
return new SyncResult { SuccessCount = staffList.Count };
}
catch (Exception ex) { return new SyncResult { FailCount = items.Count, Message = ex.Message }; }
}
/// <summary>从 KMS 批量删除数据</summary>
public async Task<SyncResult> DeleteDataAsync(string dataType, List<string> ids)
{
if (dataType != "staff") return new SyncResult { SuccessCount = 0, FailCount = 0, Message = $"不支持的数据类型: {dataType}" };
try
{
await BatchDeleteStaffAsync(ids);
return new SyncResult { SuccessCount = ids.Count };
}
catch (Exception ex) { return new SyncResult { FailCount = ids.Count, Message = ex.Message }; }
}
}