fix all via Repository.DbContext

This commit is contained in:
2026-05-17 01:07:00 +08:00
parent 52bef2b92a
commit 21c9565146
4 changed files with 122 additions and 164 deletions

View File

@@ -1,7 +1,6 @@
/* /*
*接口编写处... *网关节点管理 — A1注册/A2心跳/A3设备同步/A4告警同步
*如果接口需要做Action的权限验证请在Action上使用属性 *通过 Repository.DbContext 直接操作 SqlSugar
*如: [ApiActionPermission("gateway_nodes",Enums.ActionPermissionOptions.Search)]
*/ */
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System; using System;
@@ -11,47 +10,27 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using VolPro.Entity.DomainModels; using VolPro.Entity.DomainModels;
using Warehouse.IServices; using Warehouse.IServices;
using Warehouse.IRepositories;
using System.Linq;
using Microsoft.EntityFrameworkCore;
namespace Warehouse.Controllers namespace Warehouse.Controllers
{ {
public partial class gateway_nodesController public partial class gateway_nodesController
{ {
private readonly Igateway_nodesService _service;//访问业务代码 private readonly Igateway_nodesRepository _repo;
private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHttpContextAccessor _httpContextAccessor;
private readonly Ibase_deviceService _deviceService;
private readonly Iiot_alarmService _alarmService;
[ActivatorUtilitiesConstructor] [ActivatorUtilitiesConstructor]
public gateway_nodesController( public gateway_nodesController(
Igateway_nodesService service, Igateway_nodesService service,
IHttpContextAccessor httpContextAccessor, Igateway_nodesRepository repository,
Ibase_deviceService deviceService, IHttpContextAccessor httpContextAccessor
Iiot_alarmService alarmService
) )
: base(service) : base(service)
{ {
_service = service; _repo = repository;
_httpContextAccessor = httpContextAccessor; _httpContextAccessor = httpContextAccessor;
_deviceService = deviceService;
_alarmService = alarmService;
}
private readonly Ibase_deviceService _deviceService;
private readonly Iiot_alarmService _alarmService;
[ActivatorUtilitiesConstructor]
public gateway_nodesController(
Igateway_nodesService service,
IHttpContextAccessor httpContextAccessor,
Ibase_deviceService deviceService,
Iiot_alarmService alarmService
)
: base(service)
{
_service = service;
_httpContextAccessor = httpContextAccessor;
_deviceService = deviceService;
_alarmService = alarmService;
} }
/// <summary>A1: 网关注册 (Upsert)</summary> /// <summary>A1: 网关注册 (Upsert)</summary>
@@ -62,22 +41,21 @@ namespace Warehouse.Controllers
if (string.IsNullOrEmpty(req.NodeCode) || string.IsNullOrEmpty(req.Token)) if (string.IsNullOrEmpty(req.NodeCode) || string.IsNullOrEmpty(req.Token))
return BadRequest(new { message = "NodeCode and Token required" }); return BadRequest(new { message = "NodeCode and Token required" });
var existing = await _service.FindAsIQueryable(x => x.NodeCode == req.NodeCode) var existing = _repo.DbContext.Queryable<gateway_nodes>()
.FirstOrDefaultAsync(); .First(x => x.NodeCode == req.NodeCode);
gateway_nodes entity;
gateway_nodes entity;
if (existing != null) if (existing != null)
{ {
// 验证 Token
if (existing.NodeToken != req.Token) if (existing.NodeToken != req.Token)
return StatusCode(401, new { message = "认证失败" }); return StatusCode(401, new { message = "认证失败" });
existing.AdapterTypes = req.AdapterTypes;
existing.BaseUrl = req.BaseUrl;
existing.IsOnline = "在线";
existing.LastHeartbeat = DateTime.Now;
_repo.DbContext.Updateable(existing).ExecuteCommand();
entity = existing; entity = existing;
entity.AdapterTypes = req.AdapterTypes;
entity.BaseUrl = req.BaseUrl;
entity.IsOnline = "在线";
entity.LastHeartbeat = DateTime.Now;
_service.Add<object>(entity);
} }
else else
{ {
@@ -93,11 +71,15 @@ namespace Warehouse.Controllers
Enable = "启用", Enable = "启用",
CreateDate = DateTime.Now CreateDate = DateTime.Now
}; };
_service.Add<object>(entity); _repo.DbContext.Insertable(entity).ExecuteCommand();
} }
var devices = await _service.ServiceProvider.GetService<VolPro.WebApi.Controllers.Warehouse.base_deviceController>() // 返回当前网关的顶层设备
?.GetDevicesForGateway(entity.NodeId) ?? new List<object>(); var deviceRepo = _repo.DbContext;
var devices = deviceRepo.Queryable<base_device>()
.Where(x => x.GatewayNodeId == entity.NodeId && x.ParentDeviceId == null)
.Select(x => new { x.DeviceId, x.DeviceName, x.AdapterCode, x.SourceId, x.DeviceCategory, x.DeviceGroup, x.IsParent, x.IsOnline, x.ExtraData })
.ToList();
return Ok(new { nodeId = entity.NodeId, devices }); return Ok(new { nodeId = entity.NodeId, devices });
} }
@@ -107,14 +89,14 @@ namespace Warehouse.Controllers
[Route("/api/gateway/heartbeat")] [Route("/api/gateway/heartbeat")]
public async Task<IActionResult> GatewayHeartbeat([FromBody] GatewayHeartbeatRequest req) public async Task<IActionResult> GatewayHeartbeat([FromBody] GatewayHeartbeatRequest req)
{ {
var entity = await _service.FindAsIQueryable(x => x.NodeCode == req.NodeCode && x.NodeToken == req.Token) var entity = _repo.DbContext.Queryable<gateway_nodes>()
.FirstOrDefaultAsync(); .First(x => x.NodeCode == req.NodeCode && x.NodeToken == req.Token);
if (entity == null) if (entity == null)
return StatusCode(401, new { message = "认证失败" }); return StatusCode(401, new { message = "认证失败" });
entity.IsOnline = "在线"; entity.IsOnline = "在线";
entity.LastHeartbeat = DateTime.Now; entity.LastHeartbeat = DateTime.Now;
_service.Add<object>(entity); _repo.DbContext.Updateable(entity).ExecuteCommand();
return Ok(new { status = "ok", serverTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") }); return Ok(new { status = "ok", serverTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") });
} }
@@ -124,32 +106,28 @@ namespace Warehouse.Controllers
[Route("/api/gateway/sync/devices")] [Route("/api/gateway/sync/devices")]
public async Task<IActionResult> SyncDevices([FromBody] SyncDevicesRequest req) public async Task<IActionResult> SyncDevices([FromBody] SyncDevicesRequest req)
{ {
var node = await _service.FindAsIQueryable(x => x.NodeCode == req.NodeCode && x.NodeToken == req.Token) var node = _repo.DbContext.Queryable<gateway_nodes>()
.FirstOrDefaultAsync(); .First(x => x.NodeCode == req.NodeCode && x.NodeToken == req.Token);
if (node == null) return StatusCode(401, new { message = "认证失败" }); if (node == null) return StatusCode(401, new { message = "认证失败" });
// 批量查询已有设备映射表(用于 parentSourceId 解析) var db = _repo.DbContext;
// 批量查询已有设备映射表
var codes = req.Devices.Select(d => d.AdapterCode).Distinct().ToList(); var codes = req.Devices.Select(d => d.AdapterCode).Distinct().ToList();
var existingIds = await _deviceService var existingIds = db.Queryable<base_device>()
.FindAsIQueryable(x => codes.Contains(x.AdapterCode) && x.GatewayNodeId == node.NodeId) .Where(x => x.GatewayNodeId == node.NodeId && codes.Contains(x.AdapterCode!))
.ToDictionaryAsync(x => (x.AdapterCode, x.SourceId), x => x.DeviceId); .ToDictionary(x => (x.AdapterCode!, x.SourceId!), x => x.DeviceId);
int added = 0, updated = 0; int added = 0, updated = 0;
foreach (var d in req.Devices) foreach (var d in req.Devices)
{ {
var key = (d.AdapterCode, d.SourceId); bool isNew = !existingIds.ContainsKey((d.AdapterCode, d.SourceId));
existingIds.TryGetValue(key, out var existingId);
bool isNew = existingId == 0;
// 解析 parentSourceId → ParentDeviceId // 解析 parentSourceId → ParentDeviceId
int? parentDeviceId = null; int? parentDeviceId = null;
if (!string.IsNullOrEmpty(d.ParentSourceId)) if (!string.IsNullOrEmpty(d.ParentSourceId) && existingIds.TryGetValue((d.AdapterCode, d.ParentSourceId), out var pid))
{ parentDeviceId = pid;
existingIds.TryGetValue((d.AdapterCode, d.ParentSourceId), out var pid);
if (pid > 0) parentDeviceId = pid;
}
// 首次入库写全量,已有记录仅更新网关字段
if (isNew) if (isNew)
{ {
var entity = new base_device var entity = new base_device
@@ -172,12 +150,13 @@ namespace Warehouse.Controllers
LastSyncTime = DateTime.Now, LastSyncTime = DateTime.Now,
CreateDate = DateTime.Now CreateDate = DateTime.Now
}; };
_deviceService.Add<object>(entity); db.Insertable(entity).ExecuteCommand();
added++; added++;
} }
else else
{ {
var entity = await _deviceService.FindAsIQueryable(x => x.DeviceId == existingId).FirstOrDefaultAsync(); var existingId = existingIds[(d.AdapterCode, d.SourceId)];
var entity = db.Queryable<base_device>().InSingle(existingId);
if (entity != null) if (entity != null)
{ {
entity.IsOnline = d.IsOnline ? "在线" : "离线"; entity.IsOnline = d.IsOnline ? "在线" : "离线";
@@ -189,7 +168,7 @@ namespace Warehouse.Controllers
? System.Text.Json.JsonSerializer.Serialize(d.ExtraData) ? System.Text.Json.JsonSerializer.Serialize(d.ExtraData)
: entity.ExtraData; : entity.ExtraData;
entity.LastSyncTime = DateTime.Now; entity.LastSyncTime = DateTime.Now;
_deviceService.Add<object>(entity); db.Updateable(entity).ExecuteCommand();
updated++; updated++;
} }
} }
@@ -197,32 +176,30 @@ namespace Warehouse.Controllers
return Ok(new { added, updated, removed = 0 }); return Ok(new { added, updated, removed = 0 });
} }
/// <summary>A4: 告警同步</summary> /// <summary>A4: 告警同步DeviceSourceId→DeviceId映射 + 去重)</summary>
[HttpPost] [HttpPost]
[Route("/api/gateway/sync/alarms")] [Route("/api/gateway/sync/alarms")]
public async Task<IActionResult> SyncAlarms([FromBody] SyncAlarmsRequest req) public async Task<IActionResult> SyncAlarms([FromBody] SyncAlarmsRequest req)
{ {
var node = await _service.FindAsIQueryable(x => x.NodeCode == req.NodeCode && x.NodeToken == req.Token) var node = _repo.DbContext.Queryable<gateway_nodes>()
.FirstOrDefaultAsync(); .First(x => x.NodeCode == req.NodeCode && x.NodeToken == req.Token);
if (node == null) return StatusCode(401, new { message = "认证失败" }); if (node == null) return StatusCode(401, new { message = "认证失败" });
// 批量查出 DeviceSourceId → DeviceId 映射 var db = _repo.DbContext;
var codes = req.Alarms.Select(a => a.AdapterCode).Distinct().ToList();
// 批量查 DeviceSourceId → DeviceId
var srcIds = req.Alarms.Select(a => a.DeviceSourceId).ToList(); var srcIds = req.Alarms.Select(a => a.DeviceSourceId).ToList();
var deviceMap = await _deviceService var deviceMap = db.Queryable<base_device>()
.FindAsIQueryable(x => codes.Contains(x.AdapterCode) && srcIds.Contains(x.SourceId)) .Where(x => x.GatewayNodeId == node.NodeId && srcIds.Contains(x.SourceId!))
.ToDictionaryAsync(x => (x.AdapterCode, x.SourceId), x => x.DeviceId); .ToDictionary(x => x.SourceId!, x => x.DeviceId);
int added = 0; int added = 0;
foreach (var a in req.Alarms) foreach (var a in req.Alarms)
{ {
// 跳过已存在的告警SourceAlarmId 去重) if (db.Queryable<iot_alarm>().Any(x => x.SourceAlarmId == a.SourceAlarmId))
var exists = await _alarmService continue;
.FindAsIQueryable(x => x.SourceAlarmId == a.SourceAlarmId)
.AnyAsync();
if (exists) continue;
deviceMap.TryGetValue((a.AdapterCode, a.DeviceSourceId), out var deviceId); deviceMap.TryGetValue(a.DeviceSourceId, out var deviceId);
var alarm = new iot_alarm var alarm = new iot_alarm
{ {
SourceAlarmId = a.SourceAlarmId, SourceAlarmId = a.SourceAlarmId,
@@ -235,7 +212,7 @@ namespace Warehouse.Controllers
State = "未确认", State = "未确认",
CreateDate = DateTime.Now CreateDate = DateTime.Now
}; };
_alarmService.Add<object>(alarm); db.Insertable(alarm).ExecuteCommand();
added++; added++;
} }
return Ok(new { added }); return Ok(new { added });

View File

@@ -1,6 +1,7 @@
using Quartz; using Quartz;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Warehouse.IServices; using Warehouse.IRepositories;
using VolPro.Entity.DomainModels;
namespace VolPro.Warehouse.Services; namespace VolPro.Warehouse.Services;
@@ -9,16 +10,18 @@ public class HeartbeatMonitorJob : IJob
public async Task Execute(IJobExecutionContext context) public async Task Execute(IJobExecutionContext context)
{ {
var sp = (IServiceProvider)context.JobDetail.JobDataMap["ServiceProvider"]; var sp = (IServiceProvider)context.JobDetail.JobDataMap["ServiceProvider"];
var gwSvc = sp.GetService<Igateway_nodesService>(); var gwRepo = sp.GetService<Igateway_nodesRepository>();
var db = gwRepo.DbContext;
var timeout = DateTime.Now.AddSeconds(-30); var timeout = DateTime.Now.AddSeconds(-30);
var offlineNodes = await gwSvc.FindAsIQueryable(x => x.IsOnline == "在线" && x.LastHeartbeat < timeout)
.ToListAsync(); var offlineNodes = db.Queryable<gateway_nodes>()
.Where(x => x.IsOnline == "在线" && x.LastHeartbeat < timeout)
.ToList();
foreach (var node in offlineNodes) foreach (var node in offlineNodes)
{ {
node.IsOnline = "离线"; node.IsOnline = "离线";
gwSvc.Add<object>(node); db.Updateable(node).ExecuteCommand();
} }
} }
} }

