"第8天:同步引擎+区域匹配+设备Upsert+字段分治"

This commit is contained in:
2026-05-17 00:36:36 +08:00
parent 206eaad42d
commit b6d7e30b0d
44 changed files with 1877 additions and 7 deletions

View File

@@ -19,25 +19,39 @@ namespace Warehouse.Controllers
private readonly Igateway_nodesService _service;//访问业务代码
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly Ibase_deviceService _deviceService;
private readonly Iiot_alarmService _alarmService;
[ActivatorUtilitiesConstructor]
public gateway_nodesController(
Igateway_nodesService service,
IHttpContextAccessor httpContextAccessor
IHttpContextAccessor httpContextAccessor,
Ibase_deviceService deviceService,
Iiot_alarmService alarmService
)
: base(service)
{
_service = service;
_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
IHttpContextAccessor httpContextAccessor,
Ibase_deviceService deviceService,
Iiot_alarmService alarmService
)
: base(service)
{
_service = service;
_httpContextAccessor = httpContextAccessor;
_deviceService = deviceService;
_alarmService = alarmService;
}
/// <summary>A1: 网关注册 (Upsert)</summary>
@@ -105,7 +119,7 @@ namespace Warehouse.Controllers
return Ok(new { status = "ok", serverTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") });
}
/// <summary>A3: 设备数据同步</summary>
/// <summary>A3: 设备数据同步(字段分治 + parentSourceId映射</summary>
[HttpPost]
[Route("/api/gateway/sync/devices")]
public async Task<IActionResult> SyncDevices([FromBody] SyncDevicesRequest req)
@@ -114,10 +128,71 @@ namespace Warehouse.Controllers
.FirstOrDefaultAsync();
if (node == null) return StatusCode(401, new { message = "认证失败" });
// 批量查询已有设备映射表(用于 parentSourceId 解析)
var codes = req.Devices.Select(d => d.AdapterCode).Distinct().ToList();
var existingIds = await _deviceService
.FindAsIQueryable(x => codes.Contains(x.AdapterCode) && x.GatewayNodeId == node.NodeId)
.ToDictionaryAsync(x => (x.AdapterCode, x.SourceId), x => x.DeviceId);
int added = 0, updated = 0;
foreach (var d in req.Devices)
{
// delegate to base_device service
var key = (d.AdapterCode, d.SourceId);
existingIds.TryGetValue(key, out var existingId);
bool isNew = existingId == 0;
// 解析 parentSourceId → ParentDeviceId
int? parentDeviceId = null;
if (!string.IsNullOrEmpty(d.ParentSourceId))
{
existingIds.TryGetValue((d.AdapterCode, d.ParentSourceId), out var pid);
if (pid > 0) 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
};
await _deviceService.AddAsync(entity);
added++;
}
else
{
var entity = await _deviceService.FindAsIQueryable(x => x.DeviceId == existingId).FirstOrDefaultAsync();
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;
await _deviceService.UpdateAsync(entity);
updated++;
}
}
}
return Ok(new { added, updated, removed = 0 });
}
@@ -131,22 +206,36 @@ namespace Warehouse.Controllers
.FirstOrDefaultAsync();
if (node == null) return StatusCode(401, new { message = "认证失败" });
// 批量查出 DeviceSourceId → DeviceId 映射
var codes = req.Alarms.Select(a => a.AdapterCode).Distinct().ToList();
var srcIds = req.Alarms.Select(a => a.DeviceSourceId).ToList();
var deviceMap = await _deviceService
.FindAsIQueryable(x => codes.Contains(x.AdapterCode) && srcIds.Contains(x.SourceId))
.ToDictionaryAsync(x => (x.AdapterCode, x.SourceId), x => x.DeviceId);
int added = 0;
foreach (var a in req.Alarms)
{
// 跳过已存在的告警SourceAlarmId 去重)
var exists = await _alarmService
.FindAsIQueryable(x => x.SourceAlarmId == a.SourceAlarmId)
.AnyAsync();
if (exists) continue;
deviceMap.TryGetValue((a.AdapterCode, a.DeviceSourceId), out var deviceId);
var alarm = new iot_alarm
{
SourceAlarmId = a.SourceAlarmId,
DeviceId = null, // resolved later
DeviceId = deviceId > 0 ? deviceId : null,
AdapterCode = a.AdapterCode,
AlarmLevel = a.Level,
AlarmDesc = a.Desc,
AlarmValue = a.Value,
StartTime = DateTime.Parse(a.StartTime),
StartTime = DateTime.TryParse(a.StartTime, out var st) ? st : DateTime.Now,
State = "未确认",
CreateDate = DateTime.Now
};
await _service.ServiceProvider.GetService<IIoT_AlarmService>()?.AddAsync(alarm);
await _alarmService.AddAsync(alarm);
added++;
}
return Ok(new { added });

