247 lines
9.4 KiB
C#
247 lines
9.4 KiB
C#
/*
|
||
*网关节点管理 — 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;
|
||
}
|
||
|
||
/// <summary>A1: 网关注册(Upsert)。认证方式: NodeToken</summary>
|
||
[HttpPost]
|
||
[Route("/api/gateway/register")]
|
||
[AllowAnonymous]
|
||
public async Task<IActionResult> 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 无效" });
|
||
}
|
||
}
|
||
|
||
/// <summary>A2: 心跳。认证方式: NodeToken。每15秒调用一次。</summary>
|
||
[HttpPost]
|
||
[Route("/api/gateway/heartbeat")]
|
||
[AllowAnonymous]
|
||
public async Task<IActionResult> 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 = "认证失败" });
|
||
}
|
||
}
|
||
|
||
/// <summary>A3: 设备数据同步(字段分治 + parentSourceId 映射)。认证方式: NodeToken</summary>
|
||
[HttpPost]
|
||
[Route("/api/gateway/sync/devices")]
|
||
[AllowAnonymous]
|
||
public async Task<IActionResult> 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 = "认证失败" });
|
||
}
|
||
}
|
||
|
||
/// <summary>A4: 告警同步(DeviceSourceId→DeviceId 映射 + SourceAlarmId 去重)。认证方式: NodeToken</summary>
|
||
[HttpPost]
|
||
[Route("/api/gateway/sync/alarms")]
|
||
[AllowAnonymous]
|
||
public async Task<IActionResult> 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<SyncDeviceItemDto> 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<SyncAlarmItemDto> 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; } = "";
|
||
}
|
||
}
|