From a113d86cea94ade8d2c5d75b8f2dddff397ecc2d Mon Sep 17 00:00:00 2001 From: g82tt Date: Sun, 17 May 2026 04:39:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=8Ephase=E5=88=86=E6=94=AF=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/db_init.sql | 311 +++++---- doc/设计文档/VolPro框架改造方案.md | 512 ++++++++++++++ doc/设计文档/VolPro框架改造方案_任务清单.md | 260 ++++++++ doc/设计文档/对接网关设计文档.md | 703 ++++++++++++++++++++ doc/设计文档/对接网关设计文档_任务清单.md | 203 ++++++ 5 files changed, 1831 insertions(+), 158 deletions(-) create mode 100644 doc/设计文档/VolPro框架改造方案.md create mode 100644 doc/设计文档/VolPro框架改造方案_任务清单.md create mode 100644 doc/设计文档/对接网关设计文档.md create mode 100644 doc/设计文档/对接网关设计文档_任务清单.md diff --git a/doc/db_init.sql b/doc/db_init.sql index a80030c..b116667 100644 --- a/doc/db_init.sql +++ b/doc/db_init.sql @@ -1,168 +1,163 @@ - -- ============================================ --- SecMPS v2.0 数据库建表脚本 +-- SecMPS v3.0 数据库建表脚本(6张表) -- 数据库: gljs_main +-- 扩展表已合并到 Base_Device.ExtraData(JSON) -- ============================================ -USE gljs_main; - +-- ============================================ -- 1. 统一设备主表 -CREATE TABLE IF NOT EXISTS Base_Device ( - DeviceId CHAR(36) NOT NULL PRIMARY KEY, - DeviceName NVARCHAR(100) NOT NULL, - AdapterCode NVARCHAR(50) NOT NULL, - SourceId NVARCHAR(100) NOT NULL, - DeviceCategory INT NOT NULL DEFAULT 1, - DeviceType NVARCHAR(50), - RegionId INT NULL, - IsParent TINYINT NOT NULL DEFAULT 0, - ParentDeviceId CHAR(36) NULL, - IsOnline TINYINT NOT NULL DEFAULT 0, - IpAddress NVARCHAR(50), - Port INT, - Location NVARCHAR(200), - Lat DOUBLE, - Lng DOUBLE, - MapModelId NVARCHAR(100), - MapModelScale FLOAT DEFAULT 1.0, - MapModelRotation NVARCHAR(100), - ExtraData TEXT, - LocalOverrides TEXT, - SyncVersion BIGINT DEFAULT 0, - LastSyncTime DATETIME, - Enable TINYINT DEFAULT 1, - Remark NVARCHAR(500), - CreateID INT, - Creator NVARCHAR(50), - CreateDate DATETIME DEFAULT CURRENT_TIMESTAMP, - ModifyID INT, - Modifier NVARCHAR(50), - ModifyDate DATETIME, - UNIQUE INDEX IX_Base_Device_Adapter_Source (AdapterCode, SourceId), - INDEX IX_Base_Device_RegionId (RegionId), - INDEX IX_Base_Device_ParentId (ParentDeviceId) -); +-- ExtraData(JSON) 承载所有适配器特有字段 +-- DeviceGroup 路由到正确的网关Adapter和前端按钮组 +-- ============================================ +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', + DeviceCategory NVARCHAR(50) NOT NULL COMMENT '设备种类(数据字典:门磁/空调/智能断路器/人行道闸/车辆道闸/485钥匙柜/网络钥匙柜/紧急报警按钮/红外报警器/门禁一体机/除湿_恒湿机/空调控制器/烟雾报警器/气体报警器/温湿度变送器/摄像机/硬盘录像机/动环采集器)', + DeviceGroup NVARCHAR(20) NOT NULL COMMENT '设备分组(数据字典:视频设备/IoT设备/门禁设备/道闸设备/报警设备)', + PointId INT NULL COMMENT '所属点位ID', + GatewayNodeId INT NULL COMMENT '所属网关节点ID', + IsParent NVARCHAR(20) NOT NULL DEFAULT '否' COMMENT '是否父设备(数据字典:是/否)', + ParentDeviceId INT NULL COMMENT '父设备ID(自引用,子设备挂父设备下)', + IsOnline NVARCHAR(20) NOT NULL DEFAULT '离线' COMMENT '在线状态(数据字典:在线/离线)', + IpAddress NVARCHAR(50) COMMENT 'IP地址', + Port INT COMMENT '端口', + Location NVARCHAR(200) COMMENT '安装位置', + Lat DOUBLE COMMENT '纬度', + Lng DOUBLE COMMENT '经度', + MapModelId NVARCHAR(100) COMMENT '三维地图模型ID', + MapModelScale FLOAT DEFAULT 1.0 COMMENT '模型缩放比例', + MapModelRotation NVARCHAR(100) COMMENT '模型旋转角度(JSON)', + ExtraData TEXT COMMENT '适配器扩展数据JSON(Owl/MC4/门禁字段均存于此)', + LastSyncTime DATETIME COMMENT '上次同步时间', + Enable NVARCHAR(20) DEFAULT '启用' COMMENT '启用状态(数据字典:启用/禁用)', + Remark NVARCHAR(500) COMMENT '备注', + CreateID INT COMMENT '创建人ID', + Creator NVARCHAR(50) COMMENT '创建人', + CreateDate DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + ModifyID INT COMMENT '修改人ID', + Modifier NVARCHAR(50) COMMENT '修改人', + ModifyDate DATETIME COMMENT '修改时间', + PRIMARY KEY (DeviceId), + INDEX IX_Sync (AdapterCode, SourceId), + INDEX IX_Point (PointId), + INDEX IX_Parent (ParentDeviceId), + INDEX IX_Gateway (GatewayNodeId), + INDEX IX_Group (DeviceGroup) +) COMMENT '统一设备主表'; --- 2. 视频设备扩展表 -CREATE TABLE IF NOT EXISTS Device_Video_Ext ( - ExtId CHAR(36) NOT NULL PRIMARY KEY, - DeviceId CHAR(36) NOT NULL, - OwlDeviceId NVARCHAR(64) NOT NULL, - Protocol INT DEFAULT 1, - Manufacturer NVARCHAR(100), - Model NVARCHAR(100), - ChannelCount INT DEFAULT 0, - OwlStatus NVARCHAR(500), - CreateDate DATETIME DEFAULT CURRENT_TIMESTAMP, - UNIQUE INDEX IX_VideoExt_Owl (OwlDeviceId), - INDEX IX_VideoExt_Device (DeviceId) -); +-- ============================================ +-- 2. 视频通道表 +-- DeviceId(INT) → base_device.DeviceId +-- ============================================ +DROP TABLE IF EXISTS video_channel; +CREATE TABLE video_channel ( + ChannelId INT AUTO_INCREMENT COMMENT '通道记录ID', + OwlChannelId NVARCHAR(64) NOT NULL COMMENT 'Owl系统通道ID', + DeviceId INT NOT NULL COMMENT '关联Base_Device设备ID', + OwlStreamApp NVARCHAR(50) COMMENT 'Owl流应用名', + OwlStreamName NVARCHAR(100) COMMENT 'Owl流名称', + HasPtz TINYINT DEFAULT 0 COMMENT '是否支持云台', + HasRecording TINYINT DEFAULT 0 COMMENT '是否支持录像', + RecordMode INT DEFAULT 0 COMMENT '录像模式', + SnapshotUrl NVARCHAR(500) COMMENT '快照地址', + CreateDate DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (ChannelId), + INDEX IX_Device (DeviceId), + INDEX IX_Owl (OwlChannelId) +) COMMENT '视频通道表'; --- 3. 视频通道表 -CREATE TABLE IF NOT EXISTS Video_Channel ( - ChannelId CHAR(36) NOT NULL PRIMARY KEY, - OwlChannelId NVARCHAR(64) NOT NULL, - DeviceId CHAR(36) NOT NULL, - ChannelName NVARCHAR(100) NOT NULL, - ChannelNo INT DEFAULT 0, - OwlStreamApp NVARCHAR(50), - OwlStreamName NVARCHAR(100), - HasPtz TINYINT DEFAULT 0, - HasRecording TINYINT DEFAULT 0, - RecordMode INT DEFAULT 0, - IsOnline TINYINT DEFAULT 0, - SnapshotUrl NVARCHAR(500), - Location NVARCHAR(200), - Lat DOUBLE, - Lng DOUBLE, - Enable TINYINT DEFAULT 1, - CreateDate DATETIME DEFAULT CURRENT_TIMESTAMP, - UNIQUE INDEX IX_Channel_Owl (OwlChannelId), - INDEX IX_Channel_Device (DeviceId) -); +-- ============================================ +-- 3. 录像记录表 +-- ChannelId(INT) → video_channel.ChannelId +-- ============================================ +DROP TABLE IF EXISTS video_record; +CREATE TABLE video_record ( + RecordId INT AUTO_INCREMENT COMMENT '录像记录ID', + ChannelId INT NOT NULL COMMENT '关联通道ID', + OwlRecordId INT NOT NULL COMMENT 'Owl录像记录ID', + App NVARCHAR(50) COMMENT '应用名', + Stream NVARCHAR(100) COMMENT '流ID', + StartedAt DATETIME NOT NULL COMMENT '录像开始时间', + EndedAt DATETIME COMMENT '录像结束时间', + Duration DOUBLE DEFAULT 0 COMMENT '录像时长(秒)', + FilePath NVARCHAR(500) COMMENT '文件路径', + FileSize BIGINT DEFAULT 0 COMMENT '文件大小(字节)', + CreateDate DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (RecordId), + INDEX IX_Channel (ChannelId), + INDEX IX_Time (StartedAt) +) COMMENT '录像记录表'; --- 4. 录像记录表 -CREATE TABLE IF NOT EXISTS Video_Record ( - RecordId CHAR(36) NOT NULL PRIMARY KEY, - ChannelId CHAR(36) NOT NULL, - OwlRecordId INT NOT NULL, - App NVARCHAR(50), - Stream NVARCHAR(100), - StartedAt DATETIME NOT NULL, - EndedAt DATETIME, - Duration DOUBLE DEFAULT 0, - FilePath NVARCHAR(500), - FileSize BIGINT DEFAULT 0, - CreateDate DATETIME DEFAULT CURRENT_TIMESTAMP, - INDEX IX_Record_Channel (ChannelId), - INDEX IX_Record_Time (StartedAt) -); +-- ============================================ +-- 4. 设备数据归档表 +-- DeviceId(INT) → base_device.DeviceId +-- ============================================ +DROP TABLE IF EXISTS iot_devicedata; +CREATE TABLE iot_devicedata ( + DataId INT AUTO_INCREMENT COMMENT '数据记录ID', + DeviceId INT NOT NULL COMMENT '关联设备ID(子设备/点位)', + PointValue DOUBLE COMMENT '点位数值', + UpdateTime DATETIME NOT NULL COMMENT '数据更新时间', + `Interval` INT DEFAULT 0 COMMENT '采集间隔(毫秒)', + ArchiveType INT DEFAULT 1 COMMENT '归档类型(1小时/2日)', + CreateDate DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (DataId), + INDEX IX_Device (DeviceId), + INDEX IX_Time (CreateDate) +) COMMENT '设备数据归档表'; --- 5. IoT设备扩展表 -CREATE TABLE IF NOT EXISTS Device_IoT_Ext ( - ExtId CHAR(36) NOT NULL PRIMARY KEY, - DeviceId CHAR(36) NOT NULL, - Mc4DeviceId INT NOT NULL, - ObjectType INT, - Tag NVARCHAR(100), - ParentId INT, - Mc4Option NVARCHAR(500), - CreateDate DATETIME DEFAULT CURRENT_TIMESTAMP, - UNIQUE INDEX IX_IoTExt_Mc4 (Mc4DeviceId), - INDEX IX_IoTExt_Device (DeviceId) -); +-- ============================================ +-- 5. 告警记录表(通用) +-- DeviceId(INT) → base_device.DeviceId +-- ============================================ +DROP TABLE IF EXISTS iot_alarm; +CREATE TABLE iot_alarm ( + AlarmId INT AUTO_INCREMENT COMMENT '告警ID', + SourceAlarmId NVARCHAR(100) NOT NULL COMMENT '源系统告警ID', + DeviceId INT NOT NULL COMMENT '关联设备ID', + AlarmType INT DEFAULT 0 COMMENT '告警类型', + AlarmLevel NVARCHAR(20) DEFAULT '提示' COMMENT '告警等级(数据字典:提示/普通/重要/紧急)', + AlarmDesc NVARCHAR(500) COMMENT '告警描述', + AlarmValue DOUBLE COMMENT '触发值', + StartTime DATETIME NOT NULL COMMENT '告警开始时间', + EndTime DATETIME COMMENT '告警结束时间', + ConfirmTime DATETIME COMMENT '确认时间', + ConfirmUser NVARCHAR(50) COMMENT '确认人', + State NVARCHAR(20) DEFAULT '未确认' COMMENT '状态(数据字典:未确认/已确认/已结束)', + AdapterCode NVARCHAR(50) COMMENT '来源适配器', + CreateDate DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (AlarmId), + INDEX IX_Device (DeviceId), + INDEX IX_Source (SourceAlarmId), + INDEX IX_Time (StartTime), + INDEX IX_Level (AlarmLevel) +) COMMENT '告警记录表'; --- 6. 设备点位表 -CREATE TABLE IF NOT EXISTS IoT_DevicePoint ( - PointId CHAR(36) NOT NULL PRIMARY KEY, - DeviceId CHAR(36) 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 TINYINT DEFAULT 0, - Mc4Option NVARCHAR(500), - Enable TINYINT DEFAULT 1, - CreateDate DATETIME DEFAULT CURRENT_TIMESTAMP, - UNIQUE INDEX IX_Point_Mc4 (Mc4DeviceId, PointIndex), - INDEX IX_Point_Device (DeviceId) -); - --- 7. 设备数据归档表(仅存快照,实时不入库) -CREATE TABLE IF NOT EXISTS IoT_DeviceData ( - DataId CHAR(36) NOT NULL PRIMARY KEY, - DeviceId CHAR(36) NOT NULL, - PointId CHAR(36) NOT NULL, - PointValue DOUBLE, - UpdateTime DATETIME NOT NULL, - `Interval` INT DEFAULT 0, - ArchiveType INT DEFAULT 1, - CreateDate DATETIME DEFAULT CURRENT_TIMESTAMP, - INDEX IX_Data_Device (DeviceId), - INDEX IX_Data_Time (CreateDate) -); - --- 8. 告警记录表 -CREATE TABLE IF NOT EXISTS IoT_Alarm ( - AlarmId CHAR(36) NOT NULL PRIMARY KEY, - Mc4AlarmId NVARCHAR(64) NOT NULL, - DeviceId CHAR(36), - PointId CHAR(36), - AlarmType INT DEFAULT 0, - AlarmLevel INT DEFAULT 1, - AlarmDesc NVARCHAR(500), - AlarmValue DOUBLE, - StartTime DATETIME NOT NULL, - EndTime DATETIME, - ConfirmTime DATETIME, - ConfirmUser NVARCHAR(50), - State INT DEFAULT 1, - AdapterCode NVARCHAR(50), - CreateDate DATETIME DEFAULT CURRENT_TIMESTAMP, - UNIQUE INDEX IX_Alarm_Mc4 (Mc4AlarmId), - INDEX IX_Alarm_Device (DeviceId), - INDEX IX_Alarm_Time (StartTime) -); +-- ============================================ +-- 6. 网关节点注册表 +-- ============================================ +DROP TABLE IF EXISTS gateway_nodes; +CREATE TABLE gateway_nodes ( + NodeId INT AUTO_INCREMENT COMMENT '网关节点ID', + NodeCode NVARCHAR(50) NOT NULL COMMENT '网关唯一编码', + NodeName NVARCHAR(100) NOT NULL COMMENT '网关名称', + NodeToken NVARCHAR(100) NOT NULL COMMENT '认证令牌', + AdapterTypes NVARCHAR(200) COMMENT '支持的适配器类型(网关上报)', + BaseUrl NVARCHAR(200) COMMENT '网关自身地址(网关上报)', + LastHeartbeat DATETIME COMMENT '上次心跳时间', + IsOnline NVARCHAR(20) DEFAULT '离线' COMMENT '在线状态(数据字典:在线/离线)', + Enable NVARCHAR(20) DEFAULT '启用' COMMENT '启用状态(数据字典:启用/禁用)', + Remark NVARCHAR(500) COMMENT '备注', + CreateID INT COMMENT '创建人ID', + Creator NVARCHAR(50) COMMENT '创建人', + CreateDate DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + ModifyID INT COMMENT '修改人ID', + Modifier NVARCHAR(50) COMMENT '修改人', + ModifyDate DATETIME COMMENT '修改时间', + PRIMARY KEY (NodeId), + UNIQUE INDEX IX_Code (NodeCode), + INDEX IX_Online (IsOnline) +) COMMENT '网关节点注册表'; diff --git a/doc/设计文档/VolPro框架改造方案.md b/doc/设计文档/VolPro框架改造方案.md new file mode 100644 index 0000000..5fc605c --- /dev/null +++ b/doc/设计文档/VolPro框架改造方案.md @@ -0,0 +1,512 @@ +# Vol.Pro 框架前后端改造方案 + +> **版本**: 1.0 +> **日期**: 2025-05-17 +> **基准**: SecMPS 整合方案 v3.1 + Vol.Pro 框架官方文档 +> **核心原则**: 所有改动必须在 Partial/extension 目录中,严禁修改框架生成代码 + +--- + +## 1. 改造总览 + +### 1.1 改造清单 + +| 层面 | 改造项 | 位置 | 是否破坏可升级性 | +|------|--------|------|:---:| +| 数据库 | 5 张新表 + 字典数据 | SQL 脚本 | ❌ | +| 后端-Entity | 6 个实体 Partial 类 | `DomainModels/*/partial/` | ❌ | +| 后端-Service | 6 个 Service Partial 类 | `Services/*/Partial/` | ❌ | +| 后端-Controller | 3 个 Controller Partial 类 | `Controllers/*/Partial/` | ❌ | +| 后端-Job | 3 个 Quartz 定时任务 | `Warehouse/Services/` | ❌ | +| 后端-Config | 注册网关 HttpClient | `Startup.cs` | ⚠️ 需手动合并 | +| 前端-主表 | 自定义操作列插槽 | `extension/warehouse/` | ❌ | +| 前端-组件 | 5 个设备操作组件 | `views/warehouse/base_device/components/` | ❌ | +| 网关 | GatewayClient HTTP 客户端 | `Warehouse/Services/` | ❌ | + +### 1.2 框架扩展点总览 + +``` +Vol.Pro 框架约定: + 自动生成代码 (代码生成器覆盖) 自定义代码 (不被覆盖) + ───────────────────────────── ────────────────────────── + Controllers/xxxController.cs Controllers/xxx/Partial/xxxController.cs + Services/xxxService.cs Services/xxx/Partial/xxxService.cs + IServices/IxxxService.cs IServices/xxx/Partial/IxxxService.cs + Repositories/xxxRepository.cs (无需自定义) + IRepositories/IxxxRepository.cs (无需自定义) + DomainModels/xxx/xxx.cs DomainModels/xxx/partial/xxx.cs + 前端 views/xxx/xxx.vue extension/xxx.jsx + views/xxx/components/ +``` + +--- + +## 2. 数据库改造 + +### 2.1 新增表 + +执行 `doc/db_init.sql`,创建 5 张表: + +| 表名 | 说明 | 层级 | +|------|------|------| +| `gateway_nodes` | 网关节点 | 顶层 | +| `base_device` | 统一设备主表 | 核心,AdapterCode+SourceId 联合唯一 | +| `video_channel` | 视频通道扩展 | base_device 子表 | +| `video_record` | 录像文件 | video_channel 子表 | +| `iot_alarm` | 告警记录 | base_device 子表 | +| `iot_devicedata` | 数据归档 | base_device 子表 | + +### 2.2 字典初始化 + +Phase 0 需在 Vol.Pro 管理端 → 字典管理 中创建 8 组数据字典: + +| 字典名称 | 字典编号 | 字典值 | +|----------|:---:|------| +| 设备种类 | — | 摄像机/硬盘录像机/温湿度变送器/空调控制器/... (18 项) | +| 设备分组 | device_group | 视频设备/IoT设备/门禁设备/道闸设备/报警设备 | +| 是否父设备 | — | 是/否 | +| 在线状态 | — | 在线/离线 | +| 启用状态 | — | 启用/禁用 | +| 是否控制点 | — | 只读/可写 | +| 告警等级 | — | 提示/普通/重要/紧急 | +| 告警状态 | — | 未确认/已确认/已结束 | + +### 2.3 区-点位-设备 三级关联 + +框架支持主从表三级显示。利用现有表关系: + +``` +warehouse_regions (区域) ← 代码生成器已有 + └── warehouse_devicepoint (点位) ← 代码生成器已有 + └── base_device (设备) ← 新建,用 PointId 关联点位 + ├── video_channel ← 新建子表 + ├── iot_devicedata ← 新建子表 + └── iot_alarm ← 新建子表 +``` + +**关键**:代码生成器配置 base_device 的 `DetailTable` 属性,关联 video_channel/iot_devicedata/iot_alarm 作为子表,框架自动渲染主从表 Tab。 + +--- + +## 3. 后端改造 + +### 3.1 Entity 扩展(Partial 类) + +**原则**:框架生成的 Entity 在 `DomainModels/device_manager/base_device.cs`,不可修改。仅在 `partial/` 中添加。 + +``` +VolPro.Entity/DomainModels/device_manager/partial/ +├── base_device.cs # 添加导航属性 + AdapterCode/SourceId 唯一约束注解 +├── gateway_nodes.cs # 网关特有业务属性 +├── video_channel.cs # 通道流信息缓存 +├── video_record.cs # (留空,框架生成即够用) +├── iot_devicedata.cs # (留空) +└── iot_alarm.cs # 告警确认/结束方法 +``` + +**base_device Partial 示例**: + +```csharp +namespace VolPro.Entity.DomainModels +{ + public partial class base_device + { + // 导航属性(用于主从表) + [Navigate(NavigateType.OneToOne, nameof(DeviceId), nameof(video_channel.DeviceId))] + public video_channel? VideoChannel { get; set; } + + [Navigate(NavigateType.OneToMany, nameof(DeviceId), nameof(iot_alarm.DeviceId))] + public List? Alarms { get; set; } + + [Navigate(NavigateType.OneToMany, nameof(DeviceId), nameof(iot_devicedata.DeviceId))] + public List? DeviceData { get; set; } + + // 网关字段标识(供同步时判断哪些字段可覆盖) + public static readonly HashSet GatewayFields = new() + { + nameof(IsOnline), nameof(IsParent), nameof(ParentDeviceId), + nameof(ExtraData), nameof(IpAddress), nameof(Port), nameof(LastSyncTime) + }; + } +} +``` + +### 3.2 Service 扩展(Partial 类) + +``` +Warehouse/Services/device_manager/Partial/ +├── base_deviceService.cs # GetRegionTree / GetDevicesByPoint / 字典查询辅助 +├── gateway_nodesService.cs # 网关注册/心跳/同步入口 +├── video_channelService.cs # (留空) +├── video_recordService.cs # (留空) +├── iot_devicedataService.cs # (留空) +└── iot_alarmService.cs # (留空) +``` + +**gateway_nodesService Partial**(核心同步入口): + +```csharp +namespace Warehouse.Services +{ + public partial class gateway_nodesService + { + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly Igateway_nodesRepository _repository; + + [ActivatorUtilitiesConstructor] + public gateway_nodesService( + Igateway_nodesRepository dbRepository, + IHttpContextAccessor httpContextAccessor + ) : base(dbRepository) + { + _httpContextAccessor = httpContextAccessor; + _repository = dbRepository; + } + + // 网关注册 (Upsert) — 被 Controller A1 调用 + public async Task RegisterNodeAsync(string nodeCode, string token, string adapterTypes, string baseUrl) + { + // 实现: Upsert 逻辑 + } + + // 心跳更新 — 被 Controller A2 调用 + public async Task UpdateHeartbeatAsync(string nodeCode, string token) + { + // 实现: 更新 LastHeartbeat + IsOnline + } + + // 设备同步 — 被 Controller A3 调用 + public async Task<(int added, int updated)> SyncDevicesAsync(int gatewayNodeId, List devices) + { + // 实现: 字段分治 + parentSourceId 映射 + } + } +} +``` + +### 3.3 Controller 扩展(Partial 类) + +``` +VolPro.WebApi/Controllers/Warehouse/Partial/ +├── base_deviceController.cs # GetRegionTree / GetDevicesByPoint / 操作代理 +├── gateway_nodesController.cs # A1-A4 网关 API +├── video_channelController.cs # (留空) +├── video_recordController.cs # (留空) +├── iot_devicedataController.cs # (留空) +└── iot_alarmController.cs # (留空) +``` + +#### 3.3.1 gateway_nodesController(A 组 API) + +**核心改造**:在框架生成的 `gateway_nodesController` 基础上,Partial 中添加 4 个不受权限控制的网关回调接口。 + +```csharp +namespace Warehouse.Controllers +{ + public partial class gateway_nodesController + { + private readonly IHttpContextAccessor _httpContextAccessor; + + [ActivatorUtilitiesConstructor] + public gateway_nodesController( + Igateway_nodesService service, + IHttpContextAccessor httpContextAccessor + ) : base(service) + { + _httpContextAccessor = httpContextAccessor; + } + + /// A1: 网关注册 (Upsert) + [HttpPost, Route("/api/gateway/register"), AllowAnonymous] + public async Task RegisterGateway([FromBody] GatewayRegisterRequest req) + { + // 实现 + } + + /// A2: 心跳 + [HttpPost, Route("/api/gateway/heartbeat"), AllowAnonymous] + public async Task GatewayHeartbeat([FromBody] GatewayHeartbeatRequest req) + { + // 实现 + } + + /// A3: 设备数据同步 (字段分治) + [HttpPost, Route("/api/gateway/sync/devices"), AllowAnonymous] + public async Task SyncDevices([FromBody] SyncDevicesRequest req) + { + // 实现 + } + + /// A4: 告警同步 + [HttpPost, Route("/api/gateway/sync/alarms"), AllowAnonymous] + public async Task SyncAlarms([FromBody] SyncAlarmsRequest req) + { + // 实现 + } + } +} +``` + +**权限模型**:A 组接口加 `[AllowAnonymous]`,内部通过 `NodeToken` 二次认证。B 组接口走框架 JWT 权限。 + +#### 3.3.2 base_deviceController(B 组代理 + 设备树) + +```csharp +namespace Warehouse.Controllers +{ + public partial class base_deviceController + { + /// 区域→点位→设备树(管理端左侧树形控件) + [HttpGet, Route("/api/DeviceManager/GetRegionTree")] + public async Task GetRegionTree() + { + // SELECT warehouse_regions JOIN warehouse_devicepoint → 树形结构 + // 每个点位下统计 base_device 数量 + } + + /// 点位下设备列表(含子设备) + [HttpGet, Route("/api/DeviceManager/GetDevicesByPoint")] + public async Task GetDevicesByPoint(int pointId, int page = 1, int size = 20) + { + // SELECT base_device WHERE PointId = pointId + // Include 子设备(ParentDeviceId) + } + } +} +``` + +### 3.4 网关 HTTP 客户端(GatewayClient) + +``` +Warehouse/Services/GatewayClient.cs +``` + +```csharp +public class GatewayClient +{ + private readonly IHttpClientFactory _httpFactory; + private readonly IConfiguration _config; + + public GatewayClient(IHttpClientFactory httpFactory, IConfiguration config) + { + _httpFactory = httpFactory; + _config = config; + } + + /// 调网关 B3: 手动触发全量同步 + public async Task TriggerFullSyncAsync(string baseUrl, string adapterTypes) + { + var http = _httpFactory.CreateClient(); + // POST {baseUrl}/api/gateway/devices/sync?adapter={adapterTypes} + } + + /// 调网关 B4: 获取实时点位值 + public async Task> GetRealtimeAsync(string baseUrl, string adapter, string deviceId) + { + // GET {baseUrl}/api/gateway/realtime/{adapter}/{deviceId} + } + + /// 调网关 B5: 设备控制 + public async Task ControlDeviceAsync(string baseUrl, string adapter, string deviceId, int pointIndex, double value) + { + // POST {baseUrl}/api/gateway/realtime/{adapter}/control + } +} +``` + +### 3.5 Quartz 定时任务 + +| Job | 触发器 | 职责 | +|-----|:---:|------| +| `SyncDevicesJob` | 每 5 分钟 | 遍历所有在线网关 → 调 GatewayClient.TriggerFullSyncAsync() | +| `RealtimePollJob` | 每 10 秒 | 轮询 MC4.0 IoT 设备实时值 → 更新 iot_devicedata | +| `HeartbeatMonitorJob` | 每 15 秒 | 扫描 gateway_nodes 心跳超时 → 标记离线 + 级联设备离线 | + +**注册方式**:在 Vol.Pro 管理端 → Quartz 任务管理 → 新建任务,指定 Job 类全名。 + +### 3.6 Startup.cs 注册 + +```csharp +// 在 Startup.cs 或 Program.cs 中注册 +builder.Services.AddHttpClient("VolPro", c => +{ + c.Timeout = TimeSpan.FromSeconds(30); +}); +builder.Services.AddSingleton(); +``` + +--- + +## 4. 前端改造 + +### 4.1 架构决策 + +| 方案 | 描述 | 是否破坏可升级性 | +|------|------|:---:| +| ❌ 修改生成的 .vue | 直接改 views/warehouse/base_device/ 下框架生成文件 | ✅ 破坏 | +| ✅ extension + components | 通过扩展注入 + 自定义组件实现操作列 | ❌ 不破坏 | + +### 4.2 自定义操作列插槽 + +框架生成的 base_device 列表页面默认操作列只有"编辑/删除"。通过 **前端扩展文件** 自定义替换: + +``` +web.vite/src/extension/warehouse/base_device.jsx +``` + +```javascript +// 自定义操作列渲染 +import VideoDeviceActions from '@/views/warehouse/base_device/components/VideoDeviceActions.vue' +import IoTDeviceActions from '@/views/warehouse/base_device/components/IoTDeviceActions.vue' + +// 注册自定义组件 +export default { + components: { VideoDeviceActions, IoTDeviceActions }, + + // 替换框架默认操作列 + slots: { + // 操作列插槽 + 'col-action': (h, { row }) => { + const comp = row.deviceGroup === '视频设备' ? 'VideoDeviceActions' + : row.deviceGroup === 'IoT设备' ? 'IoTDeviceActions' + : null + if (comp) return h(comp, { props: { row } }) + // fallback 到框架默认按钮 + return null + } + } +} +``` + +### 4.3 组件目录 + +``` +web.vite/src/views/warehouse/base_device/components/ +├── VideoDeviceActions.vue # 实时预览/云台/回放/快照/同步通道 +├── IoTDeviceActions.vue # 实时数据/控制/刷新/告警 +├── AccessDeviceActions.vue # 远程开门 (Phase 3) +├── BarrierDeviceActions.vue # 抬杆/落杆 (Phase 3) +├── AlarmDeviceActions.vue # 告警/布防撤防 (Phase 3) +├── DeviceLivePreview.vue # Jessibuca 播放器弹窗 +├── PtzControlPanel.vue # ↑↓←→+ZOOM+停止 +├── RealtimeDataPanel.vue # 实时点位值表格弹窗 +├── DeviceControlPanel.vue # 控制写值面板 +├── DeviceEditDialog.vue # 设备编辑弹窗 (扩展字段) +└── MapBindingPanel.vue # VgoMap 模型绑定 +``` + +### 4.4 组件设计要点 + +**VideoDeviceActions.vue**(核心组件): + +``` +Props: row (base_device) +按钮组: + [实时预览] → 打开 DeviceLivePreview.vue 弹窗 + → GET /api/gateway/streams/{adapterCode}/{sourceId}/live → 流地址 + → 内嵌 Jessibuca 播放器(fallback: