Files
SecMPS/doc/设计文档/VolPro框架改造方案.md

18 KiB
Raw Blame History

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 示例

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<iot_alarm>? Alarms { get; set; }

        [Navigate(NavigateType.OneToMany, nameof(DeviceId), nameof(iot_devicedata.DeviceId))]
        public List<iot_devicedata>? DeviceData { get; set; }

        // 网关字段标识(供同步时判断哪些字段可覆盖)
        public static readonly HashSet<string> 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(核心同步入口):

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<gateway_nodes> 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<StandardDevice> 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_nodesControllerA 组 API

核心改造:在框架生成的 gateway_nodesController 基础上Partial 中添加 4 个不受权限控制的网关回调接口。

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;
        }

        /// <summary>A1: 网关注册 (Upsert)</summary>
        [HttpPost, Route("/api/gateway/register"), AllowAnonymous]
        public async Task<IActionResult> RegisterGateway([FromBody] GatewayRegisterRequest req)
        {
            // 实现
        }

        /// <summary>A2: 心跳</summary>
        [HttpPost, Route("/api/gateway/heartbeat"), AllowAnonymous]
        public async Task<IActionResult> GatewayHeartbeat([FromBody] GatewayHeartbeatRequest req)
        {
            // 实现
        }

        /// <summary>A3: 设备数据同步 (字段分治)</summary>
        [HttpPost, Route("/api/gateway/sync/devices"), AllowAnonymous]
        public async Task<IActionResult> SyncDevices([FromBody] SyncDevicesRequest req)
        {
            // 实现
        }

        /// <summary>A4: 告警同步</summary>
        [HttpPost, Route("/api/gateway/sync/alarms"), AllowAnonymous]
        public async Task<IActionResult> SyncAlarms([FromBody] SyncAlarmsRequest req)
        {
            // 实现
        }
    }
}

权限模型A 组接口加 [AllowAnonymous],内部通过 NodeToken 二次认证。B 组接口走框架 JWT 权限。

3.3.2 base_deviceControllerB 组代理 + 设备树)

namespace Warehouse.Controllers
{
    public partial class base_deviceController
    {
        /// <summary>区域→点位→设备树(管理端左侧树形控件)</summary>
        [HttpGet, Route("/api/DeviceManager/GetRegionTree")]
        public async Task<IActionResult> GetRegionTree()
        {
            // SELECT warehouse_regions JOIN warehouse_devicepoint → 树形结构
            // 每个点位下统计 base_device 数量
        }

        /// <summary>点位下设备列表(含子设备)</summary>
        [HttpGet, Route("/api/DeviceManager/GetDevicesByPoint")]
        public async Task<IActionResult> 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
public class GatewayClient
{
    private readonly IHttpClientFactory _httpFactory;
    private readonly IConfiguration _config;

    public GatewayClient(IHttpClientFactory httpFactory, IConfiguration config)
    {
        _httpFactory = httpFactory;
        _config = config;
    }

    /// <summary>调网关 B3: 手动触发全量同步</summary>
    public async Task TriggerFullSyncAsync(string baseUrl, string adapterTypes)
    {
        var http = _httpFactory.CreateClient();
        // POST {baseUrl}/api/gateway/devices/sync?adapter={adapterTypes}
    }

    /// <summary>调网关 B4: 获取实时点位值</summary>
    public async Task<List<PointValue>> GetRealtimeAsync(string baseUrl, string adapter, string deviceId)
    {
        // GET {baseUrl}/api/gateway/realtime/{adapter}/{deviceId}
    }

    /// <summary>调网关 B5: 设备控制</summary>
    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 注册

// 在 Startup.cs 或 Program.cs 中注册
builder.Services.AddHttpClient("VolPro", c =>
{
    c.Timeout = TimeSpan.FromSeconds(30);
});
builder.Services.AddSingleton<GatewayClient>();

4. 前端改造

4.1 架构决策

方案 描述 是否破坏可升级性
修改生成的 .vue 直接改 views/warehouse/base_device/ 下框架生成文件 破坏
extension + components 通过扩展注入 + 自定义组件实现操作列 不破坏

4.2 自定义操作列插槽

框架生成的 base_device 列表页面默认操作列只有"编辑/删除"。通过 前端扩展文件 自定义替换:

web.vite/src/extension/warehouse/base_device.jsx
// 自定义操作列渲染
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: <video> 标签 + flv.js
  
