/* *网关节点管理 — A1注册/A2心跳/A3设备同步/A4告警同步 *通过 Repository.DbContext 直接操作 SqlSugar */ 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; [ActivatorUtilitiesConstructor] public gateway_nodesController( Igateway_nodesService service, Igateway_nodesRepository repository, IHttpContextAccessor httpContextAccessor ) : base(service) { _repo = repository; _httpContextAccessor = httpContextAccessor; } /// A1: 网关注册 (Upsert) [HttpPost] [Route("/api/gateway/register")] public async Task RegisterGateway([FromBody] GatewayRegisterRequest req) { if (string.IsNullOrEmpty(req.NodeCode) || string.IsNullOrEmpty(req.Token)) return BadRequest(new { message = "NodeCode and Token required" }); var existing = _repo.DbContext.Queryable() .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() .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 }); } /// A2: 心跳 [HttpPost] [Route("/api/gateway/heartbeat")] public async Task GatewayHeartbeat([FromBody] GatewayHeartbeatRequest req) { var entity = _repo.DbContext.Queryable() .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") }); } /// A3: 设备数据同步(字段分治 + parentSourceId映射) [HttpPost] [Route("/api/gateway/sync/devices")] public async Task SyncDevices([FromBody] SyncDevicesRequest req) { var node = _repo.DbContext.Queryable() .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() .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().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 }); } /// A4: 告警同步(DeviceSourceId→DeviceId映射 + 去重) [HttpPost] [Route("/api/gateway/sync/alarms")] public async Task SyncAlarms([FromBody] SyncAlarmsRequest req) { var node = _repo.DbContext.Queryable() .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() .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().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 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? ExtraData { get; set; } } public class SyncAlarmsRequest { public string NodeCode { get; set; } = ""; public string Token { get; set; } = ""; public List 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; } = ""; } }