365 lines
15 KiB
Markdown
365 lines
15 KiB
Markdown
# 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<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; } }
|
||
|
||
// ── 2.18.7 告警 ──
|
||
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; } }
|
||
|
||
// ── 2.18.6 借还记录 ──
|
||
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; } }
|
||
|
||
// ── 2.18.5 授权记录 ──
|
||
public class KmsPermissionListResponse { public int Code { get; set; } public string? Msg { 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 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
|
||
/// <summary>
|
||
/// KMS Bearer Token 认证辅助。
|
||
/// Token 通过 POST /prod-api/getToken 获取,参数 clientId + clientSecret。
|
||
/// 缓存 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;
|
||
}
|
||
|
||
/// <summary>获取或刷新 Token(自动缓存)</summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>创建一个已认证的 HttpClient</summary>
|
||
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;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. KmsAdapter 设计
|
||
|
||
### 5.1 类声明与核心属性
|
||
|
||
```csharp
|
||
/// <summary>
|
||
/// KMS 智能钥匙柜适配器。
|
||
/// 实现 IHasFlatDevices + IHasAlarms。
|
||
/// 通过 8 个第三方接口(2.18.X)对接 KMS 子系统。
|
||
/// 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();
|
||
```
|
||
|
||
### 5.2 IGatewayAdapter — 健康检查
|
||
|
||
```csharp
|
||
/// <summary>2.18.1: 心跳检测 — GET /prod-api/heartBeat</summary>
|
||
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; }
|
||
}
|
||
```
|
||
|
||
### 5.3 IHasFlatDevices — 设备同步
|
||
|
||
```csharp
|
||
/// <summary>2.18.4: 柜体+钥匙信息 — POST /prod-api/getOpenerList</summary>
|
||
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(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<StandardDevice> { Items = devices, Total = devices.Count };
|
||
}
|
||
```
|
||
|
||
### 5.4 IHasAlarms — 告警同步
|
||
|
||
```csharp
|
||
/// <summary>2.18.7: 告警记录 — POST /prod-api/getWarningList</summary>
|
||
public async Task<PagedResult<StandardAlarm>> 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<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 };
|
||
}
|
||
```
|
||
|
||
### 5.5 扩展方法(非接口方法,供 B 组路由直接调用)
|
||
|
||
```csharp
|
||
// 2.18.6 借还记录
|
||
public async Task<PagedResult<KmsRecord>> GetBorrowRecordsAsync(DateTime? from = null, DateTime? to = null) { ... }
|
||
|
||
// 2.18.5 授权记录
|
||
public async Task<PagedResult<KmsPermission>> GetPermissionListAsync(DateTime? from = null, DateTime? to = null) { ... }
|
||
|
||
// 2.18.3 批量同步员工
|
||
public async Task BatchSyncStaffAsync(List<KmsStaff> staffList) { ... }
|
||
|
||
// 2.18.2 批量删除员工
|
||
public async Task BatchDeleteStaffAsync(List<string> staffUuids) { ... }
|
||
|
||
// 2.4.3 远程授权开门
|
||
public async Task RemoteAuthorizeAsync(KmsRemotePermissionRequest request) { ... }
|
||
|
||
// 2.18.8 第三方登录代理
|
||
public async Task<string?> 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<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);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Vol.Pro 端配套
|
||
|
||
| 项 | 改动 | 说明 |
|
||
|------|:---:|------|
|
||
| 数据库 | 无 | base_device / iot_alarm 已兼容 |
|
||
| 后端 | 无 | A1-A4 同步逻辑通用 |
|
||
| 字典 | 新增 2 项 | "智能钥匙柜" / "钥匙位" |
|
||
| 前端列表 | 无 | 自动显示 KMS 设备 |
|
||
| 前端操作 | Phase 2 | KeyDeviceActions.vue(开门/授权按钮)|
|
||
|
||
---
|
||
|
||
> **接口覆盖**: 9 个第三方接口(2.18.X + Token)100% 设计覆盖;29 个标准管理接口留 Phase 2 按需扩展。
|