diff --git a/gateway/NuGet.Config b/gateway/NuGet.Config new file mode 100644 index 0000000..765346e --- /dev/null +++ b/gateway/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/gateway/src/IntegrationGateway.Adapters.Kms/KmsAdapter.cs b/gateway/src/IntegrationGateway.Adapters.Kms/KmsAdapter.cs index 5a02c7a..671114d 100644 --- a/gateway/src/IntegrationGateway.Adapters.Kms/KmsAdapter.cs +++ b/gateway/src/IntegrationGateway.Adapters.Kms/KmsAdapter.cs @@ -17,7 +17,7 @@ namespace IntegrationGateway.Adapters.Kms; /// /// 按设计文档 §6 KmsAdapter 完整实现。 /// -public class KmsAdapter : IHasFlatDevices, IHasAlarms +public class KmsAdapter : IHasFlatDevices, IHasAlarms, IAcceptsControl, IHasBusinessLogs, IAcceptsDataSync { private readonly HttpClient _http; private readonly KmsAuthHelper _auth; @@ -237,4 +237,101 @@ public class KmsAdapter : IHasFlatDevices, IHasAlarms resp.EnsureSuccessStatusCode(); return await resp.Content.ReadAsStringAsync(); } + + // ═══════════════════════════════════════════ + // IAcceptsControl — 设备控制(远程开门) + // ═══════════════════════════════════════════ + + /// 向设备下发控制指令(如远程开门) + public async Task SendControlAsync(string sourceDeviceId, string command, Dictionary parameters) + { + await _limiter.WaitAsync(); + try + { + if (command == "open" || command == "authorize") + { + var req = new KmsRemotePermissionRequest + { + StaffIds = parameters.TryGetValue("staffIds", out var s) && s is List sl ? sl : null, + OpenerIds = parameters.TryGetValue("lockholeSort", out var lh) ? new List { (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 — 业务记录查询 + // ═══════════════════════════════════════════ + + /// 按类型查询业务记录 + public async Task> GetBusinessLogsAsync( + string logType, DateTime? from, DateTime? to, int page, int size, Dictionary? filters = null) + { + if (logType == "borrow" || logType == "handover") + { + var records = await GetBorrowRecordsAsync(from, to); + return new PagedResult + { + 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 + { + 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 { Items = new(), Total = 0 }; + } + + // ═══════════════════════════════════════════ + // IAcceptsDataSync — 数据同步写入 + // ═══════════════════════════════════════════ + + /// 向 KMS 批量同步数据 + public async Task SyncDataAsync(string dataType, List items) + { + if (dataType != "staff") return new SyncResult { SuccessCount = 0, FailCount = 0, Message = $"不支持的数据类型: {dataType}" }; + try + { + var staffList = items.Cast().ToList(); + await BatchSyncStaffAsync(staffList); + return new SyncResult { SuccessCount = staffList.Count }; + } + catch (Exception ex) { return new SyncResult { FailCount = items.Count, Message = ex.Message }; } + } + + /// 从 KMS 批量删除数据 + public async Task DeleteDataAsync(string dataType, List 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 }; } + } } diff --git a/gateway/src/IntegrationGateway.Core/Abstractions/IAcceptsControl.cs b/gateway/src/IntegrationGateway.Core/Abstractions/IAcceptsControl.cs new file mode 100644 index 0000000..1403e5e --- /dev/null +++ b/gateway/src/IntegrationGateway.Core/Abstractions/IAcceptsControl.cs @@ -0,0 +1,16 @@ +using IntegrationGateway.Core.Models; + +namespace IntegrationGateway.Core.Abstractions; + +/// +/// 设备反向控制接口。适用于支持远程下发指令的子系统(如 KMS 远程开门、门禁开闸、道闸抬杆)。 +/// 控制指令为通用键值对字典,适配器内部转换为子系统指令。 +/// +public interface IAcceptsControl : IGatewayAdapter +{ + /// 向设备下发控制指令 + /// 子系统设备原始 ID + /// 指令名,如 "open"/"close"/"authorize" + /// 指令参数键值对 + Task SendControlAsync(string sourceDeviceId, string command, Dictionary parameters); +} diff --git a/gateway/src/IntegrationGateway.Core/Abstractions/IAcceptsDataSync.cs b/gateway/src/IntegrationGateway.Core/Abstractions/IAcceptsDataSync.cs new file mode 100644 index 0000000..3a05236 --- /dev/null +++ b/gateway/src/IntegrationGateway.Core/Abstractions/IAcceptsDataSync.cs @@ -0,0 +1,19 @@ +using IntegrationGateway.Core.Models; + +namespace IntegrationGateway.Core.Abstractions; + +/// +/// 外部数据同步写入接口。适用于需要从 Vol.Pro 向子系统推送数据的场景(如员工同步)。 +/// +public interface IAcceptsDataSync : IGatewayAdapter +{ + /// 批量写入数据到子系统 + /// 数据类型: "staff"/"department" + /// 待同步的数据对象列表 + Task SyncDataAsync(string dataType, List items); + + /// 批量删除子系统数据 + /// 数据类型 + /// 待删除的 ID 列表 + Task DeleteDataAsync(string dataType, List ids); +} diff --git a/gateway/src/IntegrationGateway.Core/Abstractions/IHasBusinessLogs.cs b/gateway/src/IntegrationGateway.Core/Abstractions/IHasBusinessLogs.cs new file mode 100644 index 0000000..53c3dc0 --- /dev/null +++ b/gateway/src/IntegrationGateway.Core/Abstractions/IHasBusinessLogs.cs @@ -0,0 +1,21 @@ +using IntegrationGateway.Core.Models; + +namespace IntegrationGateway.Core.Abstractions; + +/// +/// 业务记录查询接口。适用于具有借还、交接、授权等业务日志的子系统。 +/// 不走 StandardAlarm 通道,独立分页查询。 +/// +public interface IHasBusinessLogs : IGatewayAdapter +{ + /// 分页查询业务记录 + /// 记录类型: "borrow"/"handover"/"permission"/"event" + /// 开始时间 + /// 结束时间 + /// 页码 + /// 每页条数 + /// 额外过滤条件 + Task> GetBusinessLogsAsync( + string logType, DateTime? from, DateTime? to, + int page, int size, Dictionary? filters = null); +} diff --git a/gateway/src/IntegrationGateway.Core/IntegrationGateway.Core.csproj b/gateway/src/IntegrationGateway.Core/IntegrationGateway.Core.csproj index 56a1c31..30402ac 100644 --- a/gateway/src/IntegrationGateway.Core/IntegrationGateway.Core.csproj +++ b/gateway/src/IntegrationGateway.Core/IntegrationGateway.Core.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -6,8 +6,4 @@ enable - - - - diff --git a/gateway/src/IntegrationGateway.Core/Models/BusinessLogEntry.cs b/gateway/src/IntegrationGateway.Core/Models/BusinessLogEntry.cs new file mode 100644 index 0000000..6e2ced3 --- /dev/null +++ b/gateway/src/IntegrationGateway.Core/Models/BusinessLogEntry.cs @@ -0,0 +1,20 @@ +namespace IntegrationGateway.Core.Models; + +/// 业务记录条目 +public class BusinessLogEntry +{ + /// 记录唯一 ID + public string LogId { get; set; } = ""; + /// 记录类型:borrow/handover/permission/event + public string LogType { get; set; } = ""; + /// 关联的设备源 ID + public string? DeviceSourceId { get; set; } + /// 关联的员工姓名 + public string? StaffName { get; set; } + /// 记录描述 + public string? Description { get; set; } + /// 记录创建时间 + public DateTime? CreatedAt { get; set; } + /// 扩展属性 + public Dictionary? Extra { get; set; } +} diff --git a/gateway/src/IntegrationGateway.Core/Models/ControlResult.cs b/gateway/src/IntegrationGateway.Core/Models/ControlResult.cs new file mode 100644 index 0000000..5877e36 --- /dev/null +++ b/gateway/src/IntegrationGateway.Core/Models/ControlResult.cs @@ -0,0 +1,10 @@ +namespace IntegrationGateway.Core.Models; + +/// 设备控制指令执行结果 +public class ControlResult +{ + /// 操作是否成功 + public bool Success { get; set; } + /// 失败时的错误信息 + public string? Message { get; set; } +} diff --git a/gateway/src/IntegrationGateway.Core/Models/SyncResult.cs b/gateway/src/IntegrationGateway.Core/Models/SyncResult.cs new file mode 100644 index 0000000..d79338e --- /dev/null +++ b/gateway/src/IntegrationGateway.Core/Models/SyncResult.cs @@ -0,0 +1,12 @@ +namespace IntegrationGateway.Core.Models; + +/// 数据同步操作结果 +public class SyncResult +{ + /// 成功数量 + public int SuccessCount { get; set; } + /// 失败数量 + public int FailCount { get; set; } + /// 错误信息 + public string? Message { get; set; } +} diff --git a/gateway/src/IntegrationGateway.Host/Program.cs b/gateway/src/IntegrationGateway.Host/Program.cs index 12c8ce1..e393702 100644 --- a/gateway/src/IntegrationGateway.Host/Program.cs +++ b/gateway/src/IntegrationGateway.Host/Program.cs @@ -205,6 +205,39 @@ app.MapGet("/api/gateway/recordings/{adapter}/{deviceId}", async (string adapter return Results.Ok(await a.GetRecordingsAsync(deviceId, start, end, page, size)); }); +// B10: 设备控制 — 下发控制指令(远程开门/抬杆/授权) +app.MapPost("/api/gateway/control/{adapter}", async (string adapter, ControlRequest req) => +{ + var a = registry.FindByCode(adapter); + if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED" }); + var result = await a.SendControlAsync(req.DeviceId ?? "", req.Command ?? "open", req.Parameters ?? new()); + return result.Success ? Results.Ok(result) : Results.Problem(result.Message, statusCode: 502); +}); + +// B11: 业务记录查询 — 借还/交接/授权记录 +app.MapGet("/api/gateway/logs/{adapter}", async (string adapter, string logType, DateTime? from, DateTime? to, int page, int size) => +{ + var a = registry.FindByCode(adapter); + if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED" }); + return Results.Ok(await a.GetBusinessLogsAsync(logType, from, to, page, size)); +}); + +// B12: 数据同步 — 向子系统写入数据(员工同步) +app.MapPost("/api/gateway/sync/{adapter}", async (string adapter, SyncRequest req) => +{ + var a = registry.FindByCode(adapter); + if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED" }); + return Results.Ok(await a.SyncDataAsync(req.DataType ?? "staff", req.Items ?? new())); +}); + +// B13: 数据删除 — 从子系统删除数据 +app.MapDelete("/api/gateway/sync/{adapter}", async (string adapter, SyncDeleteRequest req) => +{ + var a = registry.FindByCode(adapter); + if (a == null) return Results.NotFound(new { error = "CAPABILITY_NOT_SUPPORTED" }); + return Results.Ok(await a.DeleteDataAsync(req.DataType ?? "staff", req.Ids ?? new())); +}); + // B3: 手动同步 — 触发适配器全量设备同步 app.MapPost("/api/gateway/devices/sync", async (string adapter) => { @@ -270,3 +303,6 @@ record PtzRequest(string? Direction, string Action, float Speed); /// 点位索引 /// 目标值 record ControlRequest(string? DeviceId, int PointIndex, double Value); +record GatewayControlRequest(string? DeviceId, string? Command, Dictionary? Parameters); +record SyncRequest(string? DataType, List? Items); +record SyncDeleteRequest(string? DataType, List? Ids);