diff --git a/gateway/src/IntegrationGateway.Adapters.Kms/KmsAdapter.cs b/gateway/src/IntegrationGateway.Adapters.Kms/KmsAdapter.cs
new file mode 100644
index 0000000..5cdf835
--- /dev/null
+++ b/gateway/src/IntegrationGateway.Adapters.Kms/KmsAdapter.cs
@@ -0,0 +1,171 @@
+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
+{
+ 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;
+ }
+}