30 KiB
KMS 钥匙柜适配器详细设计文档 v2.0
版本: 2.0(完整接口版) 日期: 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 (完整接口版)