  [云台控制] → 打开 PtzControlPanel.vue 弹窗
    → POST /api/gateway/streams/{adapterCode}/{sourceId}/ptz
    → 仅方向键: ↑↓←→ + ZOOM +/- + 停止
    → mousedown 开始移动, mouseup 发送停止
  
  [查看回放] → 打开录像时间轴弹窗
    → GET /api/gateway/streams/{adapterCode}/{sourceId}/playback?start=&end=
  
  [获取快照] → 下载快照图片
  
  [同步通道] → 触发网关 B3 重新同步此 NVR 的通道

限制:
  - channel 设备显示前 4 个按钮(无"同步通道"
  - NVR(IsParent=是) 显示全部 5 个按钮

IoTDeviceActions.vue

[查看实时数据] → GET /api/gateway/realtime/{adapterCode}/{sourceId}
  → 仪表盘或数值显示(温度/湿度/电压...
  → 5s 自动轮询

[设备控制] → 仅 isControlPoint=true 的设备显示
  → 空调温度设定/开关/模式切换

[刷新点位] → 强制刷新当前点位所有设备数据

[查看告警] → GET /api/gateway/alarms/{adapterCode}
  → 跳转或内嵌告警列表

4.5 API 封装

web.vite/src/api/deviceManager.js    # (已存在于框架中)
web.vite/src/api/gateway.js          # 新增:网关 B 组接口封装
// gateway.js
import request from '@/uitils/request'

// 基础: 直连网关
const gwBase = 'http://localhost:5100/api/gateway'

export const getStreamUrl = (adapter, deviceId) =>
  request({ url: `${gwBase}/streams/${adapter}/${deviceId}/live`, method: 'get' })

export const ptzControl = (adapter, deviceId, direction, speed = 0.5) =>
  request({ url: `${gwBase}/streams/${adapter}/${deviceId}/ptz`, method: 'post', data: { direction, speed } })

export const getRealtime = (adapter, deviceId) =>
  request({ url: `${gwBase}/realtime/${adapter}/${deviceId}`, method: 'get' })

export const controlDevice = (adapter, deviceId, pointIndex, value) =>
  request({ url: `${gwBase}/realtime/${adapter}/control`, method: 'post', data: { deviceId, pointIndex, value } })

export const getAlarms = (adapter, params) =>
  request({ url: `${gwBase}/alarms/${adapter}`, method: 'get', params })

export const confirmAlarm = (adapter, alarmId) =>
  request({ url: `${gwBase}/alarms/${adapter}/${alarmId}/confirm`, method: 'post' })

前端直连网关(:5100跨域问题通过 Vol.Pro nginx 反向代理 /api/gateway/*http://localhost:5100 解决,或网关配置 CORS。


5. 可升级性保障清单

检查项 状态
不修改框架生成的 C# 基础类
所有自定义代码在 Partial 目录
不修改框架生成的 .vue 文件
前端业务逻辑在 extension 目录
新增组件在 views/xxx/components/ 下
不修改框架 NuGet 包版本
网关调用通过独立 GatewayClient 类
A 组 API 用 [AllowAnonymous] + Token 二次认证
Dictionary/Entity 属性通过代码生成器配置,不手写
Startup.cs 注册仅用 AddHttpClient/AddSingleton

6. 实施顺序

步骤 内容 依赖
1 代码生成器生成 device_manager 模块 6 张表 Phase 0
2 创建 Entity Partial 类 步骤 1
3 创建 Service Partial 类 步骤 1
4 创建 Controller Partial 类 (A1-A4 + GetRegionTree) 步骤 3
5 注册 GatewayClient + HttpClient 步骤 4
6 创建 Quartz Job (3 个) 步骤 5
7 创建前端组件 (5 个按钮组 + 5 个弹窗) 步骤 4
8 编写 extension/warehouse/base_device.jsx 步骤 7
9 nginx 配置网关反向代理 步骤 8

版本历史:

  • v1.0 (2025-05-17) — 初版,基于整合方案 v3.1 + Vol.Pro 框架规范