View File

@@ -1,6 +1,7 @@
using Quartz; using Quartz;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Warehouse.IServices; using Warehouse.IRepositories;
using VolPro.Entity.DomainModels;
namespace VolPro.Warehouse.Services; namespace VolPro.Warehouse.Services;
@@ -9,11 +10,13 @@ public class SyncDevicesJob : IJob
public async Task Execute(IJobExecutionContext context) public async Task Execute(IJobExecutionContext context)
{ {
var sp = (IServiceProvider)context.JobDetail.JobDataMap["ServiceProvider"]; var sp = (IServiceProvider)context.JobDetail.JobDataMap["ServiceProvider"];
var gwSvc = sp.GetService<Igateway_nodesService>(); var gwRepo = sp.GetService<Igateway_nodesRepository>();
var httpFactory = sp.GetService<IHttpClientFactory>(); var httpFactory = sp.GetService<IHttpClientFactory>();
var db = gwRepo.DbContext;
var onlineNodes = await gwSvc.FindAsIQueryable(x => x.IsOnline == "在线" && x.Enable == "启用" && x.BaseUrl != null) var onlineNodes = db.Queryable<gateway_nodes>()
.ToListAsync(); .Where(x => x.IsOnline == "在线" && x.Enable == "启用" && x.BaseUrl != null)
.ToList();
foreach (var node in onlineNodes) foreach (var node in onlineNodes)
{ {

View File

@@ -1,30 +1,25 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using VolPro.Core.DbSqlSugar;
using VolPro.Core.Extensions;
using VolPro.Entity.DomainModels; using VolPro.Entity.DomainModels;
using Warehouse.IServices; using Warehouse.IRepositories;
using System.Text.Json;
namespace VolPro.Warehouse.Services; namespace VolPro.Warehouse.Services;
/// <summary> /// <summary>MC4.0 同步引擎:对象树 → 区域匹配 + 设备 Upsert</summary>
/// MC4.0 同步引擎:对象树 → 区域匹配 + 设备 Upsert
/// </summary>
public class SyncEngine public class SyncEngine
{ {
private readonly IServiceProvider _sp; private readonly IServiceProvider _sp;
public SyncEngine(IServiceProvider sp) => _sp = sp; public SyncEngine(IServiceProvider sp) => _sp = sp;
private Iwarehouse_regionsService GetRegionService() => private Iwarehouse_regionsRepository GetRegionRepo() =>
_sp.GetService<Iwarehouse_regionsService>()!; _sp.GetService<Iwarehouse_regionsRepository>()!;
private Iwarehouse_devicepointService GetPointService() => private Iwarehouse_devicepointRepository GetPointRepo() =>
_sp.GetService<Iwarehouse_devicepointService>()!; _sp.GetService<Iwarehouse_devicepointRepository>()!;
private Ibase_deviceService GetDeviceService() => private Ibase_deviceRepository GetDeviceRepo() =>
_sp.GetService<Ibase_deviceService>()!; _sp.GetService<Ibase_deviceRepository>()!;
/// <summary>处理 MC4.0 对象树,匹配区域并 Upsert 设备</summary> public async Task<SyncStats> ProcessMc4TreeAsync(int gatewayNodeId, string adapterCode, List<Mc4TreeNode> tree)
public async Task<SyncStats> ProcessMc4TreeAsync(
int gatewayNodeId, string adapterCode, List<Mc4TreeNode> tree)
{ {
var stats = new SyncStats(); var stats = new SyncStats();
foreach (var node in tree) foreach (var node in tree)
@@ -35,14 +30,13 @@ public class SyncEngine
private async Task ProcessNodeAsync(int gatewayNodeId, string adapterCode, private async Task ProcessNodeAsync(int gatewayNodeId, string adapterCode,
Mc4TreeNode node, int? parentDeviceId, SyncStats stats) Mc4TreeNode node, int? parentDeviceId, SyncStats stats)
{ {
if (node.Type == 1) // 区域节点 → 匹配 warehouse_regions + warehouse_devicepoint if (node.Type == 1)
{ {
int pointId = await MatchOrCreatePoint(node); int pointId = await MatchOrCreatePoint(node);
// 递归处理子节点,子设备归属到此点位
foreach (var child in node.Children) foreach (var child in node.Children)
await ProcessNodeAsync(gatewayNodeId, adapterCode, child, null, stats); await ProcessNodeAsync(gatewayNodeId, adapterCode, child, null, stats);
} }
else if (node.Type == 2) // 设备节点 → Upsert base_device else if (node.Type == 2)
{ {
int deviceId = await UpsertDevice(gatewayNodeId, adapterCode, node, parentDeviceId); int deviceId = await UpsertDevice(gatewayNodeId, adapterCode, node, parentDeviceId);
if (stats.DeviceIds.TryGetValue(node.Id, out _)) if (stats.DeviceIds.TryGetValue(node.Id, out _))
@@ -58,34 +52,25 @@ public class SyncEngine
private async Task<int> MatchOrCreatePoint(Mc4TreeNode node) private async Task<int> MatchOrCreatePoint(Mc4TreeNode node)
{ {
// 按名称匹配已有区域 var regionRepo = GetRegionRepo();
var regionSvc = GetRegionService(); var regionDb = regionRepo.DbContext;
var region = await regionSvc.FindAsIQueryable(x => x.RegionName == node.Name)
.FirstOrDefaultAsync(); var region = regionDb.Queryable<warehouse_regions>()
.First(x => x.RegionName == node.Name);
if (region == null) if (region == null)
{ {
region = new warehouse_regions region = new warehouse_regions { RegionName = node.Name ?? $"MC4_{node.Id}" };
{ regionDb.Insertable(region).ExecuteCommand();
RegionName = node.Name ?? $"MC4_{node.Id}",
ParentId = null,
};
regionSvc.Add<object>(region);
} }
// 在此区域下找/建点位 var pointRepo = GetPointRepo();
var pointSvc = GetPointService(); var pointDb = pointRepo.DbContext;
var point = await pointSvc.FindAsIQueryable(x => x.RegionId == region.Id) var point = pointDb.Queryable<warehouse_devicepoint>()
.FirstOrDefaultAsync(); .First(x => x.RegionId == region.Id);
if (point == null) if (point == null)
{ {
point = new warehouse_devicepoint point = new warehouse_devicepoint { PointName = node.Name ?? $"MC4_PT_{node.Id}", RegionId = region.Id };
{ pointDb.Insertable(point).ExecuteCommand();
PointName = node.Name ?? $"MC4_PT_{node.Id}",
RegionId = region.Id,
};
pointSvc.Add<object>(point);
} }
return point.PointID; return point.PointID;
} }
@@ -93,11 +78,12 @@ public class SyncEngine
private async Task<int> UpsertDevice(int gatewayNodeId, string adapterCode, private async Task<int> UpsertDevice(int gatewayNodeId, string adapterCode,
Mc4TreeNode node, int? parentDeviceId) Mc4TreeNode node, int? parentDeviceId)
{ {
var svc = GetDeviceService(); var deviceRepo = GetDeviceRepo();
var db = deviceRepo.DbContext;
var sourceId = node.Id.ToString(); var sourceId = node.Id.ToString();
var existing = await svc.FindAsIQueryable(
x => x.AdapterCode == adapterCode && x.SourceId == sourceId) var existing = db.Queryable<base_device>()
.FirstOrDefaultAsync(); .First(x => x.AdapterCode == adapterCode && x.SourceId == sourceId);
if (existing != null) if (existing != null)
{ {
@@ -105,33 +91,29 @@ public class SyncEngine
existing.LastSyncTime = DateTime.Now; existing.LastSyncTime = DateTime.Now;
existing.ParentDeviceId = parentDeviceId ?? existing.ParentDeviceId; existing.ParentDeviceId = parentDeviceId ?? existing.ParentDeviceId;
if (node.Option != null) if (node.Option != null)
existing.ExtraData = System.Text.Json.JsonSerializer.Serialize(node.Option); existing.ExtraData = JsonSerializer.Serialize(node.Option);
existing.SetModifyDefaultVal(); db.Updateable(existing).ExecuteCommand();
// 通过 ServiceBase 的基础方法更新;
return existing.DeviceId; return existing.DeviceId;
} }
else
var device = new base_device
{ {
var device = new base_device DeviceName = node.Name ?? $"MC4_DEV_{node.Id}",
{ AdapterCode = adapterCode,
DeviceName = node.Name ?? $"MC4_DEV_{node.Id}", SourceId = sourceId,
AdapterCode = adapterCode, DeviceCategory = MapCategory(node.ObjectType, node.Tag),
SourceId = sourceId, DeviceGroup = "IoT设备",
DeviceCategory = MapCategory(node.ObjectType, node.Tag), GatewayNodeId = gatewayNodeId,
DeviceGroup = "IoT设备", ParentDeviceId = parentDeviceId,
GatewayNodeId = gatewayNodeId, IsParent = node.Children?.Count > 0 ? "是" : "否",
ParentDeviceId = parentDeviceId, IsOnline = "在线",
IsParent = node.Children?.Count > 0 ? "是" : "", Enable = "启用",
IsOnline = "在线", LastSyncTime = DateTime.Now,
Enable = "启用", CreateDate = DateTime.Now,
LastSyncTime = DateTime.Now, ExtraData = node.Option != null ? JsonSerializer.Serialize(node.Option) : null
ExtraData = node.Option != null };
? System.Text.Json.JsonSerializer.Serialize(node.Option) db.Insertable(device).ExecuteCommand();
: null return device.DeviceId;
};
svc.Add<object>(device);
return device.DeviceId;
}
} }
private static string MapCategory(int objectType, string? tag) => private static string MapCategory(int objectType, string? tag) =>
@@ -145,14 +127,7 @@ public class SyncEngine
}; };
} }
public class SyncStats public class SyncStats { public int Added; public int Updated; public Dictionary<int, int> DeviceIds = new(); }
{
public int Added { get; set; }
public int Updated { get; set; }
public Dictionary<int, int> DeviceIds { get; set; } = new();
}
/// <summary>简化的 MC4 树节点网关→Vol.Pro 传输用)</summary>
public class Mc4TreeNode public class Mc4TreeNode
{ {
public int Id { get; set; } public int Id { get; set; }