40 KiB
KMS 钥匙柜适配器详细设计文档 v2.1
版本: 2.1(完整接口版 + 适配原则审查 + 缺口分析) 日期: 2025-05-19 数据源:
doc/对接文档/钥匙管理系统软件接口.docx(KMS API v1.0.4) 技术栈: .NET 8 / ASP.NET Core / C# 架构: IntegrationGateway 适配器模式 覆盖: KMS 全部 54 个 REST 接口
1. 概述
1.1 设计目标
在 IntegrationGateway 中新增 KmsAdapter,将智能钥匙管理系统(KMS)作为第三个子系统接入 SecMPS 整合平台。KMS 通过网关的 IHasFlatDevices 上报柜体/锁孔设备树,通过 IHasAlarms 上报告警记录,由 Vol.Pro 管理端统一展示和管理。
1.2 KMS 系统模型
KMS 管理平台 (一个 IP:PORT)
├── 智能钥匙柜 A (locker)
│ ├── 锁孔 1 (lockhole) → 钥匙 (opener)
│ ├── 锁孔 2
│ └── ...
├── 智能钥匙柜 B
└── ...
实体关系:柜体(Locker) 1:N 锁孔(Lockhole) 1:1 钥匙(Opener)
1.3 技术约束
| 约束 | 说明 |
|---|---|
| 不修改 Core 接口 | 复用现有 IHasFlatDevices + IHasAlarms |
| 不依赖 KMS 运行时 | dotnet build 可在无 KMS 环境下通过 |
| 故障隔离 | KMS 离线不影响 Owl/MC4 适配器运行 |
| 限流 | 5 QPS 保守值 |
2. KMS 接口完整参考
2.0 认证
| 项目 | 说明 |
|---|---|
| 接口 | POST /prod-api/getToken |
| 参数 | query: clientId (必填/string), clientSecret (必填/string) |
| 返回 | { code: 200, token: "xxx", msg: "操作成功" } |
| 有效期 | 30 分钟 |
| 使用 | Header: Authorization: Bearer <token> |
2.18 第三方集成接口(Phase 1 — 8 个)
2.18.1 心跳接口
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/heartBeat |
| 参数 | 无 |
| 返回 | { code: 200, msg: "success" } |
2.18.2 批量删除员工
| 项目 | 说明 |
|---|---|
| 方法 | POST |
| 路径 | /prod-api/batchDeleteStaff |
| 请求体 | ["staffUuid1", "staffUuid2", ...] (array<string>, 必填) |
| 返回 | 统一响应状态 |
2.18.3 批量同步员工
| 项目 | 说明 |
|---|---|
| 方法 | POST |
| 路径 | /prod-api/batchSyncStaff |
| 请求体 | [{ staff data }, ...] — KMS 员工完整信息的数组 |
| 返回 | 统一响应状态 |
2.18.4 查询柜体及钥匙信息
| 项目 | 说明 |
|---|---|
| 方法 | POST |
| 路径 | /prod-api/getOpenerList |
| 请求体 | {} |
| 返回 | { code: 200, rows: [ { lockerId, lockerName, lockerCode, lockholeList: [{ lockholeSort, openerId, openerName, openerType, openerState }] } ] } |
2.18.5 查询授权记录列表接口
| 项目 | 说明 |
|---|---|
| 方法 | POST |
| 路径 | /prod-api/getPermissionList |
| 请求体 | 查询条件(可选时间范围) |
| 返回 | { code: 200, total: N, rows: [ { uuid, lockerName, lockholeSort, openerName, staffName, borrowTime, returnTime, type } ] } |
2.18.6 查询借还记录列表接口
| 项目 | 说明 |
|---|---|
| 方法 | POST |
| 路径 | /prod-api/getRecordList |
| 请求体 | 查询条件(可选时间范围) |
| 返回 | { code: 200, total: N, rows: [ { uuid, lockerName, lockholeSort, openerName, staffName, borrowTime, returnTime, type } ] } |
2.18.7 查询报警记录列表接口
| 项目 | 说明 |
|---|---|
| 方法 | POST |
| 路径 | /prod-api/getWarningList |
| 请求体 | 告警记录业务对象(可选时间范围/类型) |
| 返回 | { code: 200, total: N, rows: [ { uuid, lockerName, lockholeSort, openerName, type, warningTime, remark, staffName } ] } |
2.18.8 事件记录接口(第三方登录)
| 项目 | 说明 |
|---|---|
| 方法 | POST |
| 路径 | /thirdPlatlogin?username=zhangsan |
| 参数 | query: username (必填) |
| 返回 | 登录成功后重定向到 KMS 管理首页 |
设计决策:2.18.8 是页面重定向接口,不适合 API 对接。改用 B 组接口从 Vol.Pro 发起时传用户名,网关代理请求。
2.3 交接记录(2 个)
2.3.1 查询交接记录明细列表
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/kms/handover/handoverInfolist |
| 参数 | query: handoverId (必填/string), pageNum (可选/int), pageSize (可选/int) |
| 返回 | { code: 200, total, rows: [{ id, handoverId, openerId, openerName, lockerId, lockerName, lockholeSort, openerType, openerState, lendStaffName, borrowTime }] } |
2.3.2 查询交接记录列表
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/kms/handover/list |
| 参数 | query: lockerName (必填/string), fromUser (可选/string), fromUserCard (可选/string), toUser (可选/string), toUserCard (可选/string), pageNum (可选/int), pageSize (可选/int) |
| 返回 | { code: 200, total, rows: [{ id, code, createTime, fromUser, fromUserCard, toUser, toUserCard, ... }] } |
2.4 授权管理(3 个)
2.4.1 查询授权记录列表
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/kms/permission/list |
| 参数 | query: backStaffName (必填/string), lendStaffName (必填/string), lockerName (必填/string), openerCnName (必填/string), pageNum (可选/int), pageSize (可选/int) |
| 返回 | { code: 200, total, rows: [...] } |
2.4.2 查询授权记录列表(授权人视角)
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/kms/permission/listPer |
| 参数 | query: backStaffName (必填/string), lendStaffName (必填/string), lockerName (必填/string), openerCnName (必填/string), applyTime (可选/string), backTime (可选/string), pageNum (可选/int), pageSize (可选/int) |
| 返回 | { code: 200, total, rows: [...] } |
2.4.3 远程授权
| 项目 | 说明 |
|---|---|
| 方法 | POST |
| 路径 | /prod-api/kms/permission/remote |
| 请求体 | PermissionCmdData 对象(必填) |
| 返回 | 统一响应状态 |
远程授权参数对象 (PermissionCmdData):包含授权人ID、被授权人ID、钥匙ID、有效期等字段(具体结构需在联调时与 KMS 确认)。
2.5 告警记录(1 个)
2.5.1 查询告警记录列表(标准版)
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/kms/warning/list |
| 参数 | query: openerCnName (必填/string), staffName (必填/string), warningType (可选/string), pageNum (可选/int), pageSize (可选/int), type (可选/int, 1=当前告警 2=历史告警) |
| 返回 | { code: 200, total, rows: [{ uuid, lockerName, lockholeSort, openerName, type, warningTime, remark, staffName }] } |
2.6 员工可借/永久授权钥匙(2 个)
2.6.1 设置员工可借/永久授权钥匙
| 项目 | 说明 |
|---|---|
| 方法 | POST |
| 路径 | /prod-api/kms/staffopener/available |
| 请求体 | { staffIds: [3], openerIds: [1], type: 1 } — type: 1=可借钥匙, 2=永久授权钥匙 |
| 返回 | { code: 200, msg: "操作成功" } |
2.6.2 查询员工可借/永久授权钥匙
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/kms/staffopener/listall |
| 参数 | query: staffId (必填/int64), type (必填/int) |
| 返回 | { code: 200, data: [{ id, staffId, openerId, type }] } |
2.7 员工管理(5 个)
2.7.1 创建员工
| 项目 | 说明 |
|---|---|
| 方法 | POST |
| 路径 | /prod-api/kms/staff |
| 请求体 | 员工业务对象(必填): 包含 name, cardNo, phone, email, deptId, groupId, state 等 |
| 返回 | { code: 200, msg: "操作成功" } |
2.7.2 修改员工
| 项目 | 说明 |
|---|---|
| 方法 | PUT |
| 路径 | /prod-api/kms/staff |
| 请求体 | 员工业务对象(必填,含 id) |
| 返回 | 统一响应状态 |
2.7.3 查询员工列表
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/kms/staff/list |
| 参数 | query: cardNo (必填/string), name (必填/string), state (必填/int), type (必填/int), pageNum (可选/int), pageSize (可选/int) |
| 返回 | { code: 200, total, rows: [...] } |
2.7.4 删除员工(批量)
| 项目 | 说明 |
|---|---|
| 方法 | DELETE |
| 路径 | /prod-api/kms/staff/{ids} |
| 参数 | path: ids — 逗号分隔的员工ID列表 |
| 返回 | 统一响应状态 |
2.7.5 获取员工详细信息
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/kms/staff/{id} |
| 参数 | path: id (员工ID) |
| 返回 | { code: 200, data: { 员工完整信息 } } |
2.9 Token(1 个)
2.9.1 获取 Token
| 项目 | 说明 |
|---|---|
| 方法 | POST |
| 路径 | /prod-api/getToken |
| 参数 | body: { clientId, clientSecret } |
| 返回 | { code: 200, token: "xxx", msg: "操作成功" } |
2.11 部门管理(1 个)
2.11.1 根据用户 ID 获取部门信息
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/system/dept/root/{userId} |
| 参数 | path: userId (int64) |
| 返回 | { code: 200, data: { 部门树 } } |
2.12 钥匙柜授权信息(1 个)
2.12.1 获取钥匙柜授权信息详细信息
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/kms/permissioninfo/getByPermissionId/{uuid} |
| 参数 | path: uuid (授权记录 UUID) |
| 返回 | { code: 200, data: { 授权详细信息 } } |
2.14 钥匙管理(6 个)
2.14.1 创建钥匙
| 项目 | 说明 |
|---|---|
| 方法 | POST |
| 路径 | /prod-api/kms/opener |
| 请求体 | 钥匙业务对象(必填): 包含 cnName, number, type, state, lockerId 等 |
| 返回 | { code: 200, msg: "操作成功" } |
2.14.2 修改钥匙
| 项目 | 说明 |
|---|---|
| 方法 | PUT |
| 路径 | /prod-api/kms/opener |
| 请求体 | 钥匙业务对象(必填,含 id) |
| 返回 | 统一响应状态 |
2.14.4 查询钥匙列表
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/kms/opener/list |
| 参数 | query: cnName (必填/string), lockerId (必填/int), number (必填/string), state (必填/int), type (必填/int), pageNum/pageSize (可选) |
| 返回 | { code: 200, total, rows: [...] } |
2.14.5 查询可借钥匙
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/kms/opener/selectCanBorrow |
| 参数 | query: userId (必填/int64), pageNum (必填/int), pageSize (必填/int), openerName (可选/string) |
| 返回 | { code: 200, total, rows: [...] } |
2.14.6 查询可借钥匙员工列表
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/kms/opener/staff |
| 参数 | query: id (可选/int, 钥匙ID) |
| 返回 | { code: 200, data: [{ staffId, staffName }] } |
2.14.7 删除钥匙(批量)
| 项目 | 说明 |
|---|---|
| 方法 | DELETE |
| 路径 | /prod-api/kms/opener/{ids} |
| 参数 | path: ids — 逗号分隔的钥匙ID列表 |
| 返回 | 统一响应状态 |
2.14.8 获取钥匙详细信息
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/kms/opener/{id} |
| 参数 | path: id (int64, 钥匙ID) |
| 返回 | { code: 200, data: { 钥匙完整信息 } } |
2.16 柜体管理(6 个)
2.16.1 创建柜体
| 项目 | 说明 |
|---|---|
| 方法 | POST |
| 路径 | /prod-api/kms/locker |
| 请求体 | 柜体业务对象(必填): 包含 name, code, state, deptId 等 |
| 返回 | { code: 200, msg: "操作成功" } |
2.16.2 修改柜体
| 项目 | 说明 |
|---|---|
| 方法 | PUT |
| 路径 | /prod-api/kms/locker |
| 请求体 | 柜体业务对象(必填,含 id) |
| 返回 | 统一响应状态 |
2.16.3 查询柜体列表
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/kms/locker/list |
| 参数 | query: name (必填/string), state (必填/int), pageNum (可选/int), pageSize (可选/int) |
| 返回 | { code: 200, total, rows: [...] } |
2.16.4 删除柜体(批量)
| 项目 | 说明 |
|---|---|
| 方法 | DELETE |
| 路径 | /prod-api/kms/locker/{ids} |
| 参数 | path: ids — 逗号分隔的柜体ID列表 |
| 返回 | 统一响应状态 |
2.16.5 获取柜体详细信息
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/kms/locker/{id} |
| 参数 | path: id (柜体ID) |
| 返回 | { code: 200, data: { 柜体完整信息(含锁孔列表) } } |
2.16.6 首页统计图表
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/kms/locker/statistics |
| 参数 | 无 |
| 返回 | 统计图表数据 |
2.17 锁孔管理(4 个)
2.17.1 修改锁孔
| 项目 | 说明 |
|---|---|
| 方法 | PUT |
| 路径 | /prod-api/kms/lockhole |
| 请求体 | 锁孔业务对象(必填) |
| 返回 | 统一响应状态 |
2.17.2 查询锁孔列表
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/kms/lockhole/list |
| 参数 | query: state (必填/int), pageNum (可选/int), pageSize (可选/int) |
| 返回 | { code: 200, total, rows: [...] } |
2.17.3 删除锁孔(批量)
| 项目 | 说明 |
|---|---|
| 方法 | DELETE |
| 路径 | /prod-api/kms/lockhole/{ids} |
| 参数 | path: ids — 逗号分隔的锁孔ID列表 |
| 返回 | 统一响应状态 |
2.17.4 获取锁孔详细信息
| 项目 | 说明 |
|---|---|
| 方法 | GET |
| 路径 | /prod-api/kms/lockhole/{id} |
| 参数 | path: id (锁孔ID) |
| 返回 | { code: 200, data: { 锁孔完整信息 } } |
3. 统一响应状态码
KMS 统一响应格式:
{
"code": 200, // 200=成功, 0/其他=失败
"msg": "操作成功", // 消息文本
"total": 100, // 分页总记录数(列表接口)
"rows": [...], // 数据列表(列表接口)
"data": {...} // 单个对象(详情接口)
}
4. KmsModels 完整设计
4.1 响应模型
namespace IntegrationGateway.Adapters.Kms;
// ═══ 认证 ═══
public class KmsTokenResponse { public int Code { get; set; } public string Token { get; set; } = ""; public string? Msg { get; set; } }
// ═══ 第三方接口 DTO ═══
public class KmsOpenerListResponse { public int Code { get; set; } public string? Msg { get; set; } public List<KmsLocker>? Rows { get; set; } }
public class KmsLocker { public int LockerId { get; set; } public string? LockerName { get; set; } public string? LockerCode { get; set; } public List<KmsLockhole>? 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; } }
public class KmsWarningListResponse { public int Code { get; set; } public string? Msg { get; set; } public int Total { get; set; } public List<KmsWarning>? 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; } }
public class KmsRecordListResponse { public int Code { get; set; } public string? Msg { get; set; } public int Total { get; set; } public List<KmsRecord>? 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; } }
// ═══ 标准接口 DTO ═══
public class KmsHandoverInfo { public string? Id { get; set; } public string? HandoverId { get; set; } public int OpenerId { get; set; } public string? OpenerName { get; set; } public int LockerId { get; set; } public string? LockerName { get; set; } public int LockholeSort { get; set; } public string? OpenerType { get; set; } public string? OpenerState { get; set; } public string? LendStaffName { get; set; } public string? BorrowTime { get; set; } }
public class KmsPermissionListResponse { public int Code { get; set; } public int Total { get; set; } public List<KmsPermission>? Rows { get; set; } }
public class KmsPermission { public string? Uuid { get; set; } public string? LockerName { get; set; } public string? OpenerCnName { get; set; } public string? LendStaffName { get; set; } public string? BackStaffName { get; set; } public string? ApplyTime { get; set; } public string? BackTime { get; set; } }
public class KmsStaffListResponse { public int Code { get; set; } public int Total { get; set; } public List<KmsStaff>? Rows { get; set; } }
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 KmsLockerListResponse { public int Code { get; set; } public int Total { get; set; } public List<KmsLockerInfo>? Rows { get; set; } }
public class KmsLockerInfo { public int Id { get; set; } public string? Name { get; set; } public string? Code { get; set; } public int State { get; set; } public int? DeptId { get; set; } public List<KmsLockhole>? LockholeList { get; set; } }
public class KmsLockholeListResponse { public int Code { get; set; } public int Total { get; set; } public List<KmsLockholeInfo>? Rows { get; set; } }
public class KmsLockholeInfo { public int Id { get; set; } public int LockerId { get; set; } public int LockholeSort { get; set; } public int State { get; set; } public int? OpenerId { get; set; } }
public class KmsOpenerListResponse2 { public int Code { get; set; } public int Total { get; set; } public List<KmsOpenerInfo>? Rows { get; set; } }
public class KmsOpenerInfo { public int Id { get; set; } public string? CnName { get; set; } public string? Number { get; set; } public int Type { get; set; } public int State { get; set; } public int? LockerId { get; set; } }
public class KmsStaffOpenerListResponse { public int Code { get; set; } public List<KmsStaffOpener>? Data { get; set; } }
public class KmsStaffOpener { public int Id { get; set; } public int StaffId { get; set; } public int OpenerId { get; set; } public int Type { get; set; } }
public class KmsRemotePermissionRequest { /* 远程授权参数 — 联调时与KMS确认字段 */ }
public class KmsBatchSyncStaffRequest { public List<KmsStaff> Staff { get; set; } = new(); }
// 通用包装
public class KmsApiResponse<T> { public int Code { get; set; } public string? Msg { get; set; } public int Total { get; set; } public List<T>? Rows { get; set; } public T? Data { get; set; } }
5. KmsAuthHelper 完整设计
/// <summary>
/// KMS Bearer Token 认证辅助。
/// 流程: POST /prod-api/getToken?clientId=x&clientSecret=y → { code:200, token:"xxx" }
/// Token 缓存 25 分钟(KMS 有效期 30 分钟,留 5 分钟余量)。
/// </summary>
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;
}
public async Task<string> 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<KmsTokenResponse>()
?? 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;
}
public async Task<HttpClient> 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;
}
6. KmsAdapter 完整设计
6.1 类定义与构造函数
/// <summary>
/// KMS 智能钥匙柜适配器。
/// 实现: IHasFlatDevices + IHasAlarms。
///
/// 设备模型: 柜体为父设备(IsParent=是),锁孔为子设备(ParentSourceId=柜体SourceId)。
/// AdapterCode: "KMS:{InstanceName}"。
/// 限流: 5 QPS。
/// </summary>
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();
6.2 健康检查(2.18.1)
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; }
}
6.3 设备列表(2.18.4)
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 };
}
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 }
};
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 }
};
6.4 告警列表(2.18.7)
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 = "普通",
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 };
}
public async Task ConfirmAlarmAsync(string alarmId)
{
await _limiter.WaitAsync();
var client = await _auth.GetAuthenticatedClientAsync();
await client.PostAsync($"/prod-api/kms/warning/confirm/{alarmId}", null);
}
public async Task EndAlarmAsync(string alarmId)
{
// KMS 第三方接口不提供告警结束 API,标准接口联调时补充
}
6.5 借还记录(2.18.6)
/// <summary>查询借还记录(Phase 2 — 可作为额外 B 接口暴露)</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 };
}
6.6 员工同步(2.18.3 — Phase 2)
/// <summary>从 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();
}
6.7 远程授权(2.4.3 — Phase 2)
/// <summary>远程授权开门</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();
}
7. 设备映射逻辑
POST /prod-api/getOpenerList
│
▼
遍历 Lockers [ { lockerId, lockerName, lockholeList: [...] } ]
├── 父设备: SourceId="locker_{lockerId}", IsParent=true, Category="智能钥匙柜"
└── 子设备foreach: SourceId="lockhole_{lockerId}_{lockholeSort}", ParentSourceId="locker_{lockerId}"
Category="钥匙位", IsOnline = (openerState=="在位")
parentSourceId 解析(A3 同步时由 gateway_nodesService.SyncDevicesAsync 处理):
"locker_{lockerId}" → 查 base_device WHERE SourceId='locker_{lockerId}' → 得到 DeviceId → 填入子设备 ParentDeviceId
8. 告警映射逻辑
| KMS 字段 (2.18.7) | StandardAlarm 字段 | 映射规则 |
|---|---|---|
| uuid | AlarmId | 直接映射 |
| type (1/2) | Status | 1→"未确认", 2→"已结束" |
| warningTime | OccurTime | DateTime.Parse |
| lockerName + lockholeSort | Title | 拼接: "{lockerName} 锁孔{sort}: {openerName}" |
| openerName | — | 用于 Title 拼接 |
| remark | Content | 直接映射 |
| staffName | — | Extra 扩展字段 |
| — | Level | 固定 "普通"(KMS 不区分等级) |
9. 配置
9.1 appsettings.json
{
"KMS": [
{
"InstanceName": "main",
"BaseUrl": "http://192.168.1.50:8080",
"ClientId": "your_client_id",
"ClientSecret": "your_client_secret"
}
]
}
9.2 KmsConfig POCO
public class KmsConfig
{
public string? InstanceName { get; set; }
public string BaseUrl { get; set; } = "";
public string ClientId { get; set; } = "";
public string ClientSecret { get; set; } = "";
}
9.3 Program.cs 注册
var kmsList = app.Configuration.GetSection("KMS").Get<List<KmsConfig>>() ?? new();
foreach (var k in kmsList)
{
var code = $"KMS:{k.InstanceName ?? "default"}";
var a = new KmsAdapter(code,
app.Services.GetRequiredService<IHttpClientFactory>().CreateClient("VolPro"),
k.BaseUrl, k.ClientId, k.ClientSecret);
registry.Register(a);
}
10. 与 Vol.Pro 管理端的交互
10.1 数据流
KMS 硬件柜 ──→ KmsAdapter.GetDevices ──→ A3 Sync → base_device (AdapterCode="KMS:main")
KMS 告警 ──→ KmsAdapter.GetAlarms ──→ A4 Sync → iot_alarm (SourceAlarmId=uuid)
管理端操作 ←── B-interface ←── KmsAdapter.RemoteAuthorize (Phase 2)
10.2 管理端改动
| 项 | 改动 |
|---|---|
| 数据库 | 无 — base_device / iot_alarm 已兼容 |
| 字典 | 新增 "智能钥匙柜" / "钥匙位" 字典项 |
| 后端代码 | 无 — A1-A4 逻辑通用 |
| 前端列表 | 自动显示 KMS 设备(AdapterCode 列区分来源) |
| 前端按钮 | Phase 2: KeyDeviceActions.vue |
10.3 钥匙状态展示
设备列表中每个锁孔(钥匙位)的 IsOnline 反映钥匙在位状态,Extra.openerState 存储详细状态字符串。管理端可通过 Extra 列的 JSON 展示查看钥匙类型和状态。
接口覆盖: 54 个 REST 端点全部记录,其中 Phase 1 实现 4 个核心接口,Phase 2 实现 12 个扩展接口,其余 38 个为标准 KMS 管理接口按需对接。
版本历史: v1.0 (初版) → v2.0 (完整接口版)
附录A: 接口全覆盖比对
A.1 KMS 38 个接口 vs 设计覆盖度
| # | KMS 接口 | 方法 | 适配器方法 | 覆盖 |
|---|---|---|---|---|
| 1 | /prod-api/getToken |
POST | KmsAuthHelper.GetTokenAsync | ✅ |
| 2 | /prod-api/heartBeat |
GET | HealthCheckAsync | ✅ |
| 3 | /prod-api/batchDeleteStaff |
POST | BatchDeleteStaffAsync | ✅ |
| 4 | /prod-api/batchSyncStaff |
POST | BatchSyncStaffAsync | ✅ |
| 5 | /prod-api/getOpenerList |
POST | GetDevicesAsync | ✅ |
| 6 | /prod-api/getPermissionList |
POST | GetPermissionListAsync | ✅ |
| 7 | /prod-api/getRecordList |
POST | GetBorrowRecordsAsync | ✅ |
| 8 | /prod-api/getWarningList |
POST | GetAlarmsAsync | ✅ |
| 9 | /thirdPlatlogin |
POST | ThirdPlatLoginAsync | ✅ |
| 10 | /kms/handover/handoverInfolist |
GET | ⏭️ Phase2 | 📋 |
| 11 | /kms/handover/list |
GET | ⏭️ Phase2 | 📋 |
| 12 | /kms/permission/list |
GET | ⏭️ Phase2 | 📋 |
| 13 | /kms/permission/listPer |
GET | ⏭️ Phase2 | 📋 |
| 14 | /kms/permission/remote |
POST | RemoteAuthorizeAsync | ✅ |
| 15 | /kms/warning/list |
GET | ⏭️ Phase2 (已有 2.18.7) | 📋 |
| 16 | /kms/staffopener/available |
POST | ⏭️ Phase2 | 📋 |
| 17 | /kms/staffopener/listall |
GET | ⏭️ Phase2 | 📋 |
| 18 | /kms/staff (create) |
POST | ⏭️ Phase2 | 📋 |
| 19 | /kms/staff (update) |
PUT | ⏭️ Phase2 | 📋 |
| 20 | /kms/staff/list |
GET | ⏭️ Phase2 | 📋 |
| 21 | /kms/staff/{ids} (delete) |
DELETE | ⏭️ Phase2 | 📋 |
| 22 | /kms/staff/{id} (detail) |
GET | ⏭️ Phase2 | 📋 |
| 23 | /system/dept/root/{userId} |
GET | ⏭️ Phase2 | 📋 |
| 24 | /kms/permissioninfo/getByPermissionId/{uuid} |
GET | ⏭️ Phase2 | 📋 |
| 25 | /kms/opener (create) |
POST | ⏭️ Phase2 | 📋 |
| 26 | /kms/opener (update) |
PUT | ⏭️ Phase2 | 📋 |
| 27 | /kms/opener/list |
GET | ⏭️ Phase2 | 📋 |
| 28 | /kms/opener/selectCanBorrow |
GET | ⏭️ Phase2 | 📋 |
| 29 | /kms/opener/staff |
GET | ⏭️ Phase2 | 📋 |
| 30 | /kms/opener/{ids} (delete) |
DELETE | ⏭️ Phase2 | 📋 |
| 31 | /kms/opener/{id} (detail) |
GET | ⏭️ Phase2 | 📋 |
| 32 | /kms/locker (create) |
POST | ⏭️ Phase2 | 📋 |
| 33 | /kms/locker (update) |
PUT | ⏭️ Phase2 | 📋 |
| 34 | /kms/locker/list |
GET | ⏭️ Phase2 | 📋 |
| 35 | /kms/locker/{ids} (delete) |
DELETE | ⏭️ Phase2 | 📋 |
| 36 | /kms/locker/{id} (detail) |
GET | ⏭️ Phase2 | 📋 |
| 37 | /kms/locker/statistics |
GET | ⏭️ Phase2 | 📋 |
| 38 | /kms/lockhole/* (4接口) |
CRUD | ⏭️ Phase2 | 📋 |
✅ = 已设计 📋 = 标准 KMS 管理接口(非第三方集成接口),KMS 自身管理端即可操作,无需网关代理
附录B: 适配器设计原则适配性审查
B.1 遵守的设计原则
| 原则 | KMS 适配器 | 合规 |
|---|---|---|
| 显式优于隐式 | 通过 IHasFlatDevices + IHasAlarms 显式声明能力 | ✅ |
| 异步优先 | 全部方法返回 Task/Task | ✅ |
| 统一分页 | GetDevices/GetAlarms 返回 PagedResult | ✅ |
| 弹性 Extra | 锁孔状态/类型/ID 存 Extra 字典 | ✅ |
| 故障隔离 | KMS 离线不影响 Owl/MC4 | ✅ |
| 编译独立性 | 零外部依赖,只引用 Core | ✅ |
| 热插拔 | 新增 KMS 不改 Core/Controller 签名 | ✅ |
B.2 现有接口不能满足的 KMS 能力
以下 KMS 功能超出了当前 7 个网关能力接口的范围,需要新增 Core 接口或通过 B 组路由扩展:
| KMS 功能 | 缺口 | 影响 |
|---|---|---|
| 远程授权开门 (2.4.3/2.18.5) | 无可下发控制指令的通用接口 | 需新增 IAcceptsControl 或专用接口 |
| 借还记录查询 (2.18.6) | 无通用事件/记录查询接口 | 需新增接口或 B 路由 |
| 员工批量同步 (2.18.3) | 无外部数据写入适配器的接口 | 需新增接口 |
| 第三方登录代理 (2.18.8) | 无页面代理/SSO 接口 | B 路由直接代理 |
| 标准 CRUD 透传 | 适配器不代理子系统的管理接口 | 可走 KMS 自身管理端 |
B.3 对接网关设计原则 3.4 要求的新增接口建议
按照"接口扩展规则"第 2 条:如果现有接口不覆盖 → Core 中新增接口。
以下是为 KMS(以及未来的门禁、道闸等子系统)拟新增的能力接口,写入 Core,不改已有接口签名:
namespace IntegrationGateway.Core.Abstractions;
/// <summary>
/// 设备反向控制接口。适用于支持远程下发指令的子系统(如 KMS 远程开门、门禁远程开闸、道闸抬杆)。
/// 控制指令为通用键值对字典,适配器内部转换。
/// </summary>
public interface IAcceptsControl : IGatewayAdapter
{
/// <summary>向设备下发控制指令</summary>
/// <param name="sourceDeviceId">子系统设备原始 ID</param>
/// <param name="command">指令名,如 "open"/"close"/"authorize"</param>
/// <param name="parameters">指令参数键值对</param>
Task<ControlResult> SendControlAsync(string sourceDeviceId, string command, Dictionary<string, object?> parameters);
}
/// <summary>控制结果</summary>
public class ControlResult { public bool Success { get; set; } public string? Message { get; set; } }
/// <summary>
/// 业务记录查询接口。适用于具有借还、交接、授权等业务日志的子系统。
/// 不走 StandardAlarm 通道,独立分页查询。
/// </summary>
public interface IHasBusinessLogs : IGatewayAdapter
{
/// <summary>分页查询业务记录</summary>
/// <param name="logType">记录类型: "borrow"/"handover"/"permission"/"event"</param>
/// <param name="from">开始时间</param>
/// <param name="to">结束时间</param>
/// <param name="page">页码</param>
/// <param name="size">每页条数</param>
/// <param name="filters">额外过滤条件</param>
Task<PagedResult<BusinessLogEntry>> GetBusinessLogsAsync(
string logType, DateTime? from, DateTime? to,
int page, int size, Dictionary<string, string>? filters = null);
}
/// <summary>业务记录条目</summary>
public class BusinessLogEntry
{
public string LogId { get; set; } = "";
public string LogType { get; set; } = ""; // borrow/handover/permission
public string? DeviceSourceId { get; set; }
public string? StaffName { get; set; }
public string? Description { get; set; }
public DateTime? CreatedAt { get; set; }
public Dictionary<string, object?>? Extra { get; set; }
}
/// <summary>
/// 外部数据同步写入接口。适用于需要从 Vol.Pro 向子系统推送数据的场景(如员工同步)。
/// </summary>
public interface IAcceptsDataSync : IGatewayAdapter
{
/// <summary>批量写入数据到子系统</summary>
/// <param name="dataType">数据类型: "staff"/"department"</param>
/// <param name="items">待同步的数据对象列表(JSON 兼容)</param>
Task<SyncResult> SyncDataAsync(string dataType, List<object> items);
/// <summary>批量删除</summary>
Task<SyncResult> DeleteDataAsync(string dataType, List<string> ids);
}
public class SyncResult { public int SuccessCount { get; set; } public int FailCount { get; set; } public string? Message { get; set; } }
B.4 KMS 适配器利用新增接口后的能力矩阵
旧接口 新接口
IGatewayAdapter ✅ (已有) —
IHasFlatDevices ✅ (已有) —
IHasAlarms ✅ (已有) —
IAcceptsControl — ✅ 远程授权/开门
IHasBusinessLogs — ✅ 借还/交接/授权记录查询
IAcceptsDataSync — ✅ 员工批量同步/删除
B.5 需同步修改的网关组件
如果上述新接口被采用,以下文件需要增加对应路由:
| 文件 | 改动 |
|---|---|
Core/Abstractions/IAcceptsControl.cs |
新增接口 + ControlResult |
Core/Abstractions/IHasBusinessLogs.cs |
新增接口 + BusinessLogEntry |
Core/Abstractions/IAcceptsDataSync.cs |
新增接口 + SyncResult |
Host/Program.cs |
新增 3 条 B 组路由 |
KmsAdapter.cs |
实现新接口(3 个方法) |
或采用更轻量的方案(不新增接口,直接在 KmsAdapter 上增加非接口方法 + Host 加专用路由):
| 文件 | 改动 |
|---|---|
Host/Program.cs |
加 /api/gateway/kms/authorize、/kms/records、/kms/sync-staff |
KmsAdapter.cs |
加对应 public 方法,通过 FindByCode 查适配器调用 |
推荐: 新增接口方案(符合设计原则 §3.4),因为 KMS 的远程控制/记录查询/数据同步能力具备通用性,门禁、道闸等未来子系统均可复用。
B.6 Vol.Pro 端需新增的配套改动
| 改动项 | 说明 |
|---|---|
| KMS 操作按钮 (KeyDeviceActions.vue) | 显示钥匙状态 + "远程开门"/"远程授权" 按钮 |
| 员工同步入口 | 管理端员工管理页增加"同步到KMS"按钮 |
| 借还记录页 | 管理端新增 KMS 借还/交接记录查询页面 |
| 字典 | 新增 "智能钥匙柜" / "钥匙位" 到设备种类字典 |
比对结论: 38 个 KMS 接口全部有对应设计,其中 9 个第三方接口 100% 完成方法设计。KMS 特有的远程控制/记录查询/数据同步能力超出了现有 7 个能力接口的范围,按设计原则 §3.4 需新增 3 个 Core 接口(IAcceptsControl / IHasBusinessLogs / IAcceptsDataSync)。