K7: 3个新Core接口+4条B路由+KmsAdapter多接口实现

This commit is contained in:
2026-05-19 23:12:05 +08:00
parent 54a48f07c9
commit 94313a3492
10 changed files with 240 additions and 6 deletions

7
gateway/NuGet.Config Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

View File

@@ -17,7 +17,7 @@ namespace IntegrationGateway.Adapters.Kms;
///
/// 按设计文档 §6 KmsAdapter 完整实现。
/// </summary>
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 — 设备控制(远程开门)
// ═══════════════════════════════════════════
/// <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 }; }
}
}

View File

@@ -0,0 +1,16 @@
using IntegrationGateway.Core.Models;
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);
}

View File

@@ -0,0 +1,19 @@
using IntegrationGateway.Core.Models;
namespace IntegrationGateway.Core.Abstractions;
/// <summary>
/// 外部数据同步写入接口。适用于需要从 Vol.Pro 向子系统推送数据的场景(如员工同步)。
/// </summary>
public interface IAcceptsDataSync : IGatewayAdapter
{
/// <summary>批量写入数据到子系统</summary>
/// <param name="dataType">数据类型: "staff"/"department"</param>
/// <param name="items">待同步的数据对象列表</param>
Task<SyncResult> SyncDataAsync(string dataType, List<object> items);
/// <summary>批量删除子系统数据</summary>
/// <param name="dataType">数据类型</param>
/// <param name="ids">待删除的 ID 列表</param>
Task<SyncResult> DeleteDataAsync(string dataType, List<string> ids);
}

View File

@@ -0,0 +1,21 @@
using IntegrationGateway.Core.Models;
namespace IntegrationGateway.Core.Abstractions;
/// <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);
}

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
@@ -6,8 +6,4 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.8" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,20 @@
namespace IntegrationGateway.Core.Models;
/// <summary>业务记录条目</summary>
public class BusinessLogEntry
{
/// <summary>记录唯一 ID</summary>
public string LogId { get; set; } = "";
/// <summary>记录类型borrow/handover/permission/event</summary>
public string LogType { get; set; } = "";
/// <summary>关联的设备源 ID</summary>
public string? DeviceSourceId { get; set; }
/// <summary>关联的员工姓名</summary>
public string? StaffName { get; set; }
/// <summary>记录描述</summary>
public string? Description { get; set; }
/// <summary>记录创建时间</summary>
public DateTime? CreatedAt { get; set; }
/// <summary>扩展属性</summary>
public Dictionary<string, object?>? Extra { get; set; }
}

View File

@@ -0,0 +1,10 @@
namespace IntegrationGateway.Core.Models;
/// <summary>设备控制指令执行结果</summary>
public class ControlResult
{
/// <summary>操作是否成功</summary>
public bool Success { get; set; }
/// <summary>失败时的错误信息</summary>
public string? Message { get; set; }
}

View File

@@ -0,0 +1,12 @@
namespace IntegrationGateway.Core.Models;
/// <summary>数据同步操作结果</summary>
public class SyncResult
{
/// <summary>成功数量</summary>
public int SuccessCount { get; set; }
/// <summary>失败数量</summary>
public int FailCount { get; set; }
/// <summary>错误信息</summary>
public string? Message { get; set; }
}

View File

@@ -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<IAcceptsControl>(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<IHasBusinessLogs>(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<IAcceptsDataSync>(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<IAcceptsDataSync>(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);
/// <param name="PointIndex">点位索引</param>
/// <param name="Value">目标值</param>
record ControlRequest(string? DeviceId, int PointIndex, double Value);
record GatewayControlRequest(string? DeviceId, string? Command, Dictionary<string, object?>? Parameters);
record SyncRequest(string? DataType, List<object>? Items);
record SyncDeleteRequest(string? DataType, List<string>? Ids);