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; /// /// KMS 智能钥匙柜适配器。 /// 实现: IHasFlatDevices + IHasAlarms。 /// /// 设备模型:柜体为父设备(IsParent=是),锁孔为子设备(ParentSourceId=柜体SourceId)。 /// AdapterCode: "KMS:{InstanceName}"。 /// 限流:5 QPS。 /// /// 按设计文档 §6 KmsAdapter 完整实现。 /// public class KmsAdapter : IHasFlatDevices, IHasAlarms, IAcceptsControl, IHasBusinessLogs, IAcceptsDataSync { private readonly HttpClient _http; private readonly KmsAuthHelper _auth; private readonly RateLimiter _limiter = new(5); /// 适配器编码,格式 "KMS:{实例名}" public string AdapterCode { get; } /// 人类可读的适配器名称 public string DisplayName => $"KMS ({AdapterCode})"; /// 适配器能力声明 public AdapterCapabilities Capabilities => new() { HasFlatDevices = true, HasAlarms = true }; /// 创建 KmsAdapter 实例 /// 适配器编码 /// HttpClient 实例 /// KMS 服务地址 /// KMS 客户端 ID /// KMS 客户端密钥 public KmsAdapter(string adapterCode, HttpClient http, string baseUrl, string clientId, string clientSecret) { AdapterCode = adapterCode; _http = http; _auth = new KmsAuthHelper(http, baseUrl, clientId, clientSecret); } /// 初始化适配器:获取 KMS Token public async Task InitializeAsync() => await _auth.GetTokenAsync(); // ═══════════════════════════════════════════ // IGatewayAdapter — 健康检查(2.18.1 心跳) // ═══════════════════════════════════════════ /// 调用 KMS 心跳接口确认可达性 public async Task 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 柜体+钥匙) // ═══════════════════════════════════════════ /// /// 获取 KMS 所有柜体及其锁孔,映射为 StandardDevice 列表。 /// 柜体为父设备(IsParent=是),锁孔为子设备(ParentSourceId=柜体SourceId)。 /// public async Task> 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()!; var devices = new List(); 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 { Items = devices, Total = devices.Count }; } /// KMS 柜体 → StandardDevice(父设备) 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 { ["lockerCode"] = locker.LockerCode, ["lockholeCount"] = locker.LockholeList?.Count ?? 0 } }; /// KMS 锁孔 → StandardDevice(子设备) 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 { ["openerId"] = hole.OpenerId, ["openerType"] = hole.OpenerType, ["openerState"] = hole.OpenerState } }; // ═══════════════════════════════════════════ // IHasAlarms — 告警(2.18.7 告警列表) // ═══════════════════════════════════════════ /// 分页查询 KMS 告警列表,映射到 StandardAlarm public async Task> 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()!; 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 { Items = alarms, Total = data.Total }; } /// 确认告警(调 KMS 标准告警确认接口) public async Task ConfirmAlarmAsync(string alarmId) { await _limiter.WaitAsync(); var client = await _auth.GetAuthenticatedClientAsync(); await client.PostAsync($"/prod-api/kms/warning/confirm/{alarmId}", null); } /// 结束告警(KMS 第三方接口不提供,留空实现) public Task EndAlarmAsync(string alarmId) { // KMS 第三方接口 (2.18.7) 不提供告警结束 API return Task.CompletedTask; } // ═══════════════════════════════════════════ // 扩展方法 — 2.18 第三方接口全覆盖 // ═══════════════════════════════════════════ /// 2.18.6 查询借还记录列表 public async Task> 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()!; return new PagedResult { Items = data.Rows ?? new(), Total = data.Total }; } /// 2.18.5 查询授权记录列表 public async Task> 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()!; return new PagedResult { Items = data.Rows ?? new(), Total = data.Total }; } /// 2.18.3 从 Vol.Pro 向 KMS 批量同步员工 public async Task BatchSyncStaffAsync(List staffList) { await _limiter.WaitAsync(); var client = await _auth.GetAuthenticatedClientAsync(); var resp = await client.PostAsJsonAsync("/prod-api/batchSyncStaff", new { staff = staffList }); resp.EnsureSuccessStatusCode(); } /// 2.18.2 从 Vol.Pro 向 KMS 批量删除员工 public async Task BatchDeleteStaffAsync(List staffUuids) { await _limiter.WaitAsync(); var client = await _auth.GetAuthenticatedClientAsync(); var resp = await client.PostAsJsonAsync("/prod-api/batchDeleteStaff", staffUuids); resp.EnsureSuccessStatusCode(); } /// 2.4.3 远程授权开门 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(); } /// 2.18.8 代理 KMS 第三方登录/事件记录 public async Task 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 — 设备控制(远程开门) // ═══════════════════════════════════════════ /// 向设备下发控制指令(如远程开门) public async Task SendControlAsync(string sourceDeviceId, string command, Dictionary parameters) { await _limiter.WaitAsync(); try { if (command == "open" || command == "authorize") { var req = new KmsRemotePermissionRequest { StaffIds = parameters.TryGetValue("staffIds", out var s) && s is List sl ? sl : null, OpenerIds = parameters.TryGetValue("lockholeSort", out var lh) ? new List { (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 — 业务记录查询 // ═══════════════════════════════════════════ /// 按类型查询业务记录 public async Task> GetBusinessLogsAsync( string logType, DateTime? from, DateTime? to, int page, int size, Dictionary? filters = null) { if (logType == "borrow" || logType == "handover") { var records = await GetBorrowRecordsAsync(from, to); return new PagedResult { 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 { 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 { Items = new(), Total = 0 }; } // ═══════════════════════════════════════════ // IAcceptsDataSync — 数据同步写入 // ═══════════════════════════════════════════ /// 向 KMS 批量同步数据 public async Task SyncDataAsync(string dataType, List items) { if (dataType != "staff") return new SyncResult { SuccessCount = 0, FailCount = 0, Message = $"不支持的数据类型: {dataType}" }; try { var staffList = items.Cast().ToList(); await BatchSyncStaffAsync(staffList); return new SyncResult { SuccessCount = staffList.Count }; } catch (Exception ex) { return new SyncResult { FailCount = items.Count, Message = ex.Message }; } } /// 从 KMS 批量删除数据 public async Task DeleteDataAsync(string dataType, List 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 }; } } }