338 lines
15 KiB
C#
338 lines
15 KiB
C#
using IntegrationGateway.Core.Abstractions;
|
||
using IntegrationGateway.Core.Infrastructure;
|
||
using IntegrationGateway.Core.Models;
|
||
using System.Net.Http.Json;
|
||
using System.Text;
|
||
using System.Text.Json;
|
||
|
||
namespace IntegrationGateway.Adapters.Kms;
|
||
|
||
/// <summary>
|
||
/// KMS 智能钥匙柜适配器。
|
||
/// 实现: IHasFlatDevices + IHasAlarms。
|
||
///
|
||
/// 设备模型:柜体为父设备(IsParent=是),锁孔为子设备(ParentSourceId=柜体SourceId)。
|
||
/// AdapterCode: "KMS:{InstanceName}"。
|
||
/// 限流:5 QPS。
|
||
///
|
||
/// 按设计文档 §6 KmsAdapter 完整实现。
|
||
/// </summary>
|
||
public class KmsAdapter : IHasFlatDevices, IHasAlarms, IAcceptsControl, IHasBusinessLogs, IAcceptsDataSync
|
||
{
|
||
private readonly HttpClient _http;
|
||
private readonly KmsAuthHelper _auth;
|
||
private readonly RateLimiter _limiter = new(5);
|
||
|
||
/// <summary>适配器编码,格式 "KMS:{实例名}"</summary>
|
||
public string AdapterCode { get; }
|
||
|
||
/// <summary>人类可读的适配器名称</summary>
|
||
public string DisplayName => $"KMS ({AdapterCode})";
|
||
|
||
/// <summary>适配器能力声明</summary>
|
||
public AdapterCapabilities Capabilities => new() { HasFlatDevices = true, HasAlarms = true };
|
||
|
||
/// <summary>创建 KmsAdapter 实例</summary>
|
||
/// <param name="adapterCode">适配器编码</param>
|
||
/// <param name="http">HttpClient 实例</param>
|
||
/// <param name="baseUrl">KMS 服务地址</param>
|
||
/// <param name="clientId">KMS 客户端 ID</param>
|
||
/// <param name="clientSecret">KMS 客户端密钥</param>
|
||
public KmsAdapter(string adapterCode, HttpClient http, string baseUrl, string clientId, string clientSecret)
|
||
{
|
||
AdapterCode = adapterCode;
|
||
_http = http;
|
||
_auth = new KmsAuthHelper(http, baseUrl, clientId, clientSecret);
|
||
}
|
||
|
||
/// <summary>初始化适配器:获取 KMS Token</summary>
|
||
public async Task InitializeAsync() => await _auth.GetTokenAsync();
|
||
|
||
// ═══════════════════════════════════════════
|
||
// IGatewayAdapter — 健康检查(2.18.1 心跳)
|
||
// ═══════════════════════════════════════════
|
||
|
||
/// <summary>调用 KMS 心跳接口确认可达性</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; }
|
||
}
|
||
|
||
// ═══════════════════════════════════════════
|
||
// IHasFlatDevices — 设备列表(2.18.4 柜体+钥匙)
|
||
// ═══════════════════════════════════════════
|
||
|
||
/// <summary>
|
||
/// 获取 KMS 所有柜体及其锁孔,映射为 StandardDevice 列表。
|
||
/// 柜体为父设备(IsParent=是),锁孔为子设备(ParentSourceId=柜体SourceId)。
|
||
/// </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(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 };
|
||
}
|
||
|
||
/// <summary>KMS 柜体 → StandardDevice(父设备)</summary>
|
||
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
|
||
}
|
||
};
|
||
|
||
/// <summary>KMS 锁孔 → StandardDevice(子设备)</summary>
|
||
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
|
||
}
|
||
};
|
||
|
||
// ═══════════════════════════════════════════
|
||
// IHasAlarms — 告警(2.18.7 告警列表)
|
||
// ═══════════════════════════════════════════
|
||
|
||
/// <summary>分页查询 KMS 告警列表,映射到 StandardAlarm</summary>
|
||
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 = "普通", // KMS 不区分告警等级,统一"普通"
|
||
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 };
|
||
}
|
||
|
||
/// <summary>确认告警(调 KMS 标准告警确认接口)</summary>
|
||
public async Task ConfirmAlarmAsync(string alarmId)
|
||
{
|
||
await _limiter.WaitAsync();
|
||
var client = await _auth.GetAuthenticatedClientAsync();
|
||
await client.PostAsync($"/prod-api/kms/warning/confirm/{alarmId}", null);
|
||
}
|
||
|
||
/// <summary>结束告警(KMS 第三方接口不提供,留空实现)</summary>
|
||
public Task EndAlarmAsync(string alarmId)
|
||
{
|
||
// KMS 第三方接口 (2.18.7) 不提供告警结束 API
|
||
return Task.CompletedTask;
|
||
}
|
||
|
||
// ═══════════════════════════════════════════
|
||
// 扩展方法 — 2.18 第三方接口全覆盖
|
||
// ═══════════════════════════════════════════
|
||
|
||
/// <summary>2.18.6 查询借还记录列表</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 };
|
||
}
|
||
|
||
/// <summary>2.18.5 查询授权记录列表</summary>
|
||
public async Task<PagedResult<KmsPermission>> GetPermissionListAsync(DateTime? from = null, DateTime? to = null)
|
||
{
|
||
await _limiter.WaitAsync();
|
||
var client = await _auth.GetAuthenticatedClientAsync();
|
||
var body = "{}"; // 联调时加入时间范围
|
||
var resp = await client.PostAsync("/prod-api/getPermissionList",
|
||
new StringContent(body, Encoding.UTF8, "application/json"));
|
||
resp.EnsureSuccessStatusCode();
|
||
var data = await resp.Content.ReadFromJsonAsync<KmsPermissionListResponse>()!;
|
||
return new PagedResult<KmsPermission> { Items = data.Rows ?? new(), Total = data.Total };
|
||
}
|
||
|
||
/// <summary>2.18.3 从 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();
|
||
}
|
||
|
||
/// <summary>2.18.2 从 Vol.Pro 向 KMS 批量删除员工</summary>
|
||
public async Task BatchDeleteStaffAsync(List<string> staffUuids)
|
||
{
|
||
await _limiter.WaitAsync();
|
||
var client = await _auth.GetAuthenticatedClientAsync();
|
||
var resp = await client.PostAsJsonAsync("/prod-api/batchDeleteStaff", staffUuids);
|
||
resp.EnsureSuccessStatusCode();
|
||
}
|
||
|
||
/// <summary>2.4.3 远程授权开门</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();
|
||
}
|
||
|
||
/// <summary>2.18.8 代理 KMS 第三方登录/事件记录</summary>
|
||
public async Task<string?> ThirdPlatLoginAsync(string username)
|
||
{
|
||
await _limiter.WaitAsync();
|
||
var client = await _auth.GetAuthenticatedClientAsync();
|
||
var resp = await client.PostAsync($"/thirdPlatlogin?username={Uri.EscapeDataString(username)}", null);
|
||
if (resp.StatusCode == System.Net.HttpStatusCode.Redirect)
|
||
return resp.Headers.Location?.ToString();
|
||
resp.EnsureSuccessStatusCode();
|
||
return await resp.Content.ReadAsStringAsync();
|
||
}
|
||
|
||
// ═══════════════════════════════════════════
|
||
// IAcceptsControl — 设备控制(远程开门)
|
||
// ═══════════════════════════════════════════
|
||
|
||
/// <summary>向设备下发控制指令(如远程开门)</summary>
|
||
public async Task<ControlResult> SendControlAsync(string sourceDeviceId, string command, Dictionary<string, object?> parameters)
|
||
{
|
||
await _limiter.WaitAsync();
|
||
try
|
||
{
|
||
if (command == "open" || command == "authorize")
|
||
{
|
||
var req = new KmsRemotePermissionRequest
|
||
{
|
||
StaffIds = parameters.TryGetValue("staffIds", out var s) && s is List<int> sl ? sl : null,
|
||
OpenerIds = parameters.TryGetValue("lockholeSort", out var lh) ? new List<int> { (int)(long)lh! } : null,
|
||
Type = command == "authorize" ? 2 : 1
|
||
};
|
||
await RemoteAuthorizeAsync(req);
|
||
}
|
||
return new ControlResult { Success = true };
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return new ControlResult { Success = false, Message = ex.Message };
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════
|
||
// IHasBusinessLogs — 业务记录查询
|
||
// ═══════════════════════════════════════════
|
||
|
||
/// <summary>按类型查询业务记录</summary>
|
||
public async Task<PagedResult<BusinessLogEntry>> GetBusinessLogsAsync(
|
||
string logType, DateTime? from, DateTime? to, int page, int size, Dictionary<string, string>? filters = null)
|
||
{
|
||
if (logType == "borrow" || logType == "handover")
|
||
{
|
||
var records = await GetBorrowRecordsAsync(from, to);
|
||
return new PagedResult<BusinessLogEntry>
|
||
{
|
||
Items = records.Items.Select(r => new BusinessLogEntry
|
||
{
|
||
LogId = r.Uuid ?? "", LogType = logType,
|
||
DeviceSourceId = $"lockhole_{r.LockerName}_{r.LockholeSort}",
|
||
StaffName = r.StaffName, Description = r.OpenerName,
|
||
CreatedAt = DateTime.TryParse(r.BorrowTime, out var bt) ? bt : null
|
||
}).ToList(),
|
||
Total = records.Total
|
||
};
|
||
}
|
||
if (logType == "permission")
|
||
{
|
||
var perms = await GetPermissionListAsync(from, to);
|
||
return new PagedResult<BusinessLogEntry>
|
||
{
|
||
Items = perms.Items.Select(p => new BusinessLogEntry
|
||
{
|
||
LogId = p.Uuid ?? "", LogType = "permission",
|
||
StaffName = p.LendStaffName, Description = p.OpenerCnName,
|
||
CreatedAt = DateTime.TryParse(p.ApplyTime, out var at) ? at : null
|
||
}).ToList(),
|
||
Total = perms.Total
|
||
};
|
||
}
|
||
return new PagedResult<BusinessLogEntry> { Items = new(), Total = 0 };
|
||
}
|
||
|
||
// ═══════════════════════════════════════════
|
||
// IAcceptsDataSync — 数据同步写入
|
||
// ═══════════════════════════════════════════
|
||
|
||
/// <summary>向 KMS 批量同步数据</summary>
|
||
public async Task<SyncResult> SyncDataAsync(string dataType, List<object> items)
|
||
{
|
||
if (dataType != "staff") return new SyncResult { SuccessCount = 0, FailCount = 0, Message = $"不支持的数据类型: {dataType}" };
|
||
try
|
||
{
|
||
var staffList = items.Cast<KmsStaff>().ToList();
|
||
await BatchSyncStaffAsync(staffList);
|
||
return new SyncResult { SuccessCount = staffList.Count };
|
||
}
|
||
catch (Exception ex) { return new SyncResult { FailCount = items.Count, Message = ex.Message }; }
|
||
}
|
||
|
||
/// <summary>从 KMS 批量删除数据</summary>
|
||
public async Task<SyncResult> DeleteDataAsync(string dataType, List<string> ids)
|
||
{
|
||
if (dataType != "staff") return new SyncResult { SuccessCount = 0, FailCount = 0, Message = $"不支持的数据类型: {dataType}" };
|
||
try
|
||
{
|
||
await BatchDeleteStaffAsync(ids);
|
||
return new SyncResult { SuccessCount = ids.Count };
|
||
}
|
||
catch (Exception ex) { return new SyncResult { FailCount = ids.Count, Message = ex.Message }; }
|
||
}
|
||
}
|