V4完成: 预览+云台整合 操作列JSX按钮 全链路调通

This commit is contained in:
2026-05-17 14:22:46 +08:00
parent f118e8b245
commit f9eeeed843
13 changed files with 128 additions and 85 deletions

View File

@@ -41,13 +41,10 @@ namespace VolPro.Entity.DomainModels
/// <summary>
///来源适配器(类型:实例)
/// </summary>
[SugarColumn(IsPrimaryKey = true)]
[Key]
[Display(Name ="来源适配器(类型:实例)")]
[MaxLength(50)]
[Column(TypeName="nvarchar(50)")]
[Editable(true)]
[Required(AllowEmptyStrings=false)]
public string AdapterCode { get; set; }
/// <summary>
@@ -57,7 +54,6 @@ namespace VolPro.Entity.DomainModels
[MaxLength(100)]
[Column(TypeName="nvarchar(100)")]
[Editable(true)]
[Required(AllowEmptyStrings=false)]
public string SourceId { get; set; }
/// <summary>
@@ -73,8 +69,6 @@ namespace VolPro.Entity.DomainModels
/// <summary>
///设备分组(数据字典)
/// </summary>
[SugarColumn(IsPrimaryKey = true)]
[Key]
[Display(Name ="设备分组(数据字典)")]
[MaxLength(20)]
[Column(TypeName="nvarchar(20)")]
@@ -85,23 +79,11 @@ namespace VolPro.Entity.DomainModels
/// <summary>
///所属点位ID
/// </summary>
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
[Key]
[Display(Name ="所属点位ID")]
[Column(TypeName="int")]
[Editable(true)]
public int? PointId { get; set; }
/// <summary>
///所属网关节点ID
/// </summary>
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
[Key]
[Display(Name ="所属网关节点ID")]
[Column(TypeName="int")]
[Editable(true)]
public int? GatewayNodeId { get; set; }
/// <summary>
///是否父设备(数据字典)
/// </summary>
@@ -115,8 +97,6 @@ namespace VolPro.Entity.DomainModels
/// <summary>
///父设备ID(自引用,子设备挂父设备下)
/// </summary>
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
[Key]
[Display(Name ="父设备ID(自引用,子设备挂父设备下)")]
[Column(TypeName="int")]
[Editable(true)]
@@ -129,7 +109,6 @@ namespace VolPro.Entity.DomainModels
[MaxLength(20)]
[Column(TypeName="nvarchar(20)")]
[Editable(true)]
[Required(AllowEmptyStrings=false)]
public string IsOnline { get; set; }
/// <summary>
@@ -284,6 +263,16 @@ namespace VolPro.Entity.DomainModels
[Editable(true)]
public DateTime? ModifyDate { get; set; }
/// <summary>
///所属网关节点ID
/// </summary>
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
[Key]
[Display(Name ="所属网关节点ID")]
[Column(TypeName="int")]
[Editable(true)]
public int? NodeId { get; set; }
[Display(Name ="视频通道")]
[ForeignKey("DeviceId")][Navigate(NavigateType.OneToMany,nameof(DeviceId),nameof(DeviceId))]
public List<video_channel> video_channel { get; set; }

View File

@@ -14,7 +14,7 @@ using VolPro.Entity.SystemModels;
namespace VolPro.Entity.DomainModels
{
[Entity(TableCnName = "设备管理_网关节点",TableName = "gateway_nodes",DBServer = "ServiceDbContext")]
[Entity(TableCnName = "设备管理_网关节点",TableName = "gateway_nodes",DetailTable = new Type[] { typeof(base_device)},DetailTableCnName = "设备管理",DBServer = "ServiceDbContext")]
public partial class gateway_nodes:ServiceEntity
{
/// <summary>
@@ -165,6 +165,11 @@ namespace VolPro.Entity.DomainModels
[Editable(true)]
public DateTime? ModifyDate { get; set; }
[Display(Name ="设备管理")]
[ForeignKey("NodeId")][Navigate(NavigateType.OneToMany,nameof(NodeId),nameof(NodeId))]
public List<base_device> base_device { get; set; }
}
}

View File

