From b63915daa0acef04d039d18d58827b68b0773c56 Mon Sep 17 00:00:00 2001 From: g82tt Date: Sun, 17 May 2026 05:30:37 +0800 Subject: [PATCH] =?UTF-8?q?V1=20Entity+Service=E6=89=A9=E5=B1=95:=20?= =?UTF-8?q?=E7=BD=91=E5=85=B3=E5=90=8C=E6=AD=A5=E6=96=B9=E6=B3=95+?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=88=86=E6=B2=BB+=E5=91=8A=E8=AD=A6?= =?UTF-8?q?=E5=8E=BB=E9=87=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Partial/base_deviceService.cs | 77 ++++++++- .../Partial/gateway_nodesService.cs | 157 +++++++++++++++++- .../Partial/iot_alarmService.cs | 43 ++++- 3 files changed, 274 insertions(+), 3 deletions(-) diff --git a/api_sqlsugar/Warehouse/Services/device_manager/Partial/base_deviceService.cs b/api_sqlsugar/Warehouse/Services/device_manager/Partial/base_deviceService.cs index 65367d0..17e2fce 100644 --- a/api_sqlsugar/Warehouse/Services/device_manager/Partial/base_deviceService.cs +++ b/api_sqlsugar/Warehouse/Services/device_manager/Partial/base_deviceService.cs @@ -17,6 +17,9 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Http; using Warehouse.IRepositories; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; namespace Warehouse.Services { @@ -37,5 +40,77 @@ namespace Warehouse.Services //多租户会用到这init代码,其他情况可以不用 //base.Init(dbRepository); } - } + + /// + /// 获取指定网关节点下的顶层设备列表(用于网关注册时返回所管设备)。 + /// 返回 ParentDeviceId 为 null 的设备。 + /// + public async Task> GetDevicesByGatewayNodeAsync(int gatewayNodeId) + { + return await _repository.DbContext.Queryable() + .Where(x => x.GatewayNodeId == gatewayNodeId && x.ParentDeviceId == null) + .ToListAsync(); + } + + /// + /// 按字段分治原则 Upsert 单个设备。 + /// 首次入库时写全量(管理员字段),已有记录时仅更新网关字段(IsOnline/ExtraData等)。 + /// + /// 同步设备条目 + /// 网关节点ID + /// 已有设备映射表 (AdapterCode, SourceId) → DeviceId + public async Task UpsertDeviceAsync(SyncDeviceItem d, int gatewayNodeId, Dictionary<(string, string), int> existingIds) + { + var db = _repository.DbContext; + var key = (d.AdapterCode, d.SourceId); + existingIds.TryGetValue(key, out var existingId); + bool isNew = existingId == 0; + + // 解析父设备 + 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 + { + DeviceName = d.Name ?? $"DEV_{d.SourceId}", + AdapterCode = d.AdapterCode, + SourceId = d.SourceId, + DeviceCategory = d.Category, + DeviceGroup = d.Group, + GatewayNodeId = gatewayNodeId, + IsParent = d.IsParent ? "是" : "否", + ParentDeviceId = parentDeviceId, + IsOnline = d.IsOnline ? "在线" : "离线", + IpAddress = d.IpAddress, + Port = d.Port, + ExtraData = d.ExtraDataJson, + Enable = "启用", + LastSyncTime = DateTime.Now, + CreateDate = DateTime.Now + }; + db.Insertable(entity).ExecuteCommand(); + } + else + { + var entity = db.Queryable().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.ExtraDataJson ?? entity.ExtraData; + entity.LastSyncTime = DateTime.Now; + db.Updateable(entity).ExecuteCommand(); + } + } + } + } } diff --git a/api_sqlsugar/Warehouse/Services/device_manager/Partial/gateway_nodesService.cs b/api_sqlsugar/Warehouse/Services/device_manager/Partial/gateway_nodesService.cs index 24e8533..6f60bea 100644 --- a/api_sqlsugar/Warehouse/Services/device_manager/Partial/gateway_nodesService.cs +++ b/api_sqlsugar/Warehouse/Services/device_manager/Partial/gateway_nodesService.cs @@ -17,6 +17,10 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Http; using Warehouse.IRepositories; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Text.Json; namespace Warehouse.Services { @@ -37,5 +41,156 @@ namespace Warehouse.Services //多租户会用到这init代码,其他情况可以不用 //base.Init(dbRepository); } - } + + /// + /// 网关注册(Upsert)。 + /// NodeCode 匹配则更新适配器类型/地址/在线状态并返回已有 NodeId, + /// NodeCode 不匹配且 Token 验证通过则插入新记录。 + /// + public async Task RegisterNodeAsync(string nodeCode, string token, string adapterTypes, string baseUrl) + { + var existing = _repository.DbContext.Queryable() + .First(x => x.NodeCode == nodeCode); + + gateway_nodes entity; + if (existing != null) + { + // 已存在:验证Token,更新网关上报字段 + if (existing.NodeToken != token) + throw new UnauthorizedAccessException("NodeToken 不匹配"); + + existing.AdapterTypes = adapterTypes; + existing.BaseUrl = baseUrl; + existing.IsOnline = "在线"; + existing.LastHeartbeat = DateTime.Now; + _repository.DbContext.Updateable(existing).ExecuteCommand(); + entity = existing; + } + else + { + // 新节点:直接插入 + entity = new gateway_nodes + { + NodeCode = nodeCode, + NodeName = nodeCode, + NodeToken = token, + AdapterTypes = adapterTypes, + BaseUrl = baseUrl, + IsOnline = "在线", + Enable = "启用", + LastHeartbeat = DateTime.Now, + CreateDate = DateTime.Now + }; + _repository.DbContext.Insertable(entity).ExecuteCommand(); + } + return entity; + } + + /// + /// 心跳更新。更新 LastHeartbeat 并标记在线。 + /// + public async Task UpdateHeartbeatAsync(string nodeCode, string token) + { + var entity = _repository.DbContext.Queryable() + .First(x => x.NodeCode == nodeCode && x.NodeToken == token); + if (entity == null) + throw new UnauthorizedAccessException("认证失败:NodeCode 或 Token 无效"); + + entity.IsOnline = "在线"; + entity.LastHeartbeat = DateTime.Now; + _repository.DbContext.Updateable(entity).ExecuteCommand(); + } + + /// + /// 设备数据同步。按照字段分治原则写入 base_device: + /// 首次入库写全量,后续仅更新网关字段(IsOnline/ExtraData/ParentDeviceId等)。 + /// parentSourceId 解析为 ParentDeviceId。 + /// + public async Task<(int added, int updated)> SyncDevicesAsync(int gatewayNodeId, List devices) + { + var db = _repository.DbContext; + + // 批量查询已有设备映射表(用于 parentSourceId → ParentDeviceId 解析) + var adapterCodes = devices.Select(d => d.AdapterCode).Distinct().ToList(); + var existingIds = db.Queryable() + .Where(x => x.GatewayNodeId == gatewayNodeId && adapterCodes.Contains(x.AdapterCode)) + .ToList() + .ToDictionary(x => (x.AdapterCode, x.SourceId), x => x.DeviceId); + + int added = 0, updated = 0; + foreach (var d in devices) + { + 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 + { + DeviceName = d.Name ?? $"DEV_{d.SourceId}", + AdapterCode = d.AdapterCode, + SourceId = d.SourceId, + DeviceCategory = d.Category, + DeviceGroup = d.Group, + GatewayNodeId = gatewayNodeId, + IsParent = d.IsParent ? "是" : "否", + ParentDeviceId = parentDeviceId, + IsOnline = d.IsOnline ? "在线" : "离线", + IpAddress = d.IpAddress, + Port = d.Port, + ExtraData = d.ExtraDataJson, + Enable = "启用", + LastSyncTime = DateTime.Now, + CreateDate = DateTime.Now + }; + db.Insertable(entity).ExecuteCommand(); + added++; + } + else + { + // 已有记录:仅更新网关字段 + var entity = db.Queryable().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.ExtraDataJson ?? entity.ExtraData; + entity.LastSyncTime = DateTime.Now; + db.Updateable(entity).ExecuteCommand(); + updated++; + } + } + } + return (added, updated); + } + } + + /// 网关同步设备条目(A3 接口接收的数据模型) + 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 string? ExtraDataJson { get; set; } + } } diff --git a/api_sqlsugar/Warehouse/Services/device_manager/Partial/iot_alarmService.cs b/api_sqlsugar/Warehouse/Services/device_manager/Partial/iot_alarmService.cs index c21b297..b1b1855 100644 --- a/api_sqlsugar/Warehouse/Services/device_manager/Partial/iot_alarmService.cs +++ b/api_sqlsugar/Warehouse/Services/device_manager/Partial/iot_alarmService.cs @@ -17,6 +17,8 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Http; using Warehouse.IRepositories; +using System; +using System.Threading.Tasks; namespace Warehouse.Services { @@ -37,5 +39,44 @@ namespace Warehouse.Services //多租户会用到这init代码,其他情况可以不用 //base.Init(dbRepository); } - } + + /// + /// Upsert 单条告警。按 SourceAlarmId 去重,已存在则跳过。 + /// + public async Task UpsertAlarmAsync(SyncAlarmItem a, int? deviceId) + { + var db = _repository.DbContext; + + // SourceAlarmId 去重 + var exists = db.Queryable() + .Any(x => x.SourceAlarmId == a.SourceAlarmId); + if (exists) return; + + var alarm = new iot_alarm + { + SourceAlarmId = a.SourceAlarmId, + DeviceId = deviceId, + 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(); + } + } + + /// 告警同步条目(A4 接口接收的数据模型) + 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; } = ""; + } }