K7: 3个新Core接口+4条B路由+KmsAdapter多接口实现
This commit is contained in:
7
gateway/NuGet.Config
Normal file
7
gateway/NuGet.Config
Normal 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>
|
||||||
@@ -17,7 +17,7 @@ namespace IntegrationGateway.Adapters.Kms;
|
|||||||
///
|
///
|
||||||
/// 按设计文档 §6 KmsAdapter 完整实现。
|
/// 按设计文档 §6 KmsAdapter 完整实现。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class KmsAdapter : IHasFlatDevices, IHasAlarms
|
public class KmsAdapter : IHasFlatDevices, IHasAlarms, IAcceptsControl, IHasBusinessLogs, IAcceptsDataSync
|
||||||
{
|
{
|
||||||
private readonly HttpClient _http;
|
private readonly HttpClient _http;
|
||||||
private readonly KmsAuthHelper _auth;
|
private readonly KmsAuthHelper _auth;
|
||||||
@@ -237,4 +237,101 @@ public class KmsAdapter : IHasFlatDevices, IHasAlarms
|
|||||||
resp.EnsureSuccessStatusCode();
|
resp.EnsureSuccessStatusCode();
|
||||||
return await resp.Content.ReadAsStringAsync();
|
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 }; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
@@ -6,8 +6,4 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.8" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
}
|
||||||
10
gateway/src/IntegrationGateway.Core/Models/ControlResult.cs
Normal file
10
gateway/src/IntegrationGateway.Core/Models/ControlResult.cs
Normal 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; }
|
||||||
|
}
|
||||||
12
gateway/src/IntegrationGateway.Core/Models/SyncResult.cs
Normal file
12
gateway/src/IntegrationGateway.Core/Models/SyncResult.cs
Normal 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; }
|
||||||
|
}
|
||||||
@@ -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));
|
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: 手动同步 — 触发适配器全量设备同步
|
// B3: 手动同步 — 触发适配器全量设备同步
|
||||||
app.MapPost("/api/gateway/devices/sync", async (string adapter) =>
|
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="PointIndex">点位索引</param>
|
||||||
/// <param name="Value">目标值</param>
|
/// <param name="Value">目标值</param>
|
||||||
record ControlRequest(string? DeviceId, int PointIndex, double Value);
|
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);
|
||||||
|
|||||||
Reference in New Issue
Block a user