# KMS 钥匙柜适配器详细设计文档 > **版本**: 1.0 > **日期**: 2025-05-19 > **基准**: `doc/整合方案/KMS钥匙柜整合方案_v2.0.md` > **接口文档**: `doc/对接文档/钥匙管理系统软件接口.docx` (KMS API v1.0.4) > **技术栈**: .NET 8 / ASP.NET Core / C# --- ## 1. 接口覆盖设计 ### 1.1 完整 KMS API 概览 KMS 系统共有 **38 个 REST 端点**,分 9 大类。按设计原则,适配器只代理 **第三方集成接口**(第 2.18 节),不代理 KMS 自身管理接口。 ### 1.2 第三方集成接口(Phase 1 — 核心实现) 以下 8 个接口是 KMS 专为第三方对接设计的扁平化 API: | # | 方法 | 路径 | 用途 | 适配器方法 | |---|:---:|------|------|------| | 2.18.1 | GET | `/prod-api/heartBeat` | 心跳检测 | `HealthCheckAsync()` | | 2.18.2 | POST | `/prod-api/batchDeleteStaff` | 批量删除员工 | `BatchDeleteStaffAsync()` | | 2.18.3 | POST | `/prod-api/batchSyncStaff` | 批量同步员工 | `BatchSyncStaffAsync()` | | 2.18.4 | POST | `/prod-api/getOpenerList` | 查询柜体+钥匙信息 | `GetDevicesAsync()` | | 2.18.5 | POST | `/prod-api/getPermissionList` | 查询授权记录 | `GetPermissionListAsync()` | | 2.18.6 | POST | `/prod-api/getRecordList` | 查询借还记录 | `GetBorrowRecordsAsync()` | | 2.18.7 | POST | `/prod-api/getWarningList` | 查询告警记录 | `GetAlarmsAsync()` | | 2.18.8 | POST | `/thirdPlatlogin` | 第三方登录/事件 | `ThirdPlatLoginAsync()` | 加上认证接口 `POST /prod-api/getToken`,适配器共需对接 **9 个 KMS 接口**。 ### 1.3 标准 KMS 管理接口(Phase 2 可选) 以下 29 个接口属于 KMS 自身管理功能(员工 CRUD、柜体 CRUD、钥匙 CRUD 等),KMS 自带 Web 管理端即可操作。Vol.Pro 如需代理这些接口,可在 Phase 2 扩展,但设计文档不列入必需实现。 | 模块 | 接口数 | 备注 | |------|:---:|------| | 交接记录 | 2 | GET `/kms/handover/*` | | 授权管理 | 3 | GET `/kms/permission/*` + POST remote | | 告警记录(标准) | 1 | GET `/kms/warning/list` | | 员工可借钥匙 | 2 | POST/GET `/kms/staffopener/*` | | 员工管理 | 5 | CRUD `/kms/staff/*` | | 员工组管理 | 6 | CRUD `/kms/staffGroup/*` | | 物品类别 | 6 | CRUD `/kms/openerType/*` | | 柜体管理 | 5 | CRUD `/kms/locker/*` + statistics | | 锁孔管理 | 4 | CRUD `/kms/lockhole/*` | | 钥匙管理 | 7 | CRUD `/kms/opener/*` + selectCanBorrow | | 钥匙组 | 7 | CRUD `/kms/openerGroup/*` | | 部门 | 1 | GET `/system/dept/root/{userId}` | | 授权详情 | 1 | GET `/kms/permissioninfo/getByPermissionId/{uuid}` | | **合计** | **29** | Phase 2 按需实现 | --- ## 2. 项目结构 ``` gateway/src/IntegrationGateway.Adapters.Kms/ ├── IntegrationGateway.Adapters.Kms.csproj # 类库, net8.0 ├── KmsAdapter.cs # 适配器主体 (IHasFlatDevices + IHasAlarms) ├── KmsAuthHelper.cs # Bearer Token 认证 └── KmsModels.cs # 请求/响应 DTO ``` ### 2.1 依赖关系 ``` Host → Adapters.Kms → Core Host → Core ``` 适配器只引用 Core,零外部 NuGet 依赖。 --- ## 3. 数据模型设计 ### 3.1 KMS 响应模型(全部 9 个第三方接口的 DTO) ```csharp // ── 认证 ── public class KmsTokenResponse { public int Code { get; set; } public string Token { get; set; } = ""; public string? Msg { get; set; } } // ── 2.18.4 柜体+钥匙 ── public class KmsOpenerListResponse { public int Code { get; set; } public string? Msg { get; set; } public List? Rows { get; set; } } public class KmsLocker { public int LockerId { get; set; } public string? LockerName { get; set; } public string? LockerCode { get; set; } public List? LockholeList { get; set; } } public class KmsLockhole { public int LockholeSort { get; set; } public int OpenerId { get; set; } public string? OpenerName { get; set; } public string? OpenerType { get; set; } public string? OpenerState { get; set; } } // ── 2.18.7 告警 ── public class KmsWarningListResponse { public int Code { get; set; } public string? Msg { get; set; } public int Total { get; set; } public List? Rows { get; set; } } public class KmsWarning { public string? Uuid { get; set; } public string? LockerName { get; set; } public int LockholeSort { get; set; } public string? OpenerName { get; set; } public int Type { get; set; } public string? WarningTime { get; set; } public string? Remark { get; set; } public string? StaffName { get; set; } } // ── 2.18.6 借还记录 ── public class KmsRecordListResponse { public int Code { get; set; } public string? Msg { get; set; } public int Total { get; set; } public List? Rows { get; set; } } public class KmsRecord { public string? Uuid { get; set; } public string? LockerName { get; set; } public int LockholeSort { get; set; } public string? OpenerName { get; set; } public string? StaffName { get; set; } public string? BorrowTime { get; set; } public string? ReturnTime { get; set; } public string? Type { get; set; } } // ── 2.18.5 授权记录 ── public class KmsPermissionListResponse { public int Code { get; set; } public string? Msg { get; set; } public int Total { get; set; } public List? Rows { get; set; } } public class KmsPermission { public string? Uuid { get; set; } public string? LockerName { get; set; } public int LockholeSort { get; set; } public string? OpenerName { get; set; } public string? StaffName { get; set; } public string? ApplyTime { get; set; } public string? BackTime { get; set; } } // ── 2.18.3 员工同步 ── public class KmsStaff { public string? Uuid { get; set; } public string? Name { get; set; } public string? CardNo { get; set; } public string? Phone { get; set; } public string? Email { get; set; } public int? DeptId { get; set; } public int? GroupId { get; set; } public int State { get; set; } public int Type { get; set; } } // ── 通用响应 ── public class KmsApiResponse { public int Code { get; set; } public string? Msg { get; set; } } ``` ### 3.2 实体映射关系 ``` KMS 物理拓扑 base_device 映射 ─────── ────────────────── 智能钥匙柜A (lockerId=25) SourceId="locker_25", IsParent=是 ├── 锁孔1 "仓库大门" SourceId="lockhole_25_1", ParentSourceId="locker_25" ├── 锁孔2 "机房钥匙" SourceId="lockhole_25_2", ParentSourceId="locker_25" └── 锁孔N ... ``` --- ## 4. KmsAuthHelper 设计 ```csharp /// /// KMS Bearer Token 认证辅助。 /// Token 通过 POST /prod-api/getToken 获取,参数 clientId + clientSecret。 /// 缓存 25 分钟(KMS 有效期 30 分钟,留 5 分钟余量)。 /// public class KmsAuthHelper { private readonly HttpClient _http; private readonly string _baseUrl, _clientId, _clientSecret; private string? _token; private DateTime _tokenExpiry = DateTime.MinValue; public KmsAuthHelper(HttpClient http, string baseUrl, string clientId, string clientSecret) { _http = http; _baseUrl = baseUrl.TrimEnd('/'); _clientId = clientId; _clientSecret = clientSecret; } /// 获取或刷新 Token(自动缓存) public async Task GetTokenAsync() { if (!string.IsNullOrEmpty(_token) && DateTime.UtcNow < _tokenExpiry) return _token; var resp = await _http.PostAsync( $"{_baseUrl}/prod-api/getToken?clientId={Uri.EscapeDataString(_clientId)}&clientSecret={Uri.EscapeDataString(_clientSecret)}", null); resp.EnsureSuccessStatusCode(); var result = await resp.Content.ReadFromJsonAsync() ?? throw new Exception("KMS Token 响应为空"); if (result.Code != 200) throw new Exception($"KMS 认证失败: code={result.Code}"); _token = result.Token; _tokenExpiry = DateTime.UtcNow.AddMinutes(25); return _token; } /// 创建一个已认证的 HttpClient public async Task GetAuthenticatedClientAsync() { var token = await GetTokenAsync(); var client = new HttpClient { BaseAddress = new Uri(_baseUrl) }; client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}"); return client; } public void Invalidate() => _token = null; } ``` --- ## 5. KmsAdapter 设计 ### 5.1 类声明与核心属性 ```csharp /// /// KMS 智能钥匙柜适配器。 /// 实现 IHasFlatDevices + IHasAlarms。 /// 通过 8 个第三方接口(2.18.X)对接 KMS 子系统。 /// AdapterCode: "KMS:{InstanceName}",限流: 5 QPS。 /// public class KmsAdapter : IHasFlatDevices, IHasAlarms { private readonly HttpClient _http; private readonly KmsAuthHelper _auth; private readonly RateLimiter _limiter = new(5); public string AdapterCode { get; } public string DisplayName => $"KMS ({AdapterCode})"; public AdapterCapabilities Capabilities => new() { HasFlatDevices = true, HasAlarms = true }; public KmsAdapter(string adapterCode, HttpClient http, string baseUrl, string clientId, string clientSecret) { AdapterCode = adapterCode; _http = http; _auth = new KmsAuthHelper(http, baseUrl, clientId, clientSecret); } public async Task InitializeAsync() => await _auth.GetTokenAsync(); ``` ### 5.2 IGatewayAdapter — 健康检查 ```csharp /// 2.18.1: 心跳检测 — GET /prod-api/heartBeat public async Task HealthCheckAsync() { try { var client = await _auth.GetAuthenticatedClientAsync(); var resp = await client.GetAsync("/prod-api/heartBeat"); return resp.IsSuccessStatusCode; } catch { return false; } } ``` ### 5.3 IHasFlatDevices — 设备同步 ```csharp /// 2.18.4: 柜体+钥匙信息 — POST /prod-api/getOpenerList 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(new StandardDevice { SourceId = $"locker_{locker.LockerId}", Name = locker.LockerName ?? $"柜体{locker.LockerId}", Category = "智能钥匙柜", Group = "门禁设备", IsParent = true, IsOnline = true, Extra = new() { ["lockerCode"] = locker.LockerCode, ["lockholeCount"] = locker.LockholeList?.Count ?? 0 } }); // 子设备: 锁孔(钥匙位) foreach (var hole in locker.LockholeList ?? new()) { devices.Add(new StandardDevice { SourceId = $"lockhole_{locker.LockerId}_{hole.LockholeSort}", Name = hole.OpenerName ?? $"锁孔{hole.LockholeSort}", Category = "钥匙位", Group = "门禁设备", IsParent = false, IsOnline = hole.OpenerState == "在位", ParentSourceId = $"locker_{locker.LockerId}", Extra = new() { ["openerId"] = hole.OpenerId, ["openerType"] = hole.OpenerType, ["openerState"] = hole.OpenerState } }); } } return new PagedResult { Items = devices, Total = devices.Count }; } ``` ### 5.4 IHasAlarms — 告警同步 ```csharp /// 2.18.7: 告警记录 — POST /prod-api/getWarningList public async Task> GetAlarmsAsync(...) { 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 = "普通", 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 }; } ``` ### 5.5 扩展方法(非接口方法,供 B 组路由直接调用) ```csharp // 2.18.6 借还记录 public async Task> GetBorrowRecordsAsync(DateTime? from = null, DateTime? to = null) { ... } // 2.18.5 授权记录 public async Task> GetPermissionListAsync(DateTime? from = null, DateTime? to = null) { ... } // 2.18.3 批量同步员工 public async Task BatchSyncStaffAsync(List staffList) { ... } // 2.18.2 批量删除员工 public async Task BatchDeleteStaffAsync(List staffUuids) { ... } // 2.4.3 远程授权开门 public async Task RemoteAuthorizeAsync(KmsRemotePermissionRequest request) { ... } // 2.18.8 第三方登录代理 public async Task ThirdPlatLoginAsync(string username) { ... } ``` --- ## 6. 配置 ### 6.1 KmsConfig POCO ```csharp public class KmsConfig { public string? InstanceName { get; set; } public string BaseUrl { get; set; } = ""; public string ClientId { get; set; } = ""; public string ClientSecret { get; set; } = ""; } ``` ### 6.2 appsettings.json ```json { "KMS": [ { "InstanceName": "main", "BaseUrl": "http://192.168.1.50:8080", "ClientId": "your_client_id", "ClientSecret": "your_client_secret" } ] } ``` ### 6.3 Program.cs 注册 ```csharp var kmsList = app.Configuration.GetSection("KMS").Get>() ?? new(); foreach (var k in kmsList) { var code = $"KMS:{k.InstanceName ?? "default"}"; var a = new KmsAdapter(code, app.Services.GetRequiredService().CreateClient("VolPro"), k.BaseUrl, k.ClientId, k.ClientSecret); registry.Register(a); } ``` --- ## 7. Vol.Pro 端配套 | 项 | 改动 | 说明 | |------|:---:|------| | 数据库 | 无 | base_device / iot_alarm 已兼容 | | 后端 | 无 | A1-A4 同步逻辑通用 | | 字典 | 新增 2 项 | "智能钥匙柜" / "钥匙位" | | 前端列表 | 无 | 自动显示 KMS 设备 | | 前端操作 | Phase 2 | KeyDeviceActions.vue(开门/授权按钮)| --- > **接口覆盖**: 9 个第三方接口(2.18.X + Token)100% 设计覆盖;29 个标准管理接口留 Phase 2 按需扩展。