/* *网关节点管理 — A1注册/A2心跳/A3设备同步/A4告警同步 *A组接口使用 [AllowAnonymous] + NodeToken 二次认证 *所有改动在 Partial 目录,不破坏框架可升级性 */ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using VolPro.Core.DBManager; using VolPro.Core.DbSqlSugar; using VolPro.Entity.DomainModels; using Warehouse.IRepositories; using Warehouse.IServices; using Warehouse.Services; namespace Warehouse.Controllers { public partial class gateway_nodesController { private readonly Igateway_nodesService _service;//访问业务代码 private readonly Ibase_deviceService _deviceService; private readonly Iiot_alarmService _iot_alarmService; private readonly IHttpContextAccessor _httpContextAccessor; [ActivatorUtilitiesConstructor] public gateway_nodesController( Igateway_nodesService service, Ibase_deviceService deviceService, Iiot_alarmService iot_alarmService, IHttpContextAccessor httpContextAccessor ) : base(service) { _service = service; _deviceService = deviceService; _iot_alarmService = iot_alarmService; _httpContextAccessor = httpContextAccessor; } /// A1: 网关注册(Upsert)。认证方式: NodeToken [HttpPost] [Route("/api/gateway/register")] [AllowAnonymous] public async Task RegisterGateway([FromBody] GatewayRegisterRequest req) { if (string.IsNullOrEmpty(req.NodeCode) || string.IsNullOrEmpty(req.Token)) return BadRequest(new { message = "NodeCode 和 Token 为必填项" }); try { var node = await _service.RegisterNodeAsync(req.NodeCode, req.Token, req.AdapterTypes, req.BaseUrl); // 返回当前网关的顶层设备列表 var devices = await _deviceService.GetDevicesByGatewayNodeAsync(node.NodeId); return Ok(new { nodeId = node.NodeId, devices = devices.Select(d => new { d.DeviceId, d.DeviceName, d.AdapterCode, d.SourceId, d.DeviceCategory, d.DeviceGroup, d.IsParent, d.IsOnline, d.ExtraData }) }); } catch (UnauthorizedAccessException) { return StatusCode(401, new { message = "认证失败:Token 无效" }); } } /// A2: 心跳。认证方式: NodeToken。每15秒调用一次。 [HttpPost] [Route("/api/gateway/heartbeat")] [AllowAnonymous] public async Task GatewayHeartbeat([FromBody] GatewayHeartbeatRequest req) { if (string.IsNullOrEmpty(req.NodeCode) || string.IsNullOrEmpty(req.Token)) return BadRequest(new { message = "NodeCode 和 Token 为必填项" }); try { await _service.UpdateHeartbeatAsync(req.NodeCode, req.Token); return Ok(new { status = "ok", serverTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") }); } catch (UnauthorizedAccessException) { return StatusCode(401, new { message = "认证失败" }); } } /// A3: 设备数据同步(字段分治 + parentSourceId 映射)。认证方式: NodeToken [HttpPost] [Route("/api/gateway/sync/devices")] [AllowAnonymous] public async Task SyncDevices([FromBody] SyncDevicesRequest req) { if (string.IsNullOrEmpty(req.NodeCode) || string.IsNullOrEmpty(req.Token)) return BadRequest(new { message = "NodeCode 和 Token 为必填项" }); try { // 认证 var node = await _service.FindAsIQueryable(x => x.NodeCode == req.NodeCode && x.NodeToken == req.Token) .FirstOrDefaultAsync(); if (node == null) return StatusCode(401, new { message = "认证失败" }); var items = req.Devices.Select(d => new SyncDeviceItem { AdapterCode = d.AdapterCode, SourceId = d.SourceId, Name = d.Name, Category = d.Category, Group = d.Group, IsParent = d.IsParent, ParentSourceId = d.ParentSourceId, IsOnline = d.IsOnline, IpAddress = d.IpAddress, Port = d.Port, ExtraDataJson = d.ExtraDataJson }).ToList(); var (added, updated) = await _service.SyncDevicesAsync(node.NodeId, items); return Ok(new { added, updated, removed = 0 }); } catch (UnauthorizedAccessException) { return StatusCode(401, new { message = "认证失败" }); } } /// A4: 告警同步(DeviceSourceId→DeviceId 映射 + SourceAlarmId 去重)。认证方式: NodeToken [HttpPost] [Route("/api/gateway/sync/alarms")] [AllowAnonymous] public async Task SyncAlarms([FromBody] SyncAlarmsRequest req) { if (string.IsNullOrEmpty(req.NodeCode) || string.IsNullOrEmpty(req.Token)) return BadRequest(new { message = "NodeCode 和 Token 为必填项" }); try { var node = await _service.FindAsIQueryable(x => x.NodeCode == req.NodeCode && x.NodeToken == req.Token) .FirstOrDefaultAsync(); if (node == null) return StatusCode(401, new { message = "认证失败" }); // 获取告警服务 var alarmSvc = _iot_alarmService; // 批量查询 DeviceSourceId → DeviceId 映射 var deviceSvc = _deviceService; int added = 0; foreach (var a in req.Alarms) { int? deviceId = null; if (deviceSvc != null) { var dev = await deviceSvc.FindAsIQueryable( x => x.AdapterCode == a.AdapterCode && x.SourceId == a.DeviceSourceId) .Select(x => new { x.DeviceId }) .FirstOrDefaultAsync(); deviceId = dev?.DeviceId; } if (alarmSvc != null) { var alarmItem = new SyncAlarmItem { SourceAlarmId = a.SourceAlarmId, DeviceSourceId = a.DeviceSourceId, AdapterCode = a.AdapterCode, Level = a.Level, Desc = a.Desc, Value = a.Value, StartTime = a.StartTime }; await alarmSvc.UpsertAlarmAsync(alarmItem, deviceId); added++; } } return Ok(new { added }); } catch (UnauthorizedAccessException) { return StatusCode(401, new { message = "认证失败" }); } } } // ── A 组请求 DTO ── 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 SyncDeviceItemDto { 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; } } public class SyncAlarmsRequest { public string NodeCode { get; set; } = ""; public string Token { get; set; } = ""; public List Alarms { get; set; } = new(); } public class SyncAlarmItemDto { 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; } = ""; } }