From 7550903aa86f091a557f51a6536cc6dc54e9ca54 Mon Sep 17 00:00:00 2001 From: g82tt Date: Sun, 17 May 2026 03:26:37 +0800 Subject: [PATCH] =?UTF-8?q?VolPro=E6=A1=86=E6=9E=B6=E5=89=8D=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E6=94=B9=E9=80=A0=E6=96=B9=E6=A1=88v1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/设计文档/VolPro框架改造方案.md | 512 +++++++++++++++++++++++++++++ 1 file changed, 512 insertions(+) create mode 100644 doc/设计文档/VolPro框架改造方案.md 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: