1164 lines
45 KiB
Markdown
1164 lines
45 KiB
Markdown
# Vol.Pro + MC4.0 采集网关整合方案
|
||
|
||
> **版本**: v1.0
|
||
> **日期**: 2026-04-29
|
||
> **编制**: 浮浮酱
|
||
> **状态**: 待确认(未实施)
|
||
|
||
---
|
||
|
||
## 一、项目概述
|
||
|
||
### 1.1 需求背景
|
||
|
||
MC4.0 是一款采集网关设备,负责接入温湿度传感器、空调控制器、485接口设备、开关量设备等,通过 HTTP API 对外提供设备管理、数据采集、反向控制和告警查询能力。
|
||
|
||
**目标**: 将 MC4.0 采集网关接入 Vol.Pro 后端,实现:
|
||
- 在 Vol.Pro 管理端中对采集设备进行增删改查管理
|
||
- warehouse 用户端实时展示设备数据(温湿度等)
|
||
- warehouse 用户端手动控制设备(如空调开关、温度设定)
|
||
- 接收并展示 MC4.0 的告警信息
|
||
|
||
### 1.2 MC4.0 API 能力分析
|
||
|
||
| 功能模块 | 接口 | 说明 |
|
||
|----------|------|------|
|
||
| **认证** | `/api/central/auth/login` | Token 认证(有效期需确认) |
|
||
| **对象树** | `/api/central/object/tree` | 获取区域+设备的树形结构 |
|
||
| **点表** | `/api/central/device/point/get` | 获取设备下的点(传感器/控制点) |
|
||
| **读数据** | `/api/central/device/point/value/get` | 按设备获取所有点的实时值 |
|
||
| **读数据** | `/api/central/point/multi/value/get` | 批量获取指定设备点的值 |
|
||
| **写控制** | `/api/central/point/value/set` | 设置设备点的值(反向控制) |
|
||
| **告警查询** | `/api/central/alarm/query` | 按条件查询告警(时间/设备/状态) |
|
||
| **告警处理** | `/api/central/alarm/confirm` | 确认告警 |
|
||
| **告警处理** | `/api/central/alarm/end` | 结束告警 |
|
||
| **历史告警** | `/api/central/his_alarm/query` | 查询历史告警记录 |
|
||
|
||
### 1.3 关键前提:所有修改独立于框架代码
|
||
|
||
> 与视频监控方案一致,所有自定义代码均写在 Vol.Pro 的扩展文件中,不修改任何框架自动生成的代码。
|
||
>
|
||
> - Controller 扩展写在 `Controllers/模块/Partial/`
|
||
> - Service 扩展写在 `Services/模块/Partial/`
|
||
> - Entity 扩展写在 `DomainModels/模块/partial/`
|
||
> - 前端扩展写在 `extension/` 目录
|
||
> - 新增独立服务实现 `IDependency`,Autofac 自动注入
|
||
|
||
---
|
||
|
||
## 二、系统架构设计
|
||
|
||
### 2.1 整体架构
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────────┐
|
||
│ 客户端层 │
|
||
│ ┌─────────────────────┐ ┌─────────────────────┐ │
|
||
│ │ web.vite 管理端 │ │ warehouse 用户端 │ │
|
||
│ │ (设备管理+告警查看) │ │ (实时数据+设备控制) │ │
|
||
│ └──────────┬──────────┘ └──────────┬──────────┘ │
|
||
└─────────────┼──────────────────────────┼────────────────────────────────┘
|
||
│ │
|
||
▼ ▼
|
||
┌─────────────────────────────────────────────────────────────────────────┐
|
||
│ Vol.Pro 后端 (api_sqlsugar) │
|
||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||
│ │ 自动生成CRUD模块 │ │
|
||
│ │ ├─ Base_Device (统一设备主表,含视频监控+采集网关) │ │
|
||
│ │ ├─ Device_IoT_Ext (采集设备扩展信息) │ │
|
||
│ │ ├─ IoT_DevicePoint (设备点表/传感器点) │ │
|
||
│ │ ├─ IoT_DeviceData (实时/历史数据记录) │ │
|
||
│ │ └─ IoT_Alarm (告警记录管理) │ │
|
||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||
│ │ 扩展API模块 (IoTStreamController) │ │
|
||
│ │ ├─ POST /api/IoTStream/syncTree → 同步MC4.0对象树 │ │
|
||
│ │ ├─ POST /api/IoTStream/syncPoints → 同步设备点表 │ │
|
||
│ │ ├─ POST /api/IoTStream/realtime → 获取实时数据 │ │
|
||
│ │ ├─ POST /api/IoTStream/control → 设备反向控制 │ │
|
||
│ │ ├─ POST /api/IoTStream/alarms → 查询告警列表 │ │
|
||
│ │ ├─ POST /api/IoTStream/confirmAlarm→ 确认告警 │ │
|
||
│ │ └─ POST /api/IoTStream/pollAlarms → 手动轮询最新告警 │ │
|
||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||
│ │ MC4.0对接服务 (Mc4ApiService) │ │
|
||
│ │ ├─ Token管理 (登录/缓存/刷新) │ │
|
||
│ │ ├─ 对象树同步 (调用 /object/tree) │ │
|
||
│ │ ├─ 点表同步 (调用 /device/point/get) │ │
|
||
│ │ ├─ 实时数据获取 (调用 /device/point/value/get) │ │
|
||
│ │ ├─ 设备控制 (调用 /point/value/set) │ │
|
||
│ │ ├─ 告警查询 (调用 /alarm/query) │ │
|
||
│ │ └─ 告警确认/结束 (调用 /alarm/confirm, /alarm/end) │ │
|
||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||
│ │ 告警轮询任务 (Quartz Job) │ │
|
||
│ │ ├─ 每30秒轮询MC4.0最新告警 │ │
|
||
│ │ ├─ 将新告警写入 IoT_Alarm 表 │ │
|
||
│ │ └─ 通过 SignalR 推送给在线前端 │ │
|
||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||
└──────────────────────────────┬──────────────────────────────────────────┘
|
||
│ HTTP API (端口3000)
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────────────────┐
|
||
│ MC4.0 采集网关 │
|
||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||
│ │ 设备接入层 (温湿度/空调/485/开关量) │ │
|
||
│ │ 数据缓存层 │ │
|
||
│ │ HTTP API层 (对象树/点表/读值/写值/告警) │ │
|
||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 2.2 关键设计原则
|
||
|
||
1. **MC4.0 作为唯一设备源**: Vol.Pro 不直接管理设备接入,所有设备数据来自 MC4.0 的对象树
|
||
2. **数据双写**: MC4.0 中的设备、点表、告警通过同步机制写入 Vol.Pro 数据库
|
||
3. **定时轮询**: MC4.0 不支持主动推送,Vol.Pro 后端通过 Quartz 定时任务轮询实时数据和告警
|
||
4. **SignalR 推送**: Vol.Pro 后端通过 SignalR 将实时数据和告警主动推送给 warehouse 前端
|
||
|
||
---
|
||
|
||
## 三、数据库表设计
|
||
|
||
### 3.1 表结构说明
|
||
|
||
> **设备统一管理设计**:本方案与视频监控方案共用统一设备主表 `Base_Device`,实现视频监控设备和采集网关设备的统一管理,并支持在三维地图上统一标记展示。
|
||
|
||
#### 3.1.0 统一设备主表 (Base_Device) 【与视频监控方案共用】
|
||
|
||
| 字段名 | 类型 | 必填 | 说明 |
|
||
|--------|------|------|------|
|
||
| DeviceId | uniqueidentifier | ✅ | 主键,Vol.Pro系统内部ID |
|
||
| DeviceName | nvarchar(100) | ✅ | 设备名称 |
|
||
| DeviceCategory | int | ✅ | 设备大类:1=视频监控, 2=采集网关 |
|
||
| DeviceType | int | | 设备细分类型(根据Category解释不同值) |
|
||
| SourceId | nvarchar(64) | ✅ | 源系统设备ID(OwlDeviceId或Mc4DeviceId) |
|
||
| IpAddress | nvarchar(50) | | IP地址 |
|
||
| Port | int | | 端口 |
|
||
| IsOnline | int | | 是否在线:0=离线, 1=在线 |
|
||
| Location | nvarchar(200) | | 安装位置描述 |
|
||
| Lat | float | | 纬度(WGS84坐标系) |
|
||
| Lng | float | | 经度(WGS84坐标系) |
|
||
| **MapModelId** | **nvarchar(100)** | | **三维地图模型ID(VgoMap中对应模型的唯一标识)** |
|
||
| MapModelScale | float | | 三维模型缩放比例,默认1.0 |
|
||
| MapModelRotation | nvarchar(100) | | 三维模型旋转角度(JSON格式:{"x":0,"y":0,"z":0}) |
|
||
| Enable | int | | 启用状态:0=禁用, 1=启用 |
|
||
| Remark | nvarchar(500) | | 备注 |
|
||
| CreateID / Creator / CreateDate | | | Vol.Pro标准审计字段 |
|
||
| ModifyID / Modifier / ModifyDate | | | Vol.Pro标准审计字段 |
|
||
|
||
> **MapModelId 字段说明**:
|
||
> - 用于在warehouse前端三维地图(VgoMap)中标记设备位置
|
||
> - 值由三维地图系统提供,标识对应三维模型的唯一ID
|
||
> - 管理端可在设备编辑页面中配置/修改此字段
|
||
> - 三维地图加载时,根据MapModelId将设备图标/模型绑定到地图对应位置
|
||
|
||
#### 3.1.1 采集设备扩展表 (Device_IoT_Ext)
|
||
|
||
> 存储采集网关设备特有的扩展信息,通过DeviceId关联Base_Device主表。
|
||
|
||
| 字段名 | 类型 | 必填 | 说明 |
|
||
|--------|------|------|------|
|
||
| ExtId | uniqueidentifier | ✅ | 主键 |
|
||
| DeviceId | uniqueidentifier | ✅ | 关联Base_Device.DeviceId |
|
||
| Mc4DeviceId | int | ✅ | MC4.0 设备ID |
|
||
| ObjectType | int | | MC4.0 objectType 原始值 |
|
||
| Tag | nvarchar(100) | | 设备标签 |
|
||
| ParentId | int | | MC4.0 父级ID(区域层级) |
|
||
| Mc4Option | nvarchar(500) | | MC4.0 option 原始JSON |
|
||
|
||
#### 3.1.2 设备点表 (IoT_DevicePoint)
|
||
|
||
| 字段名 | 类型 | 必填 | 说明 |
|
||
|--------|------|------|------|
|
||
| PointId | uniqueidentifier | ✅ | 主键 |
|
||
| DeviceId | uniqueidentifier | ✅ | 关联 Base_Device.DeviceId |
|
||
| Mc4DeviceId | int | ✅ | MC4.0 设备ID |
|
||
| PointIndex | int | ✅ | 点索引(MC4.0中的index) |
|
||
| PointType | int | | 点类型(MC4.0中的type) |
|
||
| PointTag | nvarchar(100) | | 点标签 |
|
||
| PointName | nvarchar(100) | ✅ | 点名称 |
|
||
| PointDesc | nvarchar(200) | | 点描述 |
|
||
| Unit | nvarchar(50) | | 单位(如 ℃, %, V) |
|
||
| IsControlPoint | int | | 是否是控制点:0=只读, 1=可写 |
|
||
| Mc4Option | nvarchar(500) | | MC4.0 option 原始JSON |
|
||
| Enable | int | | 启用状态 |
|
||
| CreateDate | datetime | | 创建时间 |
|
||
|
||
#### 3.1.3 设备数据记录表 (IoT_DeviceData) 【仅用于历史归档】
|
||
|
||
> **⚠️ 重要设计变更**:实时数据**不入库**,直接通过SignalR推送给前端展示。本表仅用于存储**历史归档数据**(如每小时/每天保存一次快照)。
|
||
>
|
||
> **原因**:若每次轮询都入库,100设备×10点×每30秒轮询 = 288万条/天,SQL Server单表性能会急剧下降。
|
||
|
||
| 字段名 | 类型 | 必填 | 说明 |
|
||
|--------|------|------|------|
|
||
| DataId | uniqueidentifier | ✅ | 主键 |
|
||
| DeviceId | uniqueidentifier | ✅ | 关联设备 |
|
||
| PointId | uniqueidentifier | ✅ | 关联点 |
|
||
| PointValue | float | | 点值 |
|
||
| UpdateTime | datetime | ✅ | MC4.0 数据更新时间 |
|
||
| Interval | int | | 距离上一次采集间隔(毫秒) |
|
||
| CreateDate | datetime | | 记录入库时间 |
|
||
| ArchiveType | int | | 归档类型:1=小时归档, 2=日归档 |
|
||
|
||
#### 3.1.4 告警记录表 (IoT_Alarm)
|
||
|
||
| 字段名 | 类型 | 必填 | 说明 |
|
||
|--------|------|------|------|
|
||
| AlarmId | uniqueidentifier | ✅ | 主键 |
|
||
| Mc4AlarmId | nvarchar(64) | ✅ | MC4.0 告警ID |
|
||
| DeviceId | uniqueidentifier | | 关联设备 |
|
||
| AlarmType | int | | 告警类型 |
|
||
| AlarmLevel | int | | 告警等级:1=提示, 2=普通, 3=重要, 4=紧急 |
|
||
| AlarmDesc | nvarchar(500) | | 告警描述 |
|
||
| AlarmValue | float | | 触发值 |
|
||
| StartTime | datetime | ✅ | 告警开始时间 |
|
||
| EndTime | datetime | | 告警结束时间 |
|
||
| ConfirmTime | datetime | | 确认时间 |
|
||
| ConfirmUser | nvarchar(50) | | 确认人 |
|
||
| State | int | | 状态:1=未确认, 2=已确认, 3=已结束 |
|
||
| IsDischarge | int | | 是否是放电告警 |
|
||
| Mc4Option | nvarchar(500) | | MC4.0 原始JSON |
|
||
| CreateDate | datetime | | 入库时间 |
|
||
|
||
### 3.2 建表SQL (SQL Server)
|
||
|
||
```sql
|
||
-- ============================================
|
||
-- 统一设备主表(与视频监控方案共用)
|
||
-- ============================================
|
||
CREATE TABLE Base_Device (
|
||
DeviceId UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
|
||
DeviceName NVARCHAR(100) NOT NULL,
|
||
DeviceCategory INT NOT NULL, -- 1=视频监控, 2=采集网关
|
||
DeviceType INT, -- 细分类型(根据Category解释)
|
||
SourceId NVARCHAR(64) NOT NULL, -- 源系统设备ID(OwlDeviceId或Mc4DeviceId)
|
||
IpAddress NVARCHAR(50),
|
||
Port INT,
|
||
IsOnline INT DEFAULT 0,
|
||
Location NVARCHAR(200), -- 安装位置描述
|
||
Lat FLOAT, -- 纬度
|
||
Lng FLOAT, -- 经度
|
||
MapModelId NVARCHAR(100), -- 三维地图模型ID(VgoMap)
|
||
MapModelScale FLOAT DEFAULT 1.0, -- 三维模型缩放比例
|
||
MapModelRotation NVARCHAR(100), -- 三维模型旋转角度(JSON)
|
||
Enable INT DEFAULT 1,
|
||
Remark NVARCHAR(500),
|
||
CreateID INT,
|
||
Creator NVARCHAR(30),
|
||
CreateDate DATETIME DEFAULT GETDATE(),
|
||
ModifyID INT,
|
||
Modifier NVARCHAR(30),
|
||
ModifyDate DATETIME
|
||
);
|
||
CREATE INDEX IX_Base_Device_Category ON Base_Device(DeviceCategory);
|
||
CREATE INDEX IX_Base_Device_SourceId ON Base_Device(SourceId);
|
||
CREATE INDEX IX_Base_Device_IsOnline ON Base_Device(IsOnline);
|
||
CREATE INDEX IX_Base_Device_MapModelId ON Base_Device(MapModelId);
|
||
|
||
-- ============================================
|
||
-- 采集网关设备扩展表
|
||
-- ============================================
|
||
CREATE TABLE Device_IoT_Ext (
|
||
ExtId UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
|
||
DeviceId UNIQUEIDENTIFIER NOT NULL,
|
||
Mc4DeviceId INT NOT NULL,
|
||
ObjectType INT,
|
||
Tag NVARCHAR(100),
|
||
ParentId INT,
|
||
Mc4Option NVARCHAR(500),
|
||
FOREIGN KEY (DeviceId) REFERENCES Base_Device(DeviceId)
|
||
);
|
||
CREATE INDEX IX_Device_IoT_Ext_DeviceId ON Device_IoT_Ext(DeviceId);
|
||
CREATE INDEX IX_Device_IoT_Ext_Mc4DeviceId ON Device_IoT_Ext(Mc4DeviceId);
|
||
|
||
-- 设备点表
|
||
CREATE TABLE IoT_DevicePoint (
|
||
PointId UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
|
||
DeviceId UNIQUEIDENTIFIER NOT NULL,
|
||
Mc4DeviceId INT NOT NULL,
|
||
PointIndex INT NOT NULL,
|
||
PointType INT,
|
||
PointTag NVARCHAR(100),
|
||
PointName NVARCHAR(100) NOT NULL,
|
||
PointDesc NVARCHAR(200),
|
||
Unit NVARCHAR(50),
|
||
IsControlPoint INT DEFAULT 0,
|
||
Mc4Option NVARCHAR(500),
|
||
Enable INT DEFAULT 1,
|
||
CreateDate DATETIME DEFAULT GETDATE()
|
||
);
|
||
CREATE INDEX IX_IoT_DevicePoint_DeviceId ON IoT_DevicePoint(DeviceId);
|
||
CREATE INDEX IX_IoT_DevicePoint_Mc4DeviceId ON IoT_DevicePoint(Mc4DeviceId);
|
||
|
||
-- 设备数据记录表
|
||
CREATE TABLE IoT_DeviceData (
|
||
DataId UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
|
||
DeviceId UNIQUEIDENTIFIER NOT NULL,
|
||
PointId UNIQUEIDENTIFIER NOT NULL,
|
||
PointValue FLOAT,
|
||
UpdateTime DATETIME NOT NULL,
|
||
Interval INT,
|
||
CreateDate DATETIME DEFAULT GETDATE()
|
||
);
|
||
CREATE INDEX IX_IoT_DeviceData_DeviceId ON IoT_DeviceData(DeviceId);
|
||
CREATE INDEX IX_IoT_DeviceData_PointId ON IoT_DeviceData(PointId);
|
||
CREATE INDEX IX_IoT_DeviceData_UpdateTime ON IoT_DeviceData(UpdateTime);
|
||
|
||
-- 告警记录表
|
||
CREATE TABLE IoT_Alarm (
|
||
AlarmId UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
|
||
Mc4AlarmId NVARCHAR(64) NOT NULL,
|
||
DeviceId UNIQUEIDENTIFIER,
|
||
AlarmType INT,
|
||
AlarmLevel INT,
|
||
AlarmDesc NVARCHAR(500),
|
||
AlarmValue FLOAT,
|
||
StartTime DATETIME NOT NULL,
|
||
EndTime DATETIME,
|
||
ConfirmTime DATETIME,
|
||
ConfirmUser NVARCHAR(50),
|
||
State INT DEFAULT 1,
|
||
IsDischarge INT DEFAULT 0,
|
||
Mc4Option NVARCHAR(500),
|
||
CreateDate DATETIME DEFAULT GETDATE()
|
||
);
|
||
CREATE INDEX IX_IoT_Alarm_Mc4AlarmId ON IoT_Alarm(Mc4AlarmId);
|
||
CREATE INDEX IX_IoT_Alarm_DeviceId ON IoT_Alarm(DeviceId);
|
||
CREATE INDEX IX_IoT_Alarm_State ON IoT_Alarm(State);
|
||
CREATE INDEX IX_IoT_Alarm_StartTime ON IoT_Alarm(StartTime);
|
||
```
|
||
|
||
---
|
||
|
||
## 四、Vol.Pro 后端扩展方案
|
||
|
||
### 4.1 代码生成步骤
|
||
|
||
使用 Vol.Pro 代码生成器自动生成模块的 CRUD 代码:
|
||
1. **Base_Device**(统一设备主表,含视频监控+采集网关设备)
|
||
2. **Device_IoT_Ext**(采集设备扩展信息)
|
||
3. **IoT_DevicePoint**(设备点表)
|
||
4. **IoT_DeviceData**(数据记录)
|
||
5. **IoT_Alarm**(告警记录)
|
||
|
||
### 4.2 MC4.0 对接服务 (Mc4ApiService)
|
||
|
||
**新建独立服务文件**(不修改任何框架代码):
|
||
|
||
```csharp
|
||
// 文件位置:api_sqlsugar/Warehouse/Services/Mc4/Mc4ApiService.cs
|
||
|
||
/// <summary>
|
||
/// MC4.0 采集网关对接服务
|
||
/// </summary>
|
||
public interface IMc4ApiService : IDependency
|
||
{
|
||
/// <summary>获取/刷新 MC4.0 Token</summary>
|
||
Task<string> GetTokenAsync();
|
||
|
||
/// <summary>同步 MC4.0 对象树(区域+设备)</summary>
|
||
Task<List<Mc4ObjectNode>> SyncObjectTreeAsync();
|
||
|
||
/// <summary>同步指定设备的点表</summary>
|
||
Task<List<Mc4DevicePoint>> SyncDevicePointsAsync(int mc4DeviceId);
|
||
|
||
/// <summary>获取设备实时数据(所有点)</summary>
|
||
Task<List<Mc4PointValue>> GetDeviceRealtimeDataAsync(int mc4DeviceId);
|
||
|
||
/// <summary>批量获取指定点的实时数据</summary>
|
||
Task<List<Mc4PointValue>> GetMultiPointValuesAsync(List<Mc4PointQuery> points);
|
||
|
||
/// <summary>控制设备点值(反向控制)</summary>
|
||
Task<bool> SetPointValueAsync(int mc4DeviceId, int pointIndex, double value);
|
||
|
||
/// <summary>查询告警列表</summary>
|
||
Task<Mc4AlarmPageResult> QueryAlarmsAsync(Mc4AlarmQueryInput input);
|
||
|
||
/// <summary>确认告警</summary>
|
||
Task<bool> ConfirmAlarmAsync(string alarmId);
|
||
|
||
/// <summary>结束告警</summary>
|
||
Task<bool> EndAlarmAsync(string alarmId);
|
||
}
|
||
```
|
||
|
||
**实现要点**:
|
||
1. **新建文件**,实现 `IDependency`,Autofac 自动注入
|
||
2. 使用 `IHttpClientFactory` 创建指向 MC4.0(端口3000)的 HTTP 客户端
|
||
3. Token 获取后缓存到内存,失效前自动刷新
|
||
4. 所有返回值统一转换为 Vol.Pro 的 `WebResponseContent` 格式
|
||
|
||
### 4.3 扩展 API 控制器 (IoTStreamController)
|
||
|
||
**新建独立 Controller**(不修改任何框架代码):
|
||
|
||
```csharp
|
||
// 文件位置:api_sqlsugar/Warehouse/Controllers/IoTStreamController.cs
|
||
|
||
[Route("api/IoTStream")]
|
||
[JWTAuthorize]
|
||
public class IoTStreamController : VolController
|
||
{
|
||
private readonly IMc4ApiService _mc4Api;
|
||
private readonly IBase_DeviceService _deviceService;
|
||
|
||
public IoTStreamController(
|
||
IMc4ApiService mc4Api,
|
||
IBase_DeviceService deviceService)
|
||
{
|
||
_mc4Api = mc4Api;
|
||
_deviceService = deviceService;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 同步 MC4.0 对象树(区域+设备)
|
||
/// </summary>
|
||
[HttpPost, Route("SyncTree")]
|
||
public async Task<ActionResult> SyncTree()
|
||
{
|
||
var nodes = await _mc4Api.SyncObjectTreeAsync();
|
||
// 将对象树写入 Base_Device 主表(DeviceCategory=2采集网关,DeviceType=1区域/2设备)
|
||
// ... 同步逻辑
|
||
return Json(new WebResponseContent().OK(null, $"同步完成,共{nodes.Count}个节点"));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 同步指定设备的点表
|
||
/// </summary>
|
||
[HttpPost, Route("SyncPoints")]
|
||
public async Task<ActionResult> SyncPoints([FromBody] SyncPointsInput input)
|
||
{
|
||
var device = await _deviceService.GetDeviceByIdAsync(input.DeviceId);
|
||
if (device == null) return Json(new WebResponseContent().Error("设备不存在"));
|
||
|
||
var points = await _mc4Api.SyncDevicePointsAsync(device.Mc4DeviceId);
|
||
return Json(new WebResponseContent().OK(null, points));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取设备实时数据
|
||
/// </summary>
|
||
[HttpPost, Route("GetRealtime")]
|
||
public async Task<ActionResult> GetRealtime([FromBody] GetRealtimeInput input)
|
||
{
|
||
var device = await _deviceService.GetDeviceByIdAsync(input.DeviceId);
|
||
if (device == null) return Json(new WebResponseContent().Error("设备不存在"));
|
||
|
||
var data = await _mc4Api.GetDeviceRealtimeDataAsync(device.Mc4DeviceId);
|
||
return Json(new WebResponseContent().OK(null, data));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设备反向控制
|
||
/// </summary>
|
||
[HttpPost, Route("Control")]
|
||
[ApiActionPermission("Base_Device", ActionPermissionOptions.Update)]
|
||
public async Task<ActionResult> Control([FromBody] ControlInput input)
|
||
{
|
||
var device = await _deviceService.GetDeviceByIdAsync(input.DeviceId);
|
||
if (device == null) return Json(new WebResponseContent().Error("设备不存在"));
|
||
|
||
var success = await _mc4Api.SetPointValueAsync(
|
||
device.Mc4DeviceId, input.PointIndex, input.Value);
|
||
|
||
return Json(success
|
||
? new WebResponseContent().OK(null, "控制指令已下发")
|
||
: new WebResponseContent().Error("控制失败"));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查询告警列表
|
||
/// </summary>
|
||
[HttpPost, Route("GetAlarms")]
|
||
public async Task<ActionResult> GetAlarms([FromBody] AlarmQueryInput input)
|
||
{
|
||
var result = await _mc4Api.QueryAlarmsAsync(new Mc4AlarmQueryInput
|
||
{
|
||
Sids = input.DeviceIds,
|
||
From = input.StartTime,
|
||
To = input.EndTime,
|
||
ConfirmState = input.ConfirmState,
|
||
EndState = input.EndState,
|
||
Skip = (input.Page - 1) * input.Rows,
|
||
Limit = input.Rows
|
||
});
|
||
|
||
return Json(new WebResponseContent().OK(null, new {
|
||
total = result.Total,
|
||
rows = result.List
|
||
}));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确认告警
|
||
/// </summary>
|
||
[HttpPost, Route("ConfirmAlarm")]
|
||
public async Task<ActionResult> ConfirmAlarm([FromBody] AlarmActionInput input)
|
||
{
|
||
var success = await _mc4Api.ConfirmAlarmAsync(input.AlarmId);
|
||
return Json(success
|
||
? new WebResponseContent().OK()
|
||
: new WebResponseContent().Error("确认失败"));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 手动触发告警轮询(用于前端主动刷新)
|
||
/// </summary>
|
||
[HttpPost, Route("PollAlarms")]
|
||
public async Task<ActionResult> PollAlarms()
|
||
{
|
||
// 触发一次告警轮询任务
|
||
// ... 调用后台Job或直接查询MC4.0
|
||
return Json(new WebResponseContent().OK(null, "轮询已触发"));
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.4 告警轮询任务 (Quartz Job)
|
||
|
||
MC4.0 不支持主动推送告警,Vol.Pro 后端通过 **Quartz 定时任务** 定时轮询:
|
||
|
||
```csharp
|
||
// 文件位置:api_sqlsugar/Warehouse/Jobs/AlarmPollJob.cs
|
||
// (新建文件,实现 IDependency)
|
||
|
||
/// <summary>
|
||
/// MC4.0 告警轮询定时任务(每10秒执行一次)
|
||
/// </summary>
|
||
public class AlarmPollJob : IDependency
|
||
{
|
||
private readonly IMc4ApiService _mc4Api;
|
||
private readonly IIoT_AlarmService _alarmService;
|
||
private readonly IHubContext<IoTDataHub> _hubContext;
|
||
|
||
public AlarmPollJob(
|
||
IMc4ApiService mc4Api,
|
||
IIoT_AlarmService alarmService,
|
||
IHubContext<IoTDataHub> hubContext)
|
||
{
|
||
_mc4Api = mc4Api;
|
||
_alarmService = alarmService;
|
||
_hubContext = hubContext;
|
||
}
|
||
|
||
public async Task ExecuteAsync()
|
||
{
|
||
// 1. 查询最近30秒内的告警(缩短窗口匹配轮询间隔)
|
||
var result = await _mc4Api.QueryAlarmsAsync(new Mc4AlarmQueryInput
|
||
{
|
||
From = DateTime.Now.AddSeconds(-30).ToString("yyyy-MM-dd HH:mm:ss"),
|
||
To = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
|
||
Limit = 100
|
||
});
|
||
|
||
// 2. 过滤已入库的新告警(按Mc4AlarmId去重)
|
||
var newAlarms = await FilterNewAlarmsAsync(result.List);
|
||
|
||
// 3. 写入 IoT_Alarm 表
|
||
foreach (var alarm in newAlarms)
|
||
{
|
||
await _alarmService.AddAsync(alarm);
|
||
}
|
||
|
||
// 4. 通过 SignalR 按设备分组推送给前端(避免全量广播)
|
||
foreach (var alarm in newAlarms)
|
||
{
|
||
if (alarm.DeviceId.HasValue)
|
||
{
|
||
await _hubContext.Clients.Group($"device_{alarm.DeviceId}")
|
||
.SendAsync("NewAlarm", alarm);
|
||
}
|
||
}
|
||
|
||
// 5. 同时推送给告警管理员组
|
||
if (newAlarms.Any())
|
||
{
|
||
await _hubContext.Clients.Group("alarm_admin")
|
||
.SendAsync("NewAlarms", newAlarms);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.5 实时数据轮询任务(Quartz Job)
|
||
|
||
> **设计变更**:实时数据**不入库**,直接通过SignalR推送给前端展示。
|
||
|
||
```csharp
|
||
// 文件位置:api_sqlsugar/Warehouse/Jobs/RealtimeDataPollJob.cs
|
||
// (新建文件,实现 IDependency)
|
||
|
||
/// <summary>
|
||
/// 实时数据轮询任务(每5秒执行一次)
|
||
/// 注意:数据不入库,仅通过SignalR推送给前端
|
||
/// </summary>
|
||
public class RealtimeDataPollJob : IDependency
|
||
{
|
||
private readonly IMc4ApiService _mc4Api;
|
||
private readonly IBase_DeviceService _deviceService;
|
||
private readonly IHubContext<IoTDataHub> _hubContext;
|
||
|
||
public RealtimeDataPollJob(
|
||
IMc4ApiService mc4Api,
|
||
IBase_DeviceService deviceService,
|
||
IHubContext<IoTDataHub> hubContext)
|
||
{
|
||
_mc4Api = mc4Api;
|
||
_deviceService = deviceService;
|
||
_hubContext = hubContext;
|
||
}
|
||
|
||
public async Task ExecuteAsync()
|
||
{
|
||
// 1. 获取所有启用的采集网关设备
|
||
var devices = await _deviceService.GetDevicesAsync(
|
||
d => d.DeviceCategory == 2 && d.Enable == 1);
|
||
|
||
// 2. 批量获取实时数据(使用批量接口减少调用次数)
|
||
foreach (var device in devices)
|
||
{
|
||
var data = await _mc4Api.GetDeviceRealtimeDataAsync(device.Mc4DeviceId);
|
||
|
||
// 3. 直接SignalR推送给订阅了该设备的客户端(不入库!)
|
||
await _hubContext.Clients.Group($"device_{device.DeviceId}")
|
||
.SendAsync("ReceiveRealtimeData", new {
|
||
deviceId = device.DeviceId,
|
||
deviceName = device.DeviceName,
|
||
points = data,
|
||
updateTime = DateTime.Now
|
||
});
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
> **数据归档策略**(可选):
|
||
> 如需保存历史数据,可另建一个**每小时执行一次**的归档Job,将数据写入`IoT_DeviceData`表(ArchiveType=1小时归档)。
|
||
|
||
### 4.6 MC4.0 请求频率控制
|
||
|
||
为防止高频轮询触发MC4.0限流,在`Mc4ApiService`中实现请求频率控制:
|
||
|
||
```csharp
|
||
public class Mc4ApiService : IMc4ApiService
|
||
{
|
||
private static readonly SemaphoreSlim _rateLimiter = new SemaphoreSlim(2, 2); // 每秒最多2个并发请求
|
||
|
||
private async Task<T> ExecuteWithRateLimitAsync<T>(Func<Task<T>> action)
|
||
{
|
||
await _rateLimiter.WaitAsync();
|
||
try
|
||
{
|
||
return await action();
|
||
}
|
||
finally
|
||
{
|
||
// 500ms后释放,确保每秒不超过2次请求
|
||
await Task.Delay(500);
|
||
_rateLimiter.Release();
|
||
}
|
||
}
|
||
|
||
public async Task<List<Mc4PointValue>> GetDeviceRealtimeDataAsync(int mc4DeviceId)
|
||
{
|
||
return await ExecuteWithRateLimitAsync(async () =>
|
||
{
|
||
// 实际HTTP调用
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.7 SignalR 实时数据推送
|
||
|
||
warehouse 前端需要实时接收数据更新和告警推送,Vol.Pro 后端通过 SignalR 实现:
|
||
|
||
```csharp
|
||
// 文件位置:api_sqlsugar/Warehouse/Hubs/IoTDataHub.cs
|
||
// (新建文件)
|
||
|
||
public interface IIoTDataHub
|
||
{
|
||
Task ReceiveRealtimeData(List<IoTDeviceDataDto> data);
|
||
Task ReceiveNewAlarm(IoTAlarmDto alarm); // 单条告警推送(按设备分组)
|
||
Task ReceiveNewAlarms(List<IoTAlarmDto> alarms); // 批量告警推送(管理员组)
|
||
Task DeviceControlResult(string deviceId, bool success, string message);
|
||
}
|
||
|
||
[Authorize]
|
||
public class IoTDataHub : Hub<IIoTDataHub>
|
||
{
|
||
/// <summary>订阅指定设备的实时数据和告警</summary>
|
||
public async Task SubscribeDevice(string deviceId)
|
||
{
|
||
await Groups.AddToGroupAsync(Context.ConnectionId, $"device_{deviceId}");
|
||
}
|
||
|
||
public async Task UnsubscribeDevice(string deviceId)
|
||
{
|
||
await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"device_{deviceId}");
|
||
}
|
||
|
||
/// <summary>订阅告警管理员组(接收所有告警)</summary>
|
||
public async Task SubscribeAlarmAdmin()
|
||
{
|
||
await Groups.AddToGroupAsync(Context.ConnectionId, "alarm_admin");
|
||
}
|
||
|
||
public async Task UnsubscribeAlarmAdmin()
|
||
{
|
||
await Groups.RemoveFromGroupAsync(Context.ConnectionId, "alarm_admin");
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 五、前端对接方案
|
||
|
||
### 5.1 warehouse 用户端改造
|
||
|
||
warehouse 需要实现以下功能模块:
|
||
|
||
1. **实时数据看板**(新增页面或扩展现有页面):
|
||
- 展示设备列表和当前实时数据(温湿度、电压等)
|
||
- 使用 SignalR 实时接收数据更新
|
||
- 支持按区域筛选设备
|
||
|
||
2. **设备控制面板**(新增):
|
||
- 显示可控制的设备点(如空调开关、温度设定)
|
||
- 提供开关、滑块、输入框等控制组件
|
||
- 调用 `/api/IoTStream/Control` 下发控制指令
|
||
|
||
3. **告警信息面板**(新增或扩展现有告警模块):
|
||
- 实时展示最新告警(通过 SignalR 推送)
|
||
- 支持告警确认、结束操作
|
||
- 告警历史查询
|
||
|
||
**warehouse 前端 API 调用示例**:
|
||
```typescript
|
||
import http from '@/api/http'
|
||
|
||
// 获取设备列表(统一设备主表,过滤DeviceCategory=2的采集网关设备)
|
||
const getDevices = () => {
|
||
return http.post('/api/Base_Device/GetPageData', {
|
||
page: 1, rows: 100, sort: 'CreateDate', order: 'desc',
|
||
wheres: JSON.stringify([{ name: 'DeviceCategory', value: '2', displayType: 'int' }])
|
||
})
|
||
}
|
||
|
||
// 获取设备实时数据
|
||
const getRealtimeData = (deviceId: string) => {
|
||
return http.post('/api/IoTStream/GetRealtime', { deviceId })
|
||
}
|
||
|
||
// 控制设备
|
||
const controlDevice = (deviceId: string, pointIndex: number, value: number) => {
|
||
return http.post('/api/IoTStream/Control', { deviceId, pointIndex, value })
|
||
}
|
||
|
||
// 查询告警
|
||
const getAlarms = (params: any) => {
|
||
return http.post('/api/IoTStream/GetAlarms', params)
|
||
}
|
||
|
||
// 确认告警
|
||
const confirmAlarm = (alarmId: string) => {
|
||
return http.post('/api/IoTStream/ConfirmAlarm', { alarmId })
|
||
}
|
||
|
||
// SignalR 连接
|
||
import * as signalR from '@microsoft/signalr'
|
||
|
||
const connection = new signalR.HubConnectionBuilder()
|
||
.withUrl('/hubs/iotData')
|
||
.withAutomaticReconnect()
|
||
.build()
|
||
|
||
// 连接成功后订阅当前页面展示的设备
|
||
connection.start().then(() => {
|
||
// 订阅设备实时数据(按设备分组)
|
||
const deviceIds = ['guid1', 'guid2'] // 当前页面展示的设备ID
|
||
deviceIds.forEach(id => {
|
||
connection.invoke('SubscribeDevice', id)
|
||
})
|
||
|
||
// 告警管理页面订阅管理员组
|
||
connection.invoke('SubscribeAlarmAdmin')
|
||
})
|
||
|
||
connection.on('ReceiveRealtimeData', (data) => {
|
||
// 更新对应设备的实时数据到页面
|
||
console.log(`设备${data.deviceName}数据更新:`, data.points)
|
||
})
|
||
|
||
connection.on('ReceiveNewAlarm', (alarm) => {
|
||
// 单条告警通知(来自订阅的设备)
|
||
ElNotification({
|
||
title: '新告警',
|
||
message: `${alarm.deviceName}: ${alarm.alarmDesc}`,
|
||
type: 'warning'
|
||
})
|
||
})
|
||
|
||
connection.on('ReceiveNewAlarms', (alarms) => {
|
||
// 批量告警(管理员组)
|
||
// 更新告警列表
|
||
})
|
||
```
|
||
|
||
### 5.2 管理端 (web.vite) 扩展
|
||
|
||
通过 Vol.Pro 代码生成器生成的 CRUD 页面,扩展以下功能:
|
||
|
||
1. **设备列表页扩展**(`extension/warehouse/Base_Device.jsx`):
|
||
- 添加"同步对象树"按钮(仅对DeviceCategory=2的设备显示)
|
||
- 添加"同步点表"操作列
|
||
- 添加"三维地图定位"操作(可跳转地图并高亮对应模型)
|
||
- 添加"查看实时数据"按钮
|
||
|
||
2. **告警列表页扩展**:
|
||
- 添加"确认"和"结束"操作按钮
|
||
- 告警等级颜色标识(紧急=红色,重要=橙色)
|
||
|
||
---
|
||
|
||
## 六、关键 API 接口文档
|
||
|
||
### 6.1 Vol.Pro 后端对外接口
|
||
|
||
#### 6.1.1 同步对象树
|
||
|
||
```
|
||
POST /api/IoTStream/SyncTree
|
||
Authorization: Bearer <Vol.Pro JWT Token>
|
||
|
||
Response:
|
||
{
|
||
"status": true,
|
||
"message": "同步完成,共15个节点",
|
||
"data": { "total": 15, "devices": 8, "areas": 7 }
|
||
}
|
||
```
|
||
|
||
#### 6.1.2 获取设备实时数据
|
||
|
||
```
|
||
POST /api/IoTStream/GetRealtime
|
||
Authorization: Bearer <Vol.Pro JWT Token>
|
||
|
||
Request:
|
||
{
|
||
"deviceId": "guid"
|
||
}
|
||
|
||
Response:
|
||
{
|
||
"status": true,
|
||
"message": "ok",
|
||
"data": [
|
||
{ "index": 1, "name": "温度", "value": 24.5, "unit": "℃", "time": "2026-04-29 10:30:00" },
|
||
{ "index": 2, "name": "湿度", "value": 65.0, "unit": "%", "time": "2026-04-29 10:30:00" }
|
||
]
|
||
}
|
||
```
|
||
|
||
#### 6.1.3 设备反向控制
|
||
|
||
```
|
||
POST /api/IoTStream/Control
|
||
Authorization: Bearer <Vol.Pro JWT Token>
|
||
|
||
Request:
|
||
{
|
||
"deviceId": "guid",
|
||
"pointIndex": 10,
|
||
"value": 1
|
||
}
|
||
|
||
Response:
|
||
{
|
||
"status": true,
|
||
"message": "控制指令已下发"
|
||
}
|
||
```
|
||
|
||
#### 6.1.4 查询告警
|
||
|
||
```
|
||
POST /api/IoTStream/GetAlarms
|
||
Authorization: Bearer <Vol.Pro JWT Token>
|
||
|
||
Request:
|
||
{
|
||
"deviceIds": [],
|
||
"startTime": "2026-04-28 00:00:00",
|
||
"endTime": "2026-04-29 23:59:59",
|
||
"confirmState": 1,
|
||
"endState": 0,
|
||
"page": 1,
|
||
"rows": 20
|
||
}
|
||
|
||
Response:
|
||
{
|
||
"status": true,
|
||
"message": "ok",
|
||
"data": {
|
||
"total": 5,
|
||
"rows": [
|
||
{
|
||
"alarmId": "guid",
|
||
"deviceName": "机房空调A",
|
||
"alarmDesc": "温度过高告警",
|
||
"alarmValue": 32.5,
|
||
"level": 3,
|
||
"startTime": "2026-04-29 10:15:00",
|
||
"state": 1
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.2 MC4.0 内部接口(Vol.Pro 后端调用)
|
||
|
||
| 接口 | 说明 | 用途 |
|
||
|------|------|------|
|
||
| `POST /api/central/auth/login` | 登录获取 Token | 认证 |
|
||
| `POST /api/central/object/tree` | 对象树 | 同步设备 |
|
||
| `POST /api/central/device/point/get` | 设备点表 | 同步点表 |
|
||
| `POST /api/central/device/point/value/get` | 设备实时数据 | 读数据 |
|
||
| `POST /api/central/point/multi/value/get` | 批量点值 | 批量读 |
|
||
| `POST /api/central/point/value/set` | 设置点值 | 反向控制 |
|
||
| `POST /api/central/alarm/query` | 告警查询 | 告警同步 |
|
||
| `POST /api/central/alarm/confirm` | 确认告警 | 告警处理 |
|
||
| `POST /api/central/alarm/end` | 结束告警 | 告警处理 |
|
||
|
||
---
|
||
|
||
## 七、实施计划
|
||
|
||
### 阶段一:数据库与代码生成(2天)
|
||
|
||
1. 执行建表 SQL 创建 4 张表
|
||
2. 使用 Vol.Pro 代码生成器生成 CRUD 代码
|
||
3. 验证生成后的增删改查功能正常
|
||
|
||
### 阶段二:MC4.0 对接服务(3天)
|
||
|
||
**新建独立文件**:
|
||
1. 创建 `Warehouse/Services/Mc4/IMc4ApiService.cs`(接口)
|
||
2. 创建 `Warehouse/Services/Mc4/Mc4ApiService.cs`(实现)
|
||
3. 实现 Token 管理(登录/缓存/刷新)
|
||
4. 实现对象树同步、点表同步、实时数据获取
|
||
5. 实现设备控制、告警查询
|
||
6. 创建 `Warehouse/Controllers/IoTStreamController.cs`(全新 Controller)
|
||
|
||
### 阶段三:实时数据与告警推送(2天)
|
||
|
||
1. 创建 `Warehouse/Hubs/IoTDataHub.cs`(SignalR Hub,支持按设备分组)
|
||
2. 创建 `Warehouse/Jobs/RealtimeDataPollJob.cs`(实时数据轮询,每5秒,**不入库仅推送**)
|
||
3. 创建 `Warehouse/Jobs/AlarmPollJob.cs`(告警轮询,每10秒)
|
||
4. 配置定时任务和频率控制(每秒最多2次MC4.0请求)
|
||
5. 实现SignalR分组推送逻辑
|
||
|
||
### 阶段四:管理端前端扩展(1天)
|
||
|
||
1. 扩展设备列表页(同步按钮、实时数据查看)
|
||
2. 扩展告警列表页(确认/结束操作)
|
||
|
||
### 阶段五:warehouse 用户端开发(3天)
|
||
|
||
1. 开发实时数据看板页面(设备列表 + 实时数值展示)
|
||
2. 开发设备控制面板(开关、滑块、输入框)
|
||
3. 开发告警信息面板(实时告警 + 历史查询)
|
||
4. 集成 SignalR 客户端,实现实时数据接收
|
||
|
||
### 阶段六:联调测试(2天)
|
||
|
||
1. MC4.0 设备接入测试(温湿度、空调等)
|
||
2. 实时数据读取测试(验证SignalR推送,不入库)
|
||
3. 反向控制测试(空调开关、温度设定)
|
||
4. 告警轮询与推送测试(验证10秒轮询+分组推送)
|
||
5. 请求频率控制测试(验证每秒不超过2次请求)
|
||
6. 多设备并发测试
|
||
|
||
**总计预计工期**: 16个工作日(含3天缓冲)
|
||
|
||
---
|
||
|
||
## 八、风险与注意事项
|
||
|
||
### 8.1 MC4.0 API 限制
|
||
|
||
1. **轮询频率限制**: MC4.0 可能对接口调用频率有限制,已通过`SemaphoreSlim`实现每秒最多2次请求的频率控制
|
||
2. **Token 有效期**: 文档未明确 Token 有效期,需实测确认,建议每次请求前检查 Token 是否过期
|
||
3. **无主动推送**: MC4.0 仅支持查询式接口,实时数据和告警都需要轮询获取
|
||
|
||
### 8.2 数据同步策略
|
||
|
||
1. **对象树同步**: 建议在管理端提供手动同步按钮,MC4.0 设备变更时手动触发
|
||
2. **点表同步**: 在对象树同步后,对每个设备自动同步点表
|
||
3. **⚠️ 实时数据不入库**: 实时数据直接通过SignalR推送给前端,**不写入`IoT_DeviceData`表**
|
||
4. **历史归档**(可选): 如需保存历史数据,可配置每小时/每天归档Job,将数据写入`IoT_DeviceData`表(ArchiveType标记归档类型)
|
||
|
||
### 8.3 性能建议
|
||
|
||
1. **批量查询**: 使用 `/api/central/point/multi/value/get` 批量获取点值,减少接口调用次数
|
||
2. **数据缓存**: 对不频繁变化的数据(如设备列表、点表)做 Redis 缓存
|
||
3. **SignalR 分组**: 前端只订阅当前页面展示的设备,避免不必要的推送
|
||
4. **告警去重**: 轮询到的告警通过 `Mc4AlarmId` 去重,避免重复入库
|
||
5. **请求频率控制**: `Mc4ApiService`中已实现每秒最多2次并发请求的限制
|
||
|
||
### 8.4 安全建议
|
||
|
||
1. MC4.0 的登录密码建议配置在 `appsettings.json` 的机密配置段
|
||
2. 设备控制接口需要权限校验(`[ApiActionPermission]`)
|
||
3. MC4.0 的 Token 缓存需加密存储
|
||
|
||
---
|
||
|
||
## 附录A:统一设备管理与三维地图标记
|
||
|
||
### A.1 统一设备管理设计
|
||
|
||
本方案与视频监控方案共用 **`Base_Device` 统一设备主表**,实现所有设备的集中管理:
|
||
|
||
| 设备大类 (DeviceCategory) | 细分类型 (DeviceType) | 扩展表 | 对接系统 |
|
||
|--------------------------|----------------------|--------|----------|
|
||
| 1 = 视频监控 | 1=IPC, 2=NVR, 3=Platform | `Device_Video_Ext` | Owl + ZLMediaKit |
|
||
| 2 = 采集网关 | 1=区域, 2=设备(见MC4.0类型表) | `Device_IoT_Ext` | MC4.0 采集网关 |
|
||
|
||
**统一查询示例**:
|
||
```sql
|
||
-- 查询所有设备(含视频监控+采集网关)
|
||
SELECT * FROM Base_Device WHERE Enable = 1
|
||
|
||
-- 查询所有视频监控设备
|
||
SELECT * FROM Base_Device WHERE DeviceCategory = 1
|
||
|
||
-- 查询所有采集网关设备
|
||
SELECT * FROM Base_Device WHERE DeviceCategory = 2
|
||
|
||
-- 联查设备+视频扩展信息
|
||
SELECT d.*, v.ChannelCount, v.Protocol
|
||
FROM Base_Device d
|
||
LEFT JOIN Device_Video_Ext v ON d.DeviceId = v.DeviceId
|
||
WHERE d.DeviceCategory = 1
|
||
|
||
-- 联查设备+采集扩展信息
|
||
SELECT d.*, i.Mc4DeviceId, i.ObjectType
|
||
FROM Base_Device d
|
||
LEFT JOIN Device_IoT_Ext i ON d.DeviceId = i.DeviceId
|
||
WHERE d.DeviceCategory = 2
|
||
```
|
||
|
||
### A.2 三维地图标记 (VgoMap)
|
||
|
||
warehouse 前端使用 **VgoMap** 三维地图引擎,设备需要在地图上进行三维标记。
|
||
|
||
#### A.2.1 MapModelId 字段说明
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| **MapModelId** | nvarchar(100) | 三维地图中对应模型的唯一标识符 |
|
||
| **MapModelScale** | float | 模型缩放比例,默认1.0 |
|
||
| **MapModelRotation** | nvarchar(100) | 模型旋转角度,JSON格式 `{"x":0,"y":90,"z":0}` |
|
||
|
||
**配置流程**:
|
||
1. 在VgoMap三维地图编辑器中,为每个设备位置放置一个模型(如摄像头图标、传感器图标)
|
||
2. 记录每个模型的ID(VgoMap提供的唯一标识)
|
||
3. 在Vol.Pro管理端的设备编辑页面中,填写对应设备的`MapModelId`
|
||
4. 可选配置缩放比例和旋转角度
|
||
|
||
#### A.2.2 前端三维地图集成示例
|
||
|
||
```typescript
|
||
// warehouse 前端加载设备标记
|
||
const loadDeviceMarkers = async () => {
|
||
// 1. 从Vol.Pro后端获取所有设备(含MapModelId)
|
||
const res = await http.post('/api/Base_Device/GetPageData', {
|
||
page: 1, rows: 1000, wheres: JSON.stringify([{ name: 'Enable', value: '1' }])
|
||
});
|
||
|
||
const devices = res.rows;
|
||
|
||
// 2. 在VgoMap中根据MapModelId绑定设备数据
|
||
devices.forEach(device => {
|
||
if (device.MapModelId) {
|
||
// 绑定设备数据到地图模型
|
||
window.$map.setModelData(device.MapModelId, {
|
||
deviceId: device.DeviceId,
|
||
deviceName: device.DeviceName,
|
||
category: device.DeviceCategory,
|
||
isOnline: device.IsOnline
|
||
});
|
||
|
||
// 设置模型样式(在线/离线不同颜色)
|
||
const color = device.IsOnline === 1 ? '#67c23a' : '#f56c6c';
|
||
window.$map.setModelColor(device.MapModelId, color);
|
||
}
|
||
});
|
||
};
|
||
|
||
// 点击地图模型时显示设备详情
|
||
window.$map.on('modelClick', (e) => {
|
||
const deviceData = e.modelData;
|
||
if (deviceData) {
|
||
showDeviceDetail(deviceData.deviceId);
|
||
}
|
||
});
|
||
|
||
// 实时更新设备状态(通过SignalR)
|
||
connection.on('DeviceStatusChanged', (deviceId, isOnline) => {
|
||
const device = devices.find(d => d.DeviceId === deviceId);
|
||
if (device && device.MapModelId) {
|
||
const color = isOnline ? '#67c23a' : '#f56c6c';
|
||
window.$map.setModelColor(device.MapModelId, color);
|
||
}
|
||
});
|
||
```
|
||
|
||
#### A.2.3 管理端地图配置界面
|
||
|
||
在Vol.Pro管理端的设备编辑页面中,扩展地图配置表单:
|
||
|
||
```javascript
|
||
// extension/warehouse/Base_Device.jsx 中的扩展
|
||
methods: {
|
||
modelOpenAfter(row) {
|
||
// 在编辑表单中添加三维地图配置字段
|
||
this.editFormOptions.push([
|
||
{ title: '三维地图模型ID', field: 'MapModelId', type: 'text' },
|
||
{ title: '模型缩放比例', field: 'MapModelScale', type: 'number' },
|
||
{ title: '模型旋转角度', field: 'MapModelRotation', type: 'text' }
|
||
]);
|
||
}
|
||
}
|
||
```
|
||
|
||
### A.3 设备数据流汇总
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 统一设备数据流 │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ Base_Device (统一主表) │
|
||
│ ├─ DeviceCategory=1 → Device_Video_Ext → Owl/ZLM (视频流) │
|
||
│ └─ DeviceCategory=2 → Device_IoT_Ext → MC4.0 (温湿度/控制) │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ 三维地图 (VgoMap) │
|
||
│ └─ 所有设备通过 MapModelId 在地图上统一标记展示 │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
> **文档结束**
|
||
> **版本**: v1.0
|
||
> **最后更新**: 2026-04-29
|
||
> **状态**: 待主人确认后实施
|