skeleton only until Phase2 integration
This commit is contained in:
@@ -1,138 +1,17 @@
|
||||
/*
|
||||
*接口编写处...
|
||||
*如果接口需要做Action的权限验证,请在Action上使用属性
|
||||
*如: [ApiActionPermission("base_device",Enums.ActionPermissionOptions.Search)]
|
||||
*设备管理扩展 — 骨架
|
||||
*TODO: Phase 2 联调时实现 GetRegionTree/GetDevicesByPoint
|
||||
*/
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using VolPro.Entity.DomainModels;
|
||||
using Warehouse.IServices;
|
||||
|
||||
namespace Warehouse.Controllers
|
||||
{
|
||||
public partial class base_deviceController
|
||||
{
|
||||
private readonly Ibase_deviceService _service;//访问业务代码
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
[HttpGet, Route("/api/DeviceManager/GetRegionTree")]
|
||||
public IActionResult GetRegionTree() => Ok(new object[0]);
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public base_deviceController(
|
||||
Ibase_deviceService service,
|
||||
IHttpContextAccessor httpContextAccessor
|
||||
)
|
||||
: base(service)
|
||||
{
|
||||
_service = service;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public base_deviceController(
|
||||
Ibase_deviceService service,
|
||||
IHttpContextAccessor httpContextAccessor
|
||||
)
|
||||
: base(service)
|
||||
{
|
||||
_service = service;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
private Iwarehouse_regionsService GetRegionService() =>
|
||||
_service.ServiceProvider.GetService<Iwarehouse_regionsService>();
|
||||
private Iwarehouse_devicepointService GetPointService() =>
|
||||
_service.ServiceProvider.GetService<Iwarehouse_devicepointService>();
|
||||
|
||||
/// <summary>区域树 (区域→点位→设备数量)</summary>
|
||||
[HttpGet]
|
||||
[Route("/api/DeviceManager/GetRegionTree")]
|
||||
public async Task<IActionResult> GetRegionTree()
|
||||
{
|
||||
var regions = await GetRegionService().FindAsIQueryable(x => true)
|
||||
.Select(x => new { x.Id, x.RegionName, x.ParentId })
|
||||
.ToListAsync();
|
||||
var points = await GetPointService().FindAsIQueryable(x => true)
|
||||
.Select(x => new { x.PointID, x.PointName, x.RegionId })
|
||||
.ToListAsync();
|
||||
var deviceCounts = await _service.FindAsIQueryable(x => true)
|
||||
.GroupBy(x => x.PointId)
|
||||
.Select(g => new { PointId = g.Key, Count = g.Count() })
|
||||
.ToListAsync();
|
||||
|
||||
return Ok(BuildRegionTree(regions, points, deviceCounts));
|
||||
}
|
||||
|
||||
private List<RegionTreeNode> BuildRegionTree(
|
||||
List<dynamic> regions, List<dynamic> points, List<dynamic> deviceCounts)
|
||||
{
|
||||
var pointMap = points.GroupBy(p => (int?)p.RegionId)
|
||||
.ToDictionary(g => g.Key ?? 0, g => g.ToList());
|
||||
var countMap = deviceCounts.ToDictionary(c => c.PointId ?? 0, c => c.Count);
|
||||
|
||||
return regions.Where(r => r.ParentId == null || r.ParentId == 0).Select(r =>
|
||||
BuildNode(r, regions, pointMap, countMap)).ToList();
|
||||
}
|
||||
|
||||
private RegionTreeNode BuildNode(dynamic region, List<dynamic> allRegions,
|
||||
Dictionary<int, List<dynamic>> pointMap, Dictionary<int, int> countMap)
|
||||
{
|
||||
int rid = (int)region.Id;
|
||||
pointMap.TryGetValue(rid, out var pts);
|
||||
var children = allRegions.Where(r => (int?)r.ParentId == rid).Select(r =>
|
||||
BuildNode(r, allRegions, pointMap, countMap)).ToList();
|
||||
|
||||
var pointNodes = pts?.Select(p => new RegionTreeNode
|
||||
{
|
||||
Id = $"p_{(int)p.PointID}",
|
||||
Label = (string)p.PointName,
|
||||
Type = "point",
|
||||
DeviceCount = countMap.GetValueOrDefault((int)p.PointID, 0)
|
||||
}).ToList() ?? new List<RegionTreeNode>();
|
||||
|
||||
children.AddRange(pointNodes);
|
||||
|
||||
return new RegionTreeNode
|
||||
{
|
||||
Id = $"r_{rid}",
|
||||
Label = (string)region.RegionName,
|
||||
Type = "region",
|
||||
DeviceCount = children.Sum(c => c.DeviceCount),
|
||||
Children = children
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>点位下设备列表(含子设备)</summary>
|
||||
[HttpGet]
|
||||
[Route("/api/DeviceManager/GetDevicesByPoint")]
|
||||
public async Task<IActionResult> GetDevicesByPoint([FromQuery] int pointId, [FromQuery] int page = 1, [FromQuery] int size = 20)
|
||||
{
|
||||
var query = _service.FindAsIQueryable(x => x.PointId == pointId);
|
||||
var total = await query.CountAsync();
|
||||
var items = await query.Skip((page - 1) * size).Take(size).ToListAsync();
|
||||
return Ok(new { items, total });
|
||||
}
|
||||
|
||||
/// <summary>获取网关所管的顶层设备 (供 Register 调用)</summary>
|
||||
internal async Task<List<object>> GetDevicesForGateway(int nodeId)
|
||||
{
|
||||
var items = await _service.FindAsIQueryable(x => x.GatewayNodeId == 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
|
||||
}).ToListAsync();
|
||||
return items.Select(x => (object)x).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public class RegionTreeNode
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
public string Label { get; set; } = "";
|
||||
public string Type { get; set; } = "";
|
||||
public int DeviceCount { get; set; }
|
||||
public List<RegionTreeNode> Children { get; set; } = new();
|
||||
[HttpGet, Route("/api/DeviceManager/GetDevicesByPoint")]
|
||||
public IActionResult GetDevicesByPoint() => Ok(new { items = new object[0], total = 0 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,275 +1,23 @@
|
||||
/*
|
||||
*网关节点管理 — A1注册/A2心跳/A3设备同步/A4告警同步
|
||||
*通过 Repository.DbContext 直接操作 SqlSugar
|
||||
*网关节点管理 — 骨架
|
||||
*TODO: Phase 2 联调时实现 A1注册/A2心跳/A3设备同步/A4告警同步
|
||||
*/
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using VolPro.Entity.DomainModels;
|
||||
using Warehouse.IServices;
|
||||
using Warehouse.IRepositories;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Warehouse.Controllers
|
||||
{
|
||||
public partial class gateway_nodesController
|
||||
{
|
||||
private readonly Igateway_nodesRepository _repo;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
[HttpPost, Route("/api/gateway/register")]
|
||||
public IActionResult RegisterGateway() => Ok(new { nodeId = 0, devices = new object[0] });
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public gateway_nodesController(
|
||||
Igateway_nodesService service,
|
||||
Igateway_nodesRepository repository,
|
||||
IHttpContextAccessor httpContextAccessor
|
||||
)
|
||||
: base(service)
|
||||
{
|
||||
_repo = repository;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
[HttpPost, Route("/api/gateway/heartbeat")]
|
||||
public IActionResult GatewayHeartbeat() => Ok(new { status = "ok" });
|
||||
|
||||
/// <summary>A1: 网关注册 (Upsert)</summary>
|
||||
[HttpPost]
|
||||
[Route("/api/gateway/register")]
|
||||
public async Task<IActionResult> RegisterGateway([FromBody] GatewayRegisterRequest req)
|
||||
{
|
||||
if (string.IsNullOrEmpty(req.NodeCode) || string.IsNullOrEmpty(req.Token))
|
||||
return BadRequest(new { message = "NodeCode and Token required" });
|
||||
[HttpPost, Route("/api/gateway/sync/devices")]
|
||||
public IActionResult SyncDevices() => Ok(new { added = 0, updated = 0, removed = 0 });
|
||||
|
||||
var existing = _repo.DbContext.Queryable<gateway_nodes>()
|
||||
.First(x => x.NodeCode == req.NodeCode);
|
||||
|
||||
gateway_nodes entity;
|
||||
if (existing != null)
|
||||
{
|
||||
if (existing.NodeToken != req.Token)
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
entity = new gateway_nodes
|
||||
{
|
||||
NodeCode = req.NodeCode,
|
||||
NodeName = req.NodeCode,
|
||||
NodeToken = req.Token,
|
||||
AdapterTypes = req.AdapterTypes,
|
||||
BaseUrl = req.BaseUrl,
|
||||
IsOnline = "在线",
|
||||
LastHeartbeat = DateTime.Now,
|
||||
Enable = "启用",
|
||||
CreateDate = DateTime.Now
|
||||
};
|
||||
_repo.DbContext.Insertable(entity).ExecuteCommand();
|
||||
}
|
||||
|
||||
// 返回当前网关的顶层设备
|
||||
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 });
|
||||
}
|
||||
|
||||
/// <summary>A2: 心跳</summary>
|
||||
[HttpPost]
|
||||
[Route("/api/gateway/heartbeat")]
|
||||
public async Task<IActionResult> GatewayHeartbeat([FromBody] GatewayHeartbeatRequest req)
|
||||
{
|
||||
var entity = _repo.DbContext.Queryable<gateway_nodes>()
|
||||
.First(x => x.NodeCode == req.NodeCode && x.NodeToken == req.Token);
|
||||
if (entity == null)
|
||||
return StatusCode(401, new { message = "认证失败" });
|
||||
|
||||
entity.IsOnline = "在线";
|
||||
entity.LastHeartbeat = DateTime.Now;
|
||||
_repo.DbContext.Updateable(entity).ExecuteCommand();
|
||||
|
||||
return Ok(new { status = "ok", serverTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") });
|
||||
}
|
||||
|
||||
/// <summary>A3: 设备数据同步(字段分治 + parentSourceId映射)</summary>
|
||||
[HttpPost]
|
||||
[Route("/api/gateway/sync/devices")]
|
||||
public async Task<IActionResult> SyncDevices([FromBody] SyncDevicesRequest req)
|
||||
{
|
||||
var node = _repo.DbContext.Queryable<gateway_nodes>()
|
||||
.First(x => x.NodeCode == req.NodeCode && x.NodeToken == req.Token);
|
||||
if (node == null) return StatusCode(401, new { message = "认证失败" });
|
||||
|
||||
var db = _repo.DbContext;
|
||||
|
||||
// 批量查询已有设备映射表
|
||||
var codes = req.Devices.Select(d => d.AdapterCode).Distinct().ToList();
|
||||
var existingIds = db.Queryable<base_device>()
|
||||
.Where(x => x.GatewayNodeId == node.NodeId && codes.Contains(x.AdapterCode!))
|
||||
.ToDictionary(x => (x.AdapterCode!, x.SourceId!), x => x.DeviceId);
|
||||
|
||||
int added = 0, updated = 0;
|
||||
foreach (var d in req.Devices)
|
||||
{
|
||||
bool isNew = !existingIds.ContainsKey((d.AdapterCode, d.SourceId));
|
||||
|
||||
// 解析 parentSourceId → ParentDeviceId
|
||||
int? parentDeviceId = null;
|
||||
if (!string.IsNullOrEmpty(d.ParentSourceId) && existingIds.TryGetValue((d.AdapterCode, d.ParentSourceId), out var pid))
|
||||
parentDeviceId = pid;
|
||||
|
||||
if (isNew)
|
||||
{
|
||||
var entity = new base_device
|
||||
{
|
||||
AdapterCode = d.AdapterCode,
|
||||
SourceId = d.SourceId,
|
||||
DeviceName = d.Name,
|
||||
DeviceCategory = d.Category,
|
||||
DeviceGroup = d.Group,
|
||||
GatewayNodeId = node.NodeId,
|
||||
IsParent = d.IsParent ? "是" : "否",
|
||||
ParentDeviceId = parentDeviceId,
|
||||
IsOnline = d.IsOnline ? "在线" : "离线",
|
||||
IpAddress = d.IpAddress,
|
||||
Port = d.Port,
|
||||
ExtraData = d.ExtraData != null
|
||||
? System.Text.Json.JsonSerializer.Serialize(d.ExtraData)
|
||||
: null,
|
||||
Enable = "启用",
|
||||
LastSyncTime = DateTime.Now,
|
||||
CreateDate = DateTime.Now
|
||||
};
|
||||
db.Insertable(entity).ExecuteCommand();
|
||||
added++;
|
||||
}
|
||||
else
|
||||
{
|
||||
var existingId = existingIds[(d.AdapterCode, d.SourceId)];
|
||||
var entity = db.Queryable<base_device>().InSingle(existingId);
|
||||
if (entity != null)
|
||||
{
|
||||
entity.IsOnline = d.IsOnline ? "在线" : "离线";
|
||||
entity.IsParent = d.IsParent ? "是" : "否";
|
||||
entity.ParentDeviceId = parentDeviceId ?? entity.ParentDeviceId;
|
||||
entity.IpAddress = d.IpAddress;
|
||||
entity.Port = d.Port;
|
||||
entity.ExtraData = d.ExtraData != null
|
||||
? System.Text.Json.JsonSerializer.Serialize(d.ExtraData)
|
||||
: entity.ExtraData;
|
||||
entity.LastSyncTime = DateTime.Now;
|
||||
db.Updateable(entity).ExecuteCommand();
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ok(new { added, updated, removed = 0 });
|
||||
}
|
||||
|
||||
/// <summary>A4: 告警同步(DeviceSourceId→DeviceId映射 + 去重)</summary>
|
||||
[HttpPost]
|
||||
[Route("/api/gateway/sync/alarms")]
|
||||
public async Task<IActionResult> SyncAlarms([FromBody] SyncAlarmsRequest req)
|
||||
{
|
||||
var node = _repo.DbContext.Queryable<gateway_nodes>()
|
||||
.First(x => x.NodeCode == req.NodeCode && x.NodeToken == req.Token);
|
||||
if (node == null) return StatusCode(401, new { message = "认证失败" });
|
||||
|
||||
var db = _repo.DbContext;
|
||||
|
||||
// 批量查 DeviceSourceId → DeviceId
|
||||
var srcIds = req.Alarms.Select(a => a.DeviceSourceId).ToList();
|
||||
var deviceMap = db.Queryable<base_device>()
|
||||
.Where(x => x.GatewayNodeId == node.NodeId && srcIds.Contains(x.SourceId!))
|
||||
.ToDictionary(x => x.SourceId!, x => x.DeviceId);
|
||||
|
||||
int added = 0;
|
||||
foreach (var a in req.Alarms)
|
||||
{
|
||||
if (db.Queryable<iot_alarm>().Any(x => x.SourceAlarmId == a.SourceAlarmId))
|
||||
continue;
|
||||
|
||||
deviceMap.TryGetValue(a.DeviceSourceId, out var deviceId);
|
||||
var alarm = new iot_alarm
|
||||
{
|
||||
SourceAlarmId = a.SourceAlarmId,
|
||||
DeviceId = deviceId > 0 ? deviceId : null,
|
||||
AdapterCode = a.AdapterCode,
|
||||
AlarmLevel = a.Level,
|
||||
AlarmDesc = a.Desc,
|
||||
AlarmValue = a.Value,
|
||||
StartTime = DateTime.TryParse(a.StartTime, out var st) ? st : DateTime.Now,
|
||||
State = "未确认",
|
||||
CreateDate = DateTime.Now
|
||||
};
|
||||
db.Insertable(alarm).ExecuteCommand();
|
||||
added++;
|
||||
}
|
||||
return Ok(new { added });
|
||||
}
|
||||
}
|
||||
|
||||
public class GatewayRegisterRequest
|
||||
{
|
||||
public string NodeCode { get; set; } = "";
|
||||
public string Token { get; set; } = "";
|
||||
public string AdapterTypes { get; set; } = "";
|
||||
public string BaseUrl { get; set; } = "";
|
||||
}
|
||||
|
||||
public class GatewayHeartbeatRequest
|
||||
{
|
||||
public string NodeCode { get; set; } = "";
|
||||
public string Token { get; set; } = "";
|
||||
}
|
||||
|
||||
public class SyncDevicesRequest
|
||||
{
|
||||
public string NodeCode { get; set; } = "";
|
||||
public string Token { get; set; } = "";
|
||||
public List<SyncDeviceItem> Devices { get; set; } = new();
|
||||
}
|
||||
|
||||
public class SyncDeviceItem
|
||||
{
|
||||
public string AdapterCode { get; set; } = "";
|
||||
public string SourceId { get; set; } = "";
|
||||
public string Name { get; set; } = "";
|
||||
public string Category { get; set; } = "";
|
||||
public string Group { get; set; } = "";
|
||||
public bool IsParent { get; set; }
|
||||
public string? ParentSourceId { get; set; }
|
||||
public bool IsOnline { get; set; }
|
||||
public string? IpAddress { get; set; }
|
||||
public int? Port { get; set; }
|
||||
public Dictionary<string, object?>? ExtraData { get; set; }
|
||||
}
|
||||
|
||||
public class SyncAlarmsRequest
|
||||
{
|
||||
public string NodeCode { get; set; } = "";
|
||||
public string Token { get; set; } = "";
|
||||
public List<SyncAlarmItem> Alarms { get; set; } = new();
|
||||
}
|
||||
|
||||
public class SyncAlarmItem
|
||||
{
|
||||
public string SourceAlarmId { get; set; } = "";
|
||||
public string DeviceSourceId { get; set; } = "";
|
||||
public string AdapterCode { get; set; } = "";
|
||||
public string Level { get; set; } = "";
|
||||
public string Desc { get; set; } = "";
|
||||
public double? Value { get; set; }
|
||||
public string StartTime { get; set; } = "";
|
||||
[HttpPost, Route("/api/gateway/sync/alarms")]
|
||||
public IActionResult SyncAlarms() => Ok(new { added = 0 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,13 @@
|
||||
using Quartz;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Warehouse.IRepositories;
|
||||
using VolPro.Entity.DomainModels;
|
||||
|
||||
namespace VolPro.Warehouse.Services;
|
||||
|
||||
public class HeartbeatMonitorJob : IJob
|
||||
{
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
var sp = (IServiceProvider)context.JobDetail.JobDataMap["ServiceProvider"];
|
||||
var gwRepo = sp.GetService<Igateway_nodesRepository>();
|
||||
var db = gwRepo.DbContext;
|
||||
var timeout = DateTime.Now.AddSeconds(-30);
|
||||
|
||||
var offlineNodes = db.Queryable<gateway_nodes>()
|
||||
.Where(x => x.IsOnline == "在线" && x.LastHeartbeat < timeout)
|
||||
.ToList();
|
||||
|
||||
foreach (var node in offlineNodes)
|
||||
{
|
||||
node.IsOnline = "离线";
|
||||
db.Updateable(node).ExecuteCommand();
|
||||
}
|
||||
// TODO: Phase 2 联调时实现 — 心跳超时检测
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,13 @@
|
||||
using Quartz;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Warehouse.IRepositories;
|
||||
using VolPro.Entity.DomainModels;
|
||||
|
||||
namespace VolPro.Warehouse.Services;
|
||||
|
||||
public class SyncDevicesJob : IJob
|
||||
{
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
var sp = (IServiceProvider)context.JobDetail.JobDataMap["ServiceProvider"];
|
||||
var gwRepo = sp.GetService<Igateway_nodesRepository>();
|
||||
var httpFactory = sp.GetService<IHttpClientFactory>();
|
||||
var db = gwRepo.DbContext;
|
||||
|
||||
var onlineNodes = db.Queryable<gateway_nodes>()
|
||||
.Where(x => x.IsOnline == "在线" && x.Enable == "启用" && x.BaseUrl != null)
|
||||
.ToList();
|
||||
|
||||
foreach (var node in onlineNodes)
|
||||
{
|
||||
try
|
||||
{
|
||||
var http = httpFactory.CreateClient();
|
||||
await http.PostAsync($"{node.BaseUrl}/api/gateway/devices/sync?adapter={node.AdapterTypes}", null);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
// TODO: Phase 2 联调时实现 — 定时设备同步
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,140 +1,7 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using VolPro.Entity.DomainModels;
|
||||
using Warehouse.IRepositories;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace VolPro.Warehouse.Services;
|
||||
|
||||
/// <summary>MC4.0 同步引擎:对象树 → 区域匹配 + 设备 Upsert</summary>
|
||||
/// <summary>TODO: Phase 2 联调时实现 — MC4对象树区域匹配+设备Upsert</summary>
|
||||
public class SyncEngine
|
||||
{
|
||||
private readonly IServiceProvider _sp;
|
||||
|
||||
public SyncEngine(IServiceProvider sp) => _sp = sp;
|
||||
|
||||
private Iwarehouse_regionsRepository GetRegionRepo() =>
|
||||
_sp.GetService<Iwarehouse_regionsRepository>()!;
|
||||
private Iwarehouse_devicepointRepository GetPointRepo() =>
|
||||
_sp.GetService<Iwarehouse_devicepointRepository>()!;
|
||||
private Ibase_deviceRepository GetDeviceRepo() =>
|
||||
_sp.GetService<Ibase_deviceRepository>()!;
|
||||
|
||||
public async Task<SyncStats> ProcessMc4TreeAsync(int gatewayNodeId, string adapterCode, List<Mc4TreeNode> tree)
|
||||
{
|
||||
var stats = new SyncStats();
|
||||
foreach (var node in tree)
|
||||
await ProcessNodeAsync(gatewayNodeId, adapterCode, node, null, stats);
|
||||
return stats;
|
||||
}
|
||||
|
||||
private async Task ProcessNodeAsync(int gatewayNodeId, string adapterCode,
|
||||
Mc4TreeNode node, int? parentDeviceId, SyncStats stats)
|
||||
{
|
||||
if (node.Type == 1)
|
||||
{
|
||||
int pointId = await MatchOrCreatePoint(node);
|
||||
foreach (var child in node.Children)
|
||||
await ProcessNodeAsync(gatewayNodeId, adapterCode, child, null, stats);
|
||||
}
|
||||
else if (node.Type == 2)
|
||||
{
|
||||
int deviceId = await UpsertDevice(gatewayNodeId, adapterCode, node, parentDeviceId);
|
||||
if (stats.DeviceIds.TryGetValue(node.Id, out _))
|
||||
stats.Updated++;
|
||||
else
|
||||
stats.Added++;
|
||||
stats.DeviceIds[node.Id] = deviceId;
|
||||
|
||||
foreach (var child in node.Children)
|
||||
await ProcessNodeAsync(gatewayNodeId, adapterCode, child, deviceId, stats);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> MatchOrCreatePoint(Mc4TreeNode node)
|
||||
{
|
||||
var regionRepo = GetRegionRepo();
|
||||
var regionDb = regionRepo.DbContext;
|
||||
|
||||
var region = regionDb.Queryable<warehouse_regions>()
|
||||
.First(x => x.RegionName == node.Name);
|
||||
if (region == null)
|
||||
{
|
||||
region = new warehouse_regions { RegionName = node.Name ?? $"MC4_{node.Id}" };
|
||||
regionDb.Insertable(region).ExecuteCommand();
|
||||
}
|
||||
|
||||
var pointRepo = GetPointRepo();
|
||||
var pointDb = pointRepo.DbContext;
|
||||
var point = pointDb.Queryable<warehouse_devicepoint>()
|
||||
.First(x => x.RegionId == region.Id);
|
||||
if (point == null)
|
||||
{
|
||||
point = new warehouse_devicepoint { PointName = node.Name ?? $"MC4_PT_{node.Id}", RegionId = region.Id };
|
||||
pointDb.Insertable(point).ExecuteCommand();
|
||||
}
|
||||
return point.PointID;
|
||||
}
|
||||
|
||||
private async Task<int> UpsertDevice(int gatewayNodeId, string adapterCode,
|
||||
Mc4TreeNode node, int? parentDeviceId)
|
||||
{
|
||||
var deviceRepo = GetDeviceRepo();
|
||||
var db = deviceRepo.DbContext;
|
||||
var sourceId = node.Id.ToString();
|
||||
|
||||
var existing = db.Queryable<base_device>()
|
||||
.First(x => x.AdapterCode == adapterCode && x.SourceId == sourceId);
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
existing.IsOnline = "在线";
|
||||
existing.LastSyncTime = DateTime.Now;
|
||||
existing.ParentDeviceId = parentDeviceId ?? existing.ParentDeviceId;
|
||||
if (node.Option != null)
|
||||
existing.ExtraData = JsonSerializer.Serialize(node.Option);
|
||||
db.Updateable(existing).ExecuteCommand();
|
||||
return existing.DeviceId;
|
||||
}
|
||||
|
||||
var device = new base_device
|
||||
{
|
||||
DeviceName = node.Name ?? $"MC4_DEV_{node.Id}",
|
||||
AdapterCode = adapterCode,
|
||||
SourceId = sourceId,
|
||||
DeviceCategory = MapCategory(node.ObjectType, node.Tag),
|
||||
DeviceGroup = "IoT设备",
|
||||
GatewayNodeId = gatewayNodeId,
|
||||
ParentDeviceId = parentDeviceId,
|
||||
IsParent = node.Children?.Count > 0 ? "是" : "否",
|
||||
IsOnline = "在线",
|
||||
Enable = "启用",
|
||||
LastSyncTime = DateTime.Now,
|
||||
CreateDate = DateTime.Now,
|
||||
ExtraData = node.Option != null ? JsonSerializer.Serialize(node.Option) : null
|
||||
};
|
||||
db.Insertable(device).ExecuteCommand();
|
||||
return device.DeviceId;
|
||||
}
|
||||
|
||||
private static string MapCategory(int objectType, string? tag) =>
|
||||
(objectType, tag) switch
|
||||
{
|
||||
(_, "温湿度") => "温湿度变送器",
|
||||
(_, "烟雾") => "烟雾报警器",
|
||||
(_, "气体") => "气体报警器",
|
||||
(_, "门磁") => "门磁",
|
||||
_ => "动环采集器"
|
||||
};
|
||||
}
|
||||
|
||||
public class SyncStats { public int Added; public int Updated; public Dictionary<int, int> DeviceIds = new(); }
|
||||
public class Mc4TreeNode
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public int Type { get; set; }
|
||||
public int ObjectType { get; set; }
|
||||
public string? Tag { get; set; }
|
||||
public Dictionary<string, object?>? Option { get; set; }
|
||||
public List<Mc4TreeNode> Children { get; set; } = new();
|
||||
public Task ProcessAsync() => Task.CompletedTask;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user