@@ -11,17 +11,17 @@ namespace VolPro.Entity.DomainModels
{
public partial class base_device
{
/// <summary>导航属性:关联视频通道扩展记录(一对一)</summary>
[Navigate(NavigateType.OneToOne, nameof(DeviceId), nameof(video_channel.DeviceId))]
public video_channel? VideoChannel { get; set; }
/////// <summary>导航属性:关联视频通道扩展记录(一对一)</summary>
////[Navigate(NavigateType.OneToOne, nameof(DeviceId), nameof(video_channel.DeviceId))]
////public video_channel? VideoChannel { get; set; }
/// <summary>导航属性:关联告警记录(一对多)</summary>
[Navigate(NavigateType.OneToMany, nameof(DeviceId), nameof(iot_alarm.DeviceId))]
public List<iot_alarm>? Alarms { get; set; }
/////// <summary>导航属性:关联告警记录(一对多)</summary>
////[Navigate(NavigateType.OneToMany, nameof(DeviceId), nameof(iot_alarm.DeviceId))]
////public List<iot_alarm>? Alarms { get; set; }
/// <summary>导航属性:关联数据归档(一对多)</summary>
[Navigate(NavigateType.OneToMany, nameof(DeviceId), nameof(iot_devicedata.DeviceId))]
public List<iot_devicedata>? DeviceData { get; set; }
/////// <summary>导航属性:关联数据归档(一对多)</summary>
////[Navigate(NavigateType.OneToMany, nameof(DeviceId), nameof(iot_devicedata.DeviceId))]
////public List<iot_devicedata>? DeviceData { get; set; }
/// <summary>
/// 网关字段白名单。网关同步时,只有此集合中的字段会被覆盖,

View File

@@ -17,6 +17,26 @@ namespace Warehouse.Controllers
{
public partial class base_deviceController
{
private readonly Ibase_deviceService _service;//访问业务代码
private readonly Iwarehouse_regionsService _regionsService;
private readonly Iwarehouse_devicepointService _pointService;
private readonly IHttpContextAccessor _httpContextAccessor;
[ActivatorUtilitiesConstructor]
public base_deviceController(
Ibase_deviceService service,
Iwarehouse_regionsService regionsService,
Iwarehouse_devicepointService pointService,
IHttpContextAccessor httpContextAccessor
)
: base(service)
{
_service = service;
_regionsService = regionsService;
_pointService = pointService;
_httpContextAccessor = httpContextAccessor;
}
/// <summary>
/// 获取区域→点位→设备树。
/// 用于管理端左侧树形控件展示层级结构。
@@ -26,32 +46,27 @@ namespace Warehouse.Controllers
[Route("/api/DeviceManager/GetRegionTree")]
public async Task<IActionResult> GetRegionTree()
{
// 获取区域和点位服务
var regionSvcType = _service.GetType().Assembly.GetType("Warehouse.Services.warehouse_regionsService");
var pointSvcType = _service.GetType().Assembly.GetType("Warehouse.Services.warehouse_devicepointService");
var regionSvc = regionSvcType?.GetProperty("Instance")?.GetValue(null) as IService<warehouse_regions>;
var pointSvc = pointSvcType?.GetProperty("Instance")?.GetValue(null) as IService<warehouse_devicepoint>;
// 查所有区域
var regions = regionSvc != null
? await regionSvc.FindAsIQueryable(x => true).ToListAsync()
: new List<warehouse_regions>();
var regions = await _regionsService.FindAsIQueryable(x => true).ToListAsync();
// 查所有点位
var points = pointSvc != null
? await pointSvc.FindAsIQueryable(x => true).ToListAsync()
: new List<warehouse_devicepoint>();
var points = await _pointService.FindAsIQueryable(x => true).ToListAsync();
// 统计每个点位下的设备数量
//var deviceCounts = new Dictionary<int, int>();
//var allDevices = await _service.FindAsIQueryable(x => true)
// .Where(x => x.PointId != null)
// .GroupBy(x => x.PointId!.Value)
// .Select(g => new { PointId = g.Key, Count = g.Count() })
// .ToListAsync();
var deviceCounts = new Dictionary<int, int>();
var allDevices = await _service.FindAsIQueryable(x => true)
.Where(x => x.PointId != null)
.GroupBy(x => x.PointId!.Value)
.Select(g => new { PointId = g.Key, Count = g.Count() })
var devices = await _service.FindAsIQueryable(x => x.PointId != null)
.Select(x => new { x.PointId })
.ToListAsync();
foreach (var g in allDevices)
deviceCounts[g.PointId] = g.Count;
deviceCounts = devices
.Where(x => x.PointId.HasValue)
.GroupBy(x => x.PointId!.Value)
.ToDictionary(g => g.Key, g => g.Count());
// 构建树形结构
var tree = new List<object>();

View File

@@ -3,21 +3,45 @@
*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.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Http;
using VolPro.Entity.DomainModels;
using Warehouse.IServices;
using System.Linq;
using Microsoft.EntityFrameworkCore;
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")]
@@ -32,11 +56,7 @@ namespace Warehouse.Controllers
var node = await _service.RegisterNodeAsync(req.NodeCode, req.Token, req.AdapterTypes, req.BaseUrl);
// 返回当前网关的顶层设备列表
var deviceSvc = _service.GetType().Assembly.GetType("Warehouse.Services.base_deviceService")
?.GetProperty("Instance")?.GetValue(null) as Ibase_deviceService;
var devices = deviceSvc != null
? await deviceSvc.GetDevicesByGatewayNodeAsync(node.NodeId)
: new List<base_device>();
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,
@@ -125,12 +145,10 @@ namespace Warehouse.Controllers
if (node == null) return StatusCode(401, new { message = "认证失败" });
// 获取告警服务
var alarmSvcType = _service.GetType().Assembly.GetType("Warehouse.Services.iot_alarmService");
var alarmSvc = alarmSvcType?.GetProperty("Instance")?.GetValue(null) as Iiot_alarmService;
var alarmSvc = _iot_alarmService;
// 批量查询 DeviceSourceId → DeviceId 映射
var deviceSvcType = _service.GetType().Assembly.GetType("Warehouse.Services.base_deviceService");
var deviceSvc = deviceSvcType?.GetProperty("Instance")?.GetValue(null) as Ibase_deviceService;
var deviceSvc = _deviceService;
int added = 0;
foreach (var a in req.Alarms)

View File

@@ -1,13 +1,19 @@
/*
*所有关于base_device类的业务代码接口应在此处编写
*/
using VolPro.Core.BaseProvider;
using VolPro.Entity.DomainModels;
using VolPro.Core.Utilities;
using System.Linq.Expressions;
using VolPro.Core.BaseProvider;
using VolPro.Core.Utilities;
using VolPro.Entity.DomainModels;
using Warehouse.Services;
namespace Warehouse.IServices
{
public partial interface Ibase_deviceService
{
Task<List<base_device>> GetDevicesByGatewayNodeAsync(int gatewayNodeId);
Task UpsertDeviceAsync(SyncDeviceItem d, int gatewayNodeId, Dictionary<(string, string), int> existingIds);
}
}

View File

@@ -1,13 +1,19 @@
/*
*所有关于gateway_nodes类的业务代码接口应在此处编写
*/
using VolPro.Core.BaseProvider;
using VolPro.Entity.DomainModels;
using VolPro.Core.Utilities;
using System.Linq.Expressions;
using VolPro.Core.BaseProvider;
using VolPro.Core.Utilities;
using VolPro.Entity.DomainModels;
using Warehouse.Services;
namespace Warehouse.IServices
{
public partial interface Igateway_nodesService
{
Task<gateway_nodes> RegisterNodeAsync(string nodeCode, string token, string adapterTypes, string baseUrl);
Task UpdateHeartbeatAsync(string nodeCode, string token);
Task<(int added, int updated)> SyncDevicesAsync(int gatewayNodeId, List<SyncDeviceItem> devices);
}
}

View File

@@ -1,13 +1,17 @@
/*
*所有关于iot_alarm类的业务代码接口应在此处编写
*/
using VolPro.Core.BaseProvider;
using VolPro.Entity.DomainModels;
using VolPro.Core.Utilities;
using System.Linq.Expressions;
using VolPro.Core.BaseProvider;
using VolPro.Core.Utilities;
using VolPro.Entity.DomainModels;
using Warehouse.Services;
namespace Warehouse.IServices
{
public partial interface Iiot_alarmService
{
Task UpsertAlarmAsync(SyncAlarmItem a, int? deviceId);
}
}

View File

@@ -45,7 +45,7 @@ public class HeartbeatMonitorJob : IJob
if (devSvc != null)
{
var devices = await devSvc.FindAsIQueryable(
x => x.GatewayNodeId == node.NodeId && x.IsOnline == "在线")
x => x.NodeId == node.NodeId && x.IsOnline == "在线")
.ToListAsync();
foreach (var dev in devices)
{

View File

@@ -48,7 +48,7 @@ namespace Warehouse.Services
public async Task<List<base_device>> GetDevicesByGatewayNodeAsync(int gatewayNodeId)
{
return await _repository.DbContext.Queryable<base_device>()
.Where(x => x.GatewayNodeId == gatewayNodeId && x.ParentDeviceId == null)
.Where(x => x.NodeId == gatewayNodeId && x.ParentDeviceId == null)
.ToListAsync();
}
@@ -83,7 +83,7 @@ namespace Warehouse.Services
SourceId = d.SourceId,
DeviceCategory = d.Category,
DeviceGroup = d.Group,
GatewayNodeId = gatewayNodeId,
NodeId = gatewayNodeId,
IsParent = d.IsParent ? "是" : "否",
ParentDeviceId = parentDeviceId,
IsOnline = d.IsOnline ? "在线" : "离线",

View File

@@ -113,7 +113,7 @@ namespace Warehouse.Services
// 批量查询已有设备映射表(用于 parentSourceId → ParentDeviceId 解析)
var adapterCodes = devices.Select(d => d.AdapterCode).Distinct().ToList();
var existingIds = db.Queryable<base_device>()
.Where(x => x.GatewayNodeId == gatewayNodeId && adapterCodes.Contains(x.AdapterCode))
.Where(x => x.NodeId == gatewayNodeId && adapterCodes.Contains(x.AdapterCode))
.ToList()
.ToDictionary(x => (x.AdapterCode, x.SourceId), x => x.DeviceId);
@@ -142,7 +142,7 @@ namespace Warehouse.Services
SourceId = d.SourceId,
DeviceCategory = d.Category,
DeviceGroup = d.Group,
GatewayNodeId = gatewayNodeId,
NodeId = gatewayNodeId,
IsParent = d.IsParent ? "是" : "否",
ParentDeviceId = parentDeviceId,
IsOnline = d.IsOnline ? "在线" : "离线",

View File

@@ -55,11 +55,11 @@ namespace Warehouse.Services
var alarm = new iot_alarm
{
SourceAlarmId = a.SourceAlarmId,
DeviceId = deviceId,
DeviceId = (int)deviceId,
AdapterCode = a.AdapterCode,
AlarmLevel = a.Level,
AlarmDesc = a.Desc,
AlarmValue = a.Value,
AlarmValue = (decimal?)a.Value,
StartTime = DateTime.TryParse(a.StartTime, out var st) ? st : DateTime.Now,
State = "未确认",
CreateDate = DateTime.Now

View File

@@ -13,15 +13,15 @@ DROP TABLE IF EXISTS base_device;
CREATE TABLE base_device (
DeviceId INT AUTO_INCREMENT COMMENT '设备ID',
DeviceName NVARCHAR(100) NOT NULL COMMENT '设备名称',
AdapterCode NVARCHAR(50) NOT NULL COMMENT '来源适配器(类型:实例)',
SourceId NVARCHAR(100) NOT NULL COMMENT '源系统设备ID',
AdapterCode NVARCHAR(50) COMMENT '来源适配器(类型:实例)',
SourceId NVARCHAR(100) COMMENT '源系统设备ID',
DeviceCategory NVARCHAR(50) NOT NULL COMMENT '设备种类(数据字典:门磁/空调/智能断路器/人行道闸/车辆道闸/485钥匙柜/网络钥匙柜/紧急报警按钮/红外报警器/门禁一体机/除湿_恒湿机/空调控制器/烟雾报警器/气体报警器/温湿度变送器/摄像机/硬盘录像机/动环采集器)',
DeviceGroup NVARCHAR(20) NOT NULL COMMENT '设备分组(数据字典:视频设备/IoT设备/门禁设备/道闸设备/报警设备)',
PointId INT NULL COMMENT '所属点位ID',
GatewayNodeId INT NULL COMMENT '所属网关节点ID',
NodeId INT NULL COMMENT '所属网关节点ID',
IsParent NVARCHAR(20) NOT NULL DEFAULT '' COMMENT '是否父设备(数据字典:是/否)',
ParentDeviceId INT NULL COMMENT '父设备ID(自引用,子设备挂父设备下)',
IsOnline NVARCHAR(20) NOT NULL DEFAULT '离线' COMMENT '在线状态(数据字典:在线/离线)',
IsOnline NVARCHAR(20) DEFAULT '离线' COMMENT '在线状态(数据字典:在线/离线)',
IpAddress NVARCHAR(50) COMMENT 'IP地址',
Port INT COMMENT '端口',
Location NVARCHAR(200) COMMENT '安装位置',