View File

@@ -0,0 +1,21 @@
/*
*代码由框架生成,任何更改都可能导致被代码生成器覆盖
*如果要增加方法请在当前目录下Partial文件夹base_deviceController编写
*/
using Microsoft.AspNetCore.Mvc;
using VolPro.Core.Controllers.Basic;
using VolPro.Entity.AttributeManager;
using Warehouse.IServices;
namespace Warehouse.Controllers
{
[Route("api/base_device")]
[PermissionTable(Name = "base_device")]
public partial class base_deviceController : ApiBaseController<Ibase_deviceService>
{
public base_deviceController(Ibase_deviceService service)
: base(service)
{
}
}
}

View File

@@ -0,0 +1,21 @@
/*
*代码由框架生成,任何更改都可能导致被代码生成器覆盖
*如果要增加方法请在当前目录下Partial文件夹gateway_nodesController编写
*/
using Microsoft.AspNetCore.Mvc;
using VolPro.Core.Controllers.Basic;
using VolPro.Entity.AttributeManager;
using Warehouse.IServices;
namespace Warehouse.Controllers
{
[Route("api/gateway_nodes")]
[PermissionTable(Name = "gateway_nodes")]
public partial class gateway_nodesController : ApiBaseController<Igateway_nodesService>
{
public gateway_nodesController(Igateway_nodesService service)
: base(service)
{
}
}
}

View File

@@ -0,0 +1,21 @@
/*
*代码由框架生成,任何更改都可能导致被代码生成器覆盖
*如果要增加方法请在当前目录下Partial文件夹iot_alarmController编写
*/
using Microsoft.AspNetCore.Mvc;
using VolPro.Core.Controllers.Basic;
using VolPro.Entity.AttributeManager;
using Warehouse.IServices;
namespace Warehouse.Controllers
{
[Route("api/iot_alarm")]
[PermissionTable(Name = "iot_alarm")]
public partial class iot_alarmController : ApiBaseController<Iiot_alarmService>
{
public iot_alarmController(Iiot_alarmService service)
: base(service)
{
}
}
}

View File

@@ -0,0 +1,21 @@
/*
*代码由框架生成,任何更改都可能导致被代码生成器覆盖
*如果要增加方法请在当前目录下Partial文件夹iot_devicedataController编写
*/
using Microsoft.AspNetCore.Mvc;
using VolPro.Core.Controllers.Basic;
using VolPro.Entity.AttributeManager;
using Warehouse.IServices;
namespace Warehouse.Controllers
{
[Route("api/iot_devicedata")]
[PermissionTable(Name = "iot_devicedata")]
public partial class iot_devicedataController : ApiBaseController<Iiot_devicedataService>
{
public iot_devicedataController(Iiot_devicedataService service)
: base(service)
{
}
}
}

View File

@@ -0,0 +1,21 @@
/*
*代码由框架生成,任何更改都可能导致被代码生成器覆盖
*如果要增加方法请在当前目录下Partial文件夹video_channelController编写
*/
using Microsoft.AspNetCore.Mvc;
using VolPro.Core.Controllers.Basic;
using VolPro.Entity.AttributeManager;
using Warehouse.IServices;
namespace Warehouse.Controllers
{
[Route("api/video_channel")]
[PermissionTable(Name = "video_channel")]
public partial class video_channelController : ApiBaseController<Ivideo_channelService>
{
public video_channelController(Ivideo_channelService service)
: base(service)
{
}
}
}

View File

@@ -0,0 +1,21 @@
/*
*代码由框架生成,任何更改都可能导致被代码生成器覆盖
*如果要增加方法请在当前目录下Partial文件夹video_recordController编写
*/
using Microsoft.AspNetCore.Mvc;
using VolPro.Core.Controllers.Basic;
using VolPro.Entity.AttributeManager;
using Warehouse.IServices;
namespace Warehouse.Controllers
{
[Route("api/video_record")]
[PermissionTable(Name = "video_record")]
public partial class video_recordController : ApiBaseController<Ivideo_recordService>
{
public video_recordController(Ivideo_recordService service)
: base(service)
{
}
}
}