VolPro框架前后端改造方案v1.0

This commit is contained in:
2026-05-17 03:26:37 +08:00
parent 5fd71a20b9
commit 7550903aa8

View File

@@ -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<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**(核心同步入口):
```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<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 个不受权限控制的网关回调接口。
```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;
}
/// <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 组代理 + 设备树)
```csharp
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
```
```csharp
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 注册
```csharp
// 在 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
```
```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: <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 组接口封装
```
```javascript
// 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 框架规范