# KMS 钥匙柜适配器 — 任务清单 > **基准文档**: `doc/设计文档/KMS钥匙柜适配器详细设计文档.md` v2.1 > **分支**: gateway-dev > **原则**: 严格按照设计文档执行,严禁无中生有。网关/Vol.Pro 改动放倒数第二步,联调放最后。 --- ## Phase K0: 项目骨架(预计 15min) ### K0.1 创建适配器项目 - [ ] 在 `gateway/src/` 下执行 `dotnet new classlib -n IntegrationGateway.Adapters.Kms -f net8.0` - [ ] 删除自动生成的 `Class1.cs` - [ ] 添加项目引用:`dotnet add reference ../IntegrationGateway.Core/IntegrationGateway.Core.csproj` - [ ] 将项目加入解决方案:`dotnet sln add` ### K0.2 Host 引用适配器 - [ ] `dotnet add Host reference Adapters.Kms` ### K0.3 编译验证 - [ ] `dotnet build` → 0 错误 > **K0 提交点**: `PhaseK0_scaffold — Kms适配器项目骨架就绪` --- ## Phase K1: KmsModels — 数据模型(预计 1h) ### K1.1 认证模型 - [ ] 创建 `KmsModels.cs` - [ ] 添加 `KmsTokenResponse { Code, Token, Msg }` ### K1.2 第三方接口响应模型(2.18.X) - [ ] `KmsOpenerListResponse { Code, Msg, Rows }` - [ ] `KmsLocker { LockerId, LockerName, LockerCode, LockholeList }` - [ ] `KmsLockhole { LockholeSort, OpenerId, OpenerName, OpenerType, OpenerState }` - [ ] `KmsWarningListResponse { Code, Msg, Total, Rows }` - [ ] `KmsWarning { Uuid, LockerName, LockholeSort, OpenerName, Type, WarningTime, Remark, StaffName }` - [ ] `KmsRecordListResponse { Code, Msg, Total, Rows }` - [ ] `KmsRecord { Uuid, LockerName, LockholeSort, OpenerName, StaffName, BorrowTime, ReturnTime, Type }` ### K1.3 标准接口响应模型(2.3-2.17) - [ ] `KmsHandoverInfo` — 交接记录 - [ ] `KmsPermissionListResponse` + `KmsPermission` — 授权记录 - [ ] `KmsStaffListResponse` + `KmsStaff` — 员工 - [ ] `KmsLockerListResponse` + `KmsLockerInfo` — 柜体 - [ ] `KmsLockholeListResponse` + `KmsLockholeInfo` — 锁孔 - [ ] `KmsOpenerListResponse2` + `KmsOpenerInfo` — 钥匙 - [ ] `KmsStaffOpenerListResponse` + `KmsStaffOpener` — 员工可借 - [ ] `KmsRemotePermissionRequest` — 远程授权请求(联调时确认字段) ### K1.4 编译验证 - [ ] `dotnet build` → 0 错误(DTO 引用 Core 的 `StandardDevice`/`StandardAlarm` 等确认无编译错误) > **K1 提交点**: `PhaseK1_models — KmsModels.cs 完整定义全部 15 个 DTO` --- ## Phase K2: KmsAuthHelper — 认证(预计 30min) ### K2.1 创建 KmsAuthHelper.cs - [ ] 构造函数:接收 `HttpClient`, `baseUrl`, `clientId`, `clientSecret` - [ ] 属性:`_token` (string?), `_tokenExpiry` (DateTime) - [ ] 依赖:`System.Text.Json`, `System.Net.Http.Json` ### K2.2 GetTokenAsync - [ ] POST `/prod-api/getToken?clientId=xx&clientSecret=yy` - [ ] 检查 `resp.EnsureSuccessStatusCode()` - [ ] 反序列化 `KmsTokenResponse` - [ ] 校验 `Code == 200` - [ ] 缓存 Token,过期时间 = `UtcNow.AddMinutes(25)`(30 分钟效期,5 分钟余量) ### K2.3 GetAuthenticatedClientAsync - [ ] 调用 `GetTokenAsync()` - [ ] 创建新 `HttpClient`,`BaseAddress = _baseUrl` - [ ] 设置 Header `Authorization: Bearer {token}` - [ ] 返回 client ### K2.4 Invalidate - [ ] `_token = null` 强制下次重新获取 ### K2.5 编译验证 - [ ] `dotnet build` → 0 错误 > **K2 提交点**: `PhaseK2_auth — KmsAuthHelper Bearer Token 认证就绪` --- ## Phase K3: KmsAdapter 核心方法(预计 1.5h) ### K3.1 类定义与构造函数 - [ ] `public class KmsAdapter : IHasFlatDevices, IHasAlarms` - [ ] 字段:`_http`, `_auth` (KmsAuthHelper), `_limiter` (RateLimiter(5)) - [ ] 属性:`AdapterCode`, `DisplayName`, `Capabilities { HasFlatDevices=true, HasAlarms=true }` - [ ] 构造函数:注入 `httpClient`, `baseUrl`, `clientId`, `clientSecret` ### K3.2 InitializeAsync - [ ] `await _auth.GetTokenAsync()` ### K3.3 HealthCheckAsync(2.18.1) - [ ] POST `/prod-api/heartBeat` (空 body `{}`) - [ ] 返回 `resp.IsSuccessStatusCode` - [ ] 异常捕获返回 false ### K3.4 GetDevicesAsync(2.18.4 — 柜体+锁孔 → StandardDevice) - [ ] `await _limiter.WaitAsync()` - [ ] POST `/prod-api/getOpenerList` (body `{}`) - [ ] 反序列化 `KmsOpenerListResponse` - [ ] 遍历 `Rows`: - 每个 `KmsLocker` → `MapLockerToDevice`(父设备,SourceId=`locker_{LockerId}`) - 每个 `KmsLockhole` → `MapLockholeToDevice`(子设备,ParentSourceId=`locker_{LockerId}`) - [ ] IsOnline 判断:`OpenerState == "在位"` → true - [ ] Extra 字典:`{ openerId, openerType, openerState }` / `{ lockerCode, lockholeCount }` - [ ] 返回 `PagedResult` ### K3.5 GetAlarmsAsync(2.18.7 — 告警列表 → StandardAlarm) - [ ] `await _limiter.WaitAsync()` - [ ] POST `/prod-api/getWarningList` (body `{}`) - [ ] 反序列化 `KmsWarningListResponse` - [ ] 映射:`AlarmId=uuid`, `Title="{lockerName} 锁孔{lockholeSort}: {openerName}"`, `Status=Type==1?"未确认":"已结束"`, `Level="普通"` - [ ] 返回 `PagedResult` ### K3.6 ConfirmAlarmAsync / EndAlarmAsync - [ ] `ConfirmAlarmAsync`: POST `/prod-api/kms/warning/confirm/{alarmId}` - [ ] `EndAlarmAsync`: 留空实现(KMS 第三方接口不提供结束告警) ### K3.7 编译验证 - [ ] `dotnet build` → 0 错误 > **K3 提交点**: `PhaseK3_adapter_core — KmsAdapter 核心4方法就绪(HealthCheck/GetDevices/GetAlarms/Confirm)` --- ## Phase K4: KmsAdapter 扩展方法(预计 1h) ### K4.1 GetBorrowRecordsAsync(2.18.6) - [ ] POST `/prod-api/getRecordList` - [ ] 参数:`from`, `to` DateTime?(联调时确认请求体格式) - [ ] 返回 `PagedResult` ### K4.2 GetPermissionListAsync(2.18.5) - [ ] POST `/prod-api/getPermissionList` - [ ] 参数:`from`, `to` DateTime? - [ ] 返回 `PagedResult` ### K4.3 BatchSyncStaffAsync(2.18.3) - [ ] POST `/prod-api/batchSyncStaff` - [ ] 请求体:`new { staff = staffList }` - [ ] `resp.EnsureSuccessStatusCode()` ### K4.4 BatchDeleteStaffAsync(2.18.2) - [ ] POST `/prod-api/batchDeleteStaff` - [ ] 请求体:`List` (staffUuid 数组) - [ ] `resp.EnsureSuccessStatusCode()` ### K4.5 RemoteAuthorizeAsync(2.4.3) - [ ] POST `/prod-api/kms/permission/remote` - [ ] 请求体:`KmsRemotePermissionRequest`(联调确认字段) ### K4.6 ThirdPlatLoginAsync(2.18.8) - [ ] POST `/thirdPlatlogin?username={username}` - [ ] 处理 302 重定向:返回 `Location` header 或响应体 - [ ] 超时设置 15s ### K4.7 编译验证 - [ ] `dotnet build` → 0 错误 > **K4 提交点**: `PhaseK4_adapter_ext — 6个扩展方法全部就绪(记录/同步/授权/登录)` --- ## Phase K5: 配置与注册(预计 15min) ### K5.1 KmsConfig POCO - [ ] 在 `Program.cs` 同级新增 `KmsConfig` 类 - [ ] 属性:`InstanceName?`, `BaseUrl`, `ClientId`, `ClientSecret` ### K5.2 appsettings.json - [ ] 新增 `KMS` 数组配置段 - [ ] 配置项:`InstanceName`, `BaseUrl`, `ClientId`, `ClientSecret` ### K5.3 Program.cs 注册 - [ ] `var kmsList = app.Configuration.GetSection("KMS").Get>() ?? new();` - [ ] foreach 注册 `KmsAdapter("KMS:{InstanceName}", http, baseUrl, clientId, clientSecret)` - [ ] 适配器编码加入 `adapterTypes` 拼接 ### K5.4 编译验证 - [ ] `dotnet build` → 0 错误 > **K5 提交点**: `PhaseK5_config — KMS多实例配置+Program.cs注册就绪` --- ## Phase K6: 编译与自测(预计 15min) ### K6.1 全量编译 - [ ] `dotnet build` → 0 错误(确认 KMS 适配器不引入外部依赖) ### K6.2 启动测试 - [ ] `dotnet run` 启动网关 - [ ] 检查控制台输出:`[Gateway] N 个适配器已注册: Owl:main,MC4:31ku,KMS:main` - [ ] 确认 KMS 初始化失败时打印错误但不阻塞 > **K6 提交点**: `PhaseK6_build — 网关全量编译通过 KMS适配器热加载不阻塞启动` --- ## Phase K7: 网关核心与 Host 扩展(预计 1.5h)⚠️ 倒数第二步 > **说明**: 此阶段按设计文档附录 B 新增 Core 能力接口 + B 组路由,遵循网关设计原则 §3.4。 ### K7.1 新增 IAcceptsControl 接口 - [ ] 创建 `Core/Abstractions/IAcceptsControl.cs` - [ ] 方法:`Task SendControlAsync(sourceDeviceId, command, parameters)` - [ ] 新增 `Core/Models/ControlResult.cs`:`{ Success, Message }` ### K7.2 新增 IHasBusinessLogs 接口 - [ ] 创建 `Core/Abstractions/IHasBusinessLogs.cs` - [ ] 方法:`Task> GetBusinessLogsAsync(logType, from, to, page, size, filters)` - [ ] 新增 `Core/Models/BusinessLogEntry.cs`:`{ LogId, LogType, DeviceSourceId, StaffName, Description, CreatedAt, Extra }` ### K7.3 新增 IAcceptsDataSync 接口 - [ ] 创建 `Core/Abstractions/IAcceptsDataSync.cs` - [ ] 方法:`Task SyncDataAsync(dataType, items)` - [ ] 方法:`Task DeleteDataAsync(dataType, ids)` - [ ] 新增 `Core/Models/SyncResult.cs`:`{ SuccessCount, FailCount, Message }` ### K7.4 KmsAdapter 实现新接口 - [ ] `KmsAdapter` 增加 `: IAcceptsControl, IHasBusinessLogs, IAcceptsDataSync` - [ ] `SendControlAsync`:调 `RemoteAuthorizeAsync`,command="open" 时调 `/kms/permission/remote` - [ ] `GetBusinessLogsAsync`:按 logType 分发到 `GetBorrowRecordsAsync` / `GetPermissionListAsync` / 交接记录 - [ ] `SyncDataAsync`:dataType="staff" 时调 `BatchSyncStaffAsync` - [ ] `DeleteDataAsync`:dataType="staff" 时调 `BatchDeleteStaffAsync` ### K7.5 Program.cs 新增 B 组路由 - [ ] `POST /api/gateway/control/{adapter}` — `IAcceptsControl.SendControlAsync` - [ ] `GET /api/gateway/logs/{adapter}` — `IHasBusinessLogs.GetBusinessLogsAsync` - [ ] `POST /api/gateway/sync/{adapter}` — `IAcceptsDataSync.SyncDataAsync` - [ ] `DELETE /api/gateway/sync/{adapter}` — `IAcceptsDataSync.DeleteDataAsync` ### K7.6 编译验证 - [ ] `dotnet build` → 0 错误 > **K7 提交点**: `PhaseK7_gateway — 3个新Core接口+4条B路由+KmsAdapter多接口实现` --- ## Phase K8: Vol.Pro 管理端配套(预计 1h)⚠️ 倒数第二步 ### K8.1 数据字典补充 - [ ] 管理端 → 字典管理 → 设备种类新增:"智能钥匙柜" / "钥匙位" ### K8.2 前端操作列扩展 - [ ] 编辑 `web.vite/src/views/warehouse/device_manager/base_device.vue` - [ ] `onInited` 的 render 函数中增加 `DeviceGroup==='门禁设备'` 分支 - [ ] 显示 "开门" 按钮(调用网关 B8) - [ ] 显示 "权限" 下拉菜单(永久授权/临时授权/取消授权) ### K8.3 前端 API 调用 - [ ] `fetch()` 调网关 `http://localhost:5100/api/gateway/control/KMS:main` - [ ] 请求体:`{ sourceDeviceId, command: "open", parameters: { openerId, staffId } }` ### K8.4 编译验证 - [ ] `npm run dev` → 无编译错误 > **K8 提交点**: `PhaseK8_volpro — 字典+前端操作按钮就绪` --- ## Phase K9: 联调验证(预计 3h)⚠️ 最后 > **前置条件**: KMS 服务端可访问,已分配 clientId/clientSecret ### K9.1 认证联调 - [ ] 网关启动 → KmsAdapter.InitializeAsync 成功获取 Token - [ ] Token 过期自动刷新验证 - [ ] 错误 clientSecret → 网关控制台打印初始化失败日志 ### K9.2 设备同步联调(2.18.4) - [ ] `/api/gateway/health` 返回 KMS 适配器在线 - [ ] `/api/gateway/devices?adapter=KMS:main` 返回柜体+锁孔设备树 - [ ] 管理端 base_device 列表显示 KMS 设备(AdapterCode=KMS:main) ### K9.3 告警同步联调(2.18.7) - [ ] `/api/gateway/alarms/KMS:main` 返回告警列表 - [ ] 管理端 iot_alarm 表有记录 ### K9.4 远程控制联调(2.4.3) - [ ] `/api/gateway/control/KMS:main` → 远程开门 → KMS 端锁孔门开 ### K9.5 记录查询联调(2.18.6) - [ ] `/api/gateway/logs/KMS:main?logType=borrow` 返回借还记录 ### K9.6 员工同步联调(2.18.3) - [ ] `/api/gateway/sync/KMS:main` → 批量同步员工成功 ### K9.7 异常场景 - [ ] KMS 服务离线 → `/api/gateway/health` 中 KMS 返回 unhealthy - [ ] KMS 恢复 → 下次心跳自动变 healthy - [ ] 并发请求超过 5 QPS → 限流生效不崩溃 ### K9.8 验收 - [ ] 网关 + Vol.Pro + KMS 三端数据一致 - [ ] 管理端可查看 KMS 设备树、告警 - [ ] 前端可远程开门 > **K9 提交点**: `PhaseK9_integration — 全链路联调通过` --- ## 任务总览 | Phase | 内容 | 文件数 | 预计 | |:---:|------|:---:|:---:| | K0 | 项目骨架 | 2 | 15min | | K1 | KmsModels 全部 DTO | 1 | 1h | | K2 | KmsAuthHelper | 1 | 30min | | K3 | KmsAdapter 核心方法 | 1 | 1.5h | | K4 | KmsAdapter 扩展方法 | 1 | 1h | | K5 | 配置与注册 | 3 | 15min | | K6 | 编译自测 | — | 15min | | K7 | 网关 Core + Host 扩展 | 6 | 1.5h | | K8 | Vol.Pro 管理端配套 | 2 | 1h | | K9 | 联调验证 | — | 3h | | **合计** | — | **17** | **~10h** | --- > **版本**: 1.0 / 2025-05-19 / 严格按照 `KMS钥匙柜适配器详细设计文档.md` v2.1 制订