Initial_commit_SecMPS_v2
This commit is contained in:
878
doc/整合方案/Vol.Pro_Owl_ZLMediaKit_整合方案_v1.0.md
Normal file
878
doc/整合方案/Vol.Pro_Owl_ZLMediaKit_整合方案_v1.0.md
Normal file
@@ -0,0 +1,878 @@
|
||||
# Vol.Pro + Owl + ZLMediaKit 视频监控整合方案
|
||||
|
||||
> **版本**: v1.0
|
||||
> **日期**: 2026-04-29
|
||||
> **编制**: 浮浮酱
|
||||
> **状态**: 待确认(未实施)
|
||||
|
||||
---
|
||||
|
||||
## 一、项目概述
|
||||
|
||||
### 1.1 需求背景
|
||||
|
||||
现有项目包含四个模块:
|
||||
|
||||
| 模块 | 技术栈 | 用途 |
|
||||
|------|--------|------|
|
||||
| **api_sqlsugar** | .NET 8 + SqlSugar + Vol.Pro框架 | 后端API服务 |
|
||||
| **web.vite** | Vue3 + Vite + TypeScript | Vol.Pro管理端(管理后台) |
|
||||
| **warehouse** | Vue3 + 自定义前端 | 用户端(大屏展示/业务端) |
|
||||
| **owl_zlmediakit** | Docker + Owl + ZLMediaKit | GB28181视频监控平台 |
|
||||
|
||||
**目标**: 在Vol.Pro管理端中管理NVR和摄像机(增删改查),通过Vol.Pro后端对接Owl获取实时流/回放流地址,向两个前端(管理端+用户端)提供播放接口,实现浏览器中的实时监控、历史回放和云台控制。
|
||||
|
||||
### 1.2 核心问题
|
||||
|
||||
1. warehouse已有视频UI框架(`Live.vue`、`History.vue`、`VideoWall.vue`),但**无实际播放器集成**,数据为硬编码模拟数据
|
||||
2. Vol.Pro后端**尚无摄像机/NVR相关实体和控制器**
|
||||
3. Owl与Vol.Pro的**数据模型不兼容**,需要中间层转换
|
||||
4. 播放器选型(浏览器播放GB28181流的方案)
|
||||
|
||||
### 1.3 关键前提:所有修改独立于框架代码
|
||||
|
||||
> **经过对Vol.Pro框架源码和文档的调研,确认:所有自定义代码均可完全独立于框架自动生成代码进行开发和维护。**
|
||||
|
||||
Vol.Pro框架的设计核心原则是:**"自动生成代码不修改,自定义代码写Partial/扩展文件"**。具体体现在:
|
||||
|
||||
| 层级 | 自动生成文件位置 | 扩展文件位置 | 扩展方式 |
|
||||
|------|-----------------|-------------|----------|
|
||||
| **Controller** | `Controllers/模块/表名Controller.cs` | `Controllers/模块/Partial/表名Controller.cs` | `partial class` |
|
||||
| **Service** | `Services/模块/表名Service.cs` | `Services/模块/Partial/表名Service.cs` | `partial class` + override/委托钩子 |
|
||||
| **Entity** | `DomainModels/模块/表名.cs` | `DomainModels/模块/partial/表名.cs` | `partial class` + `[NotMapped]` |
|
||||
| **前端(Vue)** | `views/模块/表名.vue` + `options.js` | `extension/模块/表名.jsx` 或 `.vue` | 扩展组件/自定义逻辑 |
|
||||
| **独立服务** | — | 新建文件,实现`IDependency` | Autofac自动IOC注入 |
|
||||
|
||||
**框架生成的文件中明确注释**:
|
||||
> *"代码由框架生成,任何更改都可能导致被代码生成器覆盖。如果要增加方法请在当前目录下Partial文件夹编写。"*
|
||||
|
||||
**结论**:
|
||||
- 代码生成器重新生成时,**只会覆盖自动生成的文件**,不会触碰`Partial/`、`partial/`、`extension/`目录下的自定义代码
|
||||
- 所有视频监控相关的业务代码(Owl对接、取流、回放、云台)将写在扩展文件中,与框架代码完全隔离
|
||||
- 新增的服务(如`OwlApiService`)通过实现`IDependency`接口由Autofac自动注册,无需修改框架的任何配置
|
||||
- **框架升级时,只需重新运行代码生成器覆盖自动生成的CRUD文件,所有自定义业务逻辑完全保留**
|
||||
|
||||
---
|
||||
|
||||
## 二、系统架构设计
|
||||
|
||||
### 2.1 整体架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ 客户端层 │
|
||||
│ ┌─────────────────────┐ ┌─────────────────────┐ │
|
||||
│ │ web.vite 管理端 │ │ warehouse 用户端 │ │
|
||||
│ │ (设备管理+视频查看) │ │ (大屏+实时监控墙) │ │
|
||||
│ └──────────┬──────────┘ └──────────┬──────────┘ │
|
||||
└─────────────┼──────────────────────────┼────────────────────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ Vol.Pro 后端 (api_sqlsugar) │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 自动生成CRUD模块 (VideoDevice / VideoChannel / VideoRecord) │ │
|
||||
│ │ ├─ 设备管理 (增删改查) │ │
|
||||
│ │ ├─ 通道管理 (增删改查) │ │
|
||||
│ │ └─ 录像管理 (查询) │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 扩展API模块 (VideoStreamController) │ │
|
||||
│ │ ├─ POST /api/VideoStream/live → 获取实时流地址 │ │
|
||||
│ │ ├─ POST /api/VideoStream/playback → 获取回放流地址 │ │
|
||||
│ │ ├─ POST /api/VideoStream/ptz → 云台控制 │ │
|
||||
│ │ ├─ POST /api/VideoStream/snapshot → 获取快照 │ │
|
||||
│ │ └─ POST /api/VideoStream/stop → 停止播放 │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Owl对接服务 (OwlApiService) │ │
|
||||
│ │ ├─ 设备同步 (定时从Owl拉取设备列表) │ │
|
||||
│ │ ├─ JWT Token管理 (缓存/刷新) │ │
|
||||
│ │ ├─ 流地址获取 (调用Owl /channels/:id/play) │ │
|
||||
│ │ ├─ 回放查询 (调用Owl /recordings) │ │
|
||||
│ │ └─ 云台控制 (调用Owl /channels/:id/ptz/control) │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────┬──────────────────────────────────────────┘
|
||||
│ HTTP API
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ Owl (GoWVP) 平台 │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ SIP信令层 (GB28181设备注册/心跳/控制) │ │
|
||||
│ │ HTTP API层 (设备/通道/播放/回放/云台) │ │
|
||||
│ │ WebHook层 (接收ZLM事件回调) │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────┬──────────────────────────────────────────┘
|
||||
│ HTTP API / WebHook
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ ZLMediaKit 流媒体服务器 │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ RTP收流 / RTMP推流 / RTSP拉流 │ │
|
||||
│ │ 协议转换 (HLS/FLV/WebRTC/RTMP/RTSP) │ │
|
||||
│ │ 录像存储 (MP4切片) │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 关键设计原则
|
||||
|
||||
1. **Owl作为主设备源**: Owl负责GB28181设备接入、SIP信令、流管理,Vol.Pro不直接对接设备,只对接Owl的HTTP API
|
||||
2. **数据双写**: 设备在Owl中注册后,通过同步机制将设备信息写入Vol.Pro数据库,实现Vol.Pro管理端对设备的CRUD管理
|
||||
3. **播放地址代理**: Vol.Pro后端作为统一入口,前端不直接访问Owl,所有取流请求经Vol.Pro转发(便于权限控制和日志记录)
|
||||
4. **播放器统一**: 两个前端使用同一套播放器组件,支持FLV/HLS/WebRTC多协议
|
||||
|
||||
---
|
||||
|
||||
## 三、数据库表设计
|
||||
|
||||
### 3.1 表结构说明
|
||||
|
||||
> **设备统一管理设计**:本方案与MC4.0采集网关方案共用统一设备主表 `Base_Device`,实现视频监控设备和采集网关设备的统一管理,并支持在三维地图上统一标记展示。
|
||||
|
||||
#### 3.1.0 统一设备主表 (Base_Device) 【与MC4.0方案共用】
|
||||
|
||||
| 字段名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| 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_Video_Ext)
|
||||
|
||||
> 存储视频监控设备特有的扩展信息,通过DeviceId关联Base_Device主表。
|
||||
|
||||
| 字段名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| ExtId | uniqueidentifier | ✅ | 主键 |
|
||||
| DeviceId | uniqueidentifier | ✅ | 关联Base_Device.DeviceId |
|
||||
| OwlDeviceId | nvarchar(64) | ✅ | Owl系统设备ID(GB28181的DeviceID) |
|
||||
| Protocol | int | | 接入协议:1=GB28181, 2=ONVIF, 3=RTMP, 4=RTSP |
|
||||
| Manufacturer | nvarchar(100) | | 厂商 |
|
||||
| Model | nvarchar(100) | | 型号 |
|
||||
| ChannelCount | int | | 通道数量 |
|
||||
| OwlStatus | nvarchar(50) | | Owl中的原始状态JSON |
|
||||
|
||||
#### 3.1.2 视频通道表 (Video_Channel)
|
||||
|
||||
| 字段名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| ChannelId | uniqueidentifier | ✅ | 主键,Vol.Pro系统内部ID |
|
||||
| OwlChannelId | nvarchar(64) | ✅ | Owl系统通道ID |
|
||||
| DeviceId | uniqueidentifier | ✅ | 关联Video_Device.DeviceId |
|
||||
| ChannelName | nvarchar(100) | ✅ | 通道名称 |
|
||||
| ChannelNo | int | | 通道编号 |
|
||||
| OwlStreamApp | nvarchar(50) | | Owl中的stream app(如rtp) |
|
||||
| OwlStreamName | nvarchar(100) | | Owl中的stream name |
|
||||
| HasPtz | int | | 是否支持云台:0=否, 1=是 |
|
||||
| HasRecording | int | | 是否支持录像:0=否, 1=是 |
|
||||
| RecordMode | int | | 录像模式:0=不录像, 1=设备录像(SD卡), 2=中心录像(Owl云录像) |
|
||||
| IsOnline | int | | 是否在线 |
|
||||
| SnapshotUrl | nvarchar(500) | | 快照图片URL |
|
||||
| Location | nvarchar(200) | | 安装位置 |
|
||||
| Lat | float | | 纬度 |
|
||||
| Lng | float | | 经度 |
|
||||
| Enable | int | | 启用状态 |
|
||||
| Remark | nvarchar(500) | | 备注 |
|
||||
| CreateID / Creator / CreateDate | | | 标准审计字段 |
|
||||
| ModifyID / Modifier / ModifyDate | | | 标准审计字段 |
|
||||
|
||||
#### 3.1.3 云录像记录表 (Video_Record)
|
||||
|
||||
| 字段名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| RecordId | uniqueidentifier | ✅ | 主键 |
|
||||
| OwlRecordId | nvarchar(64) | ✅ | Owl录像记录ID |
|
||||
| ChannelId | uniqueidentifier | ✅ | 关联Video_Channel.ChannelId |
|
||||
| StartTime | datetime | ✅ | 开始时间 |
|
||||
| EndTime | datetime | ✅ | 结束时间 |
|
||||
| Duration | int | | 时长(秒) |
|
||||
| FileSize | bigint | | 文件大小(字节) |
|
||||
| FilePath | nvarchar(500) | | 文件路径 |
|
||||
| RecordType | int | | 录像类型:1=定时录像, 2=告警录像, 3=手动录像 |
|
||||
| ThumbUrl | nvarchar(500) | | 缩略图URL |
|
||||
| CreateDate | datetime | | 创建时间 |
|
||||
|
||||
### 3.2 建表SQL (SQL Server)
|
||||
|
||||
```sql
|
||||
-- ============================================
|
||||
-- 统一设备主表(与MC4.0采集网关方案共用)
|
||||
-- ============================================
|
||||
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_Video_Ext (
|
||||
ExtId UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
|
||||
DeviceId UNIQUEIDENTIFIER NOT NULL,
|
||||
OwlDeviceId NVARCHAR(64) NOT NULL,
|
||||
Protocol INT DEFAULT 1, -- 1=GB28181, 2=ONVIF, 3=RTMP, 4=RTSP
|
||||
Manufacturer NVARCHAR(100),
|
||||
Model NVARCHAR(100),
|
||||
ChannelCount INT DEFAULT 0,
|
||||
OwlStatus NVARCHAR(50),
|
||||
FOREIGN KEY (DeviceId) REFERENCES Base_Device(DeviceId)
|
||||
);
|
||||
CREATE INDEX IX_Device_Video_Ext_DeviceId ON Device_Video_Ext(DeviceId);
|
||||
CREATE INDEX IX_Device_Video_Ext_OwlDeviceId ON Device_Video_Ext(OwlDeviceId);
|
||||
|
||||
-- 视频通道表
|
||||
CREATE TABLE Video_Channel (
|
||||
ChannelId UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
|
||||
OwlChannelId NVARCHAR(64) NOT NULL,
|
||||
DeviceId UNIQUEIDENTIFIER NOT NULL,
|
||||
ChannelName NVARCHAR(100) NOT NULL,
|
||||
ChannelNo INT DEFAULT 1,
|
||||
OwlStreamApp NVARCHAR(50),
|
||||
OwlStreamName NVARCHAR(100),
|
||||
HasPtz INT DEFAULT 0,
|
||||
HasRecording INT DEFAULT 0,
|
||||
RecordMode INT DEFAULT 0,
|
||||
IsOnline INT DEFAULT 0,
|
||||
SnapshotUrl NVARCHAR(500),
|
||||
Enable INT DEFAULT 1,
|
||||
Remark NVARCHAR(500),
|
||||
CreateID INT,
|
||||
Creator NVARCHAR(30),
|
||||
CreateDate DATETIME DEFAULT GETDATE(),
|
||||
ModifyID INT,
|
||||
Modifier NVARCHAR(30),
|
||||
ModifyDate DATETIME,
|
||||
FOREIGN KEY (DeviceId) REFERENCES Base_Device(DeviceId)
|
||||
);
|
||||
CREATE INDEX IX_Video_Channel_DeviceId ON Video_Channel(DeviceId);
|
||||
CREATE INDEX IX_Video_Channel_OwlChannelId ON Video_Channel(OwlChannelId);
|
||||
CREATE INDEX IX_Video_Channel_IsOnline ON Video_Channel(IsOnline);
|
||||
|
||||
-- 云录像记录表
|
||||
CREATE TABLE Video_Record (
|
||||
RecordId UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
|
||||
OwlRecordId NVARCHAR(64) NOT NULL,
|
||||
ChannelId UNIQUEIDENTIFIER NOT NULL,
|
||||
StartTime DATETIME NOT NULL,
|
||||
EndTime DATETIME NOT NULL,
|
||||
Duration INT,
|
||||
FileSize BIGINT,
|
||||
FilePath NVARCHAR(500),
|
||||
RecordType INT DEFAULT 1,
|
||||
ThumbUrl NVARCHAR(500),
|
||||
CreateDate DATETIME DEFAULT GETDATE()
|
||||
);
|
||||
CREATE INDEX IX_Video_Record_ChannelId ON Video_Record(ChannelId);
|
||||
CREATE INDEX IX_Video_Record_StartTime ON Video_Record(StartTime);
|
||||
CREATE INDEX IX_Video_Record_EndTime ON Video_Record(EndTime);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、Vol.Pro后端扩展方案
|
||||
|
||||
### 4.1 代码生成步骤
|
||||
|
||||
使用Vol.Pro代码生成器自动生成3个模块的CRUD代码:
|
||||
|
||||
1. **Base_Device**(统一设备主表,DeviceCategory=1表示视频监控设备)
|
||||
2. **Device_Video_Ext**(视频设备扩展信息)
|
||||
3. **Video_Channel**(视频通道)
|
||||
4. **Video_Record**(云录像)
|
||||
|
||||
生成后获得:
|
||||
- `Base_DeviceController` / `Base_DeviceService` / `IBase_DeviceService` / `Base_Device`实体
|
||||
- `Device_Video_ExtController` / `Device_Video_ExtService` / `IDevice_Video_ExtService` / `Device_Video_Ext`实体
|
||||
- `Video_ChannelController` / `Video_ChannelService` / `IVideo_ChannelService` / `Video_Channel`实体
|
||||
- `Video_RecordController` / `Video_RecordService` / `IVideo_RecordService` / `Video_Record`实体
|
||||
|
||||
### 4.2 Owl对接服务 (OwlApiService)
|
||||
|
||||
在Vol.Pro后端创建**完全独立**的自定义服务,封装对Owl的所有HTTP调用:
|
||||
|
||||
```csharp
|
||||
// 文件位置:api_sqlsugar/Warehouse/Services/Owl/OwlApiService.cs
|
||||
// (新建文件,不修改任何框架代码)
|
||||
|
||||
/// <summary>
|
||||
/// Owl API 对接服务
|
||||
/// </summary>
|
||||
public interface IOwlApiService : IDependency
|
||||
{
|
||||
/// <summary>获取Owl JWT Token(带缓存)</summary>
|
||||
Task<string> GetTokenAsync();
|
||||
|
||||
/// <summary>同步Owl设备列表到本地</summary>
|
||||
Task<WebResponseContent> SyncDevicesAsync();
|
||||
|
||||
/// <summary>同步Owl通道列表到本地</summary>
|
||||
Task<WebResponseContent> SyncChannelsAsync();
|
||||
|
||||
/// <summary>获取实时流播放地址</summary>
|
||||
Task<StreamPlayResult> GetLiveUrlAsync(string owlChannelId, string protocol = "ws_flv");
|
||||
|
||||
/// <summary>获取回放流地址</summary>
|
||||
Task<StreamPlaybackResult> GetPlaybackUrlAsync(string owlChannelId, DateTime start, DateTime end);
|
||||
|
||||
/// <summary>云台控制</summary>
|
||||
Task<WebResponseContent> PtzControlAsync(string owlChannelId, PtzCommand command);
|
||||
|
||||
/// <summary>获取快照</summary>
|
||||
Task<byte[]> GetSnapshotAsync(string owlChannelId);
|
||||
|
||||
/// <summary>停止播放</summary>
|
||||
Task<WebResponseContent> StopPlayAsync(string owlChannelId);
|
||||
}
|
||||
```
|
||||
|
||||
**实现要点**:
|
||||
1. **新建文件**实现`IOwlApiService`,不修改任何框架代码
|
||||
2. 实现`IDependency`接口,Autofac自动注册到IOC容器
|
||||
3. 使用 `IHttpClientFactory` 创建HTTP客户端
|
||||
4. Token缓存到内存/Redis,过期前自动刷新
|
||||
5. 所有方法内部处理Owl的JWT认证(自动附加Authorization header)
|
||||
6. 返回值统一转换为Vol.Pro的 `WebResponseContent` 格式
|
||||
|
||||
### 4.3 扩展API控制器 (VideoStreamController)
|
||||
|
||||
在Vol.Pro中**新增独立的Controller文件**(不修改任何框架代码),提供取流、回放、云台等扩展接口:
|
||||
|
||||
```csharp
|
||||
// 文件位置:api_sqlsugar/Warehouse/Controllers/VideoStreamController.cs
|
||||
// (全新文件,与框架代码无任何耦合)
|
||||
|
||||
namespace Warehouse.Controllers
|
||||
{
|
||||
[Route("api/VideoStream")]
|
||||
[JWTAuthorize]
|
||||
public class VideoStreamController : VolController
|
||||
{
|
||||
private readonly IOwlApiService _owlApi;
|
||||
private readonly IVideo_ChannelService _channelService;
|
||||
|
||||
public VideoStreamController(
|
||||
IOwlApiService owlApi,
|
||||
IVideo_ChannelService channelService)
|
||||
{
|
||||
_owlApi = owlApi;
|
||||
_channelService = channelService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取实时流播放地址
|
||||
/// </summary>
|
||||
[HttpPost, Route("GetLiveUrl")]
|
||||
public async Task<ActionResult> GetLiveUrl([FromBody] LiveUrlInput input)
|
||||
{
|
||||
var channel = await _channelService.GetChannelByIdAsync(input.ChannelId);
|
||||
if (channel == null) return Json(new WebResponseContent().Error("通道不存在"));
|
||||
|
||||
var result = await _owlApi.GetLiveUrlAsync(channel.OwlChannelId, input.Protocol);
|
||||
return Json(new WebResponseContent().OK(null, result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取回放流地址
|
||||
/// </summary>
|
||||
[HttpPost, Route("GetPlaybackUrl")]
|
||||
public async Task<ActionResult> GetPlaybackUrl([FromBody] PlaybackUrlInput input)
|
||||
{
|
||||
var channel = await _channelService.GetChannelByIdAsync(input.ChannelId);
|
||||
if (channel == null) return Json(new WebResponseContent().Error("通道不存在"));
|
||||
|
||||
var result = await _owlApi.GetPlaybackUrlAsync(
|
||||
channel.OwlChannelId, input.StartTime, input.EndTime);
|
||||
return Json(new WebResponseContent().OK(null, result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 云台控制
|
||||
/// </summary>
|
||||
[HttpPost, Route("PtzControl")]
|
||||
public async Task<ActionResult> PtzControl([FromBody] PtzInput input)
|
||||
{
|
||||
var channel = await _channelService.GetChannelByIdAsync(input.ChannelId);
|
||||
if (channel == null) return Json(new WebResponseContent().Error("通道不存在"));
|
||||
if (channel.HasPtz != 1) return Json(new WebResponseContent().Error("该通道不支持云台"));
|
||||
|
||||
var result = await _owlApi.PtzControlAsync(channel.OwlChannelId, new PtzCommand
|
||||
{
|
||||
Action = input.Action,
|
||||
Direction = input.Direction,
|
||||
Speed = input.Speed
|
||||
});
|
||||
return Json(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取通道快照
|
||||
/// </summary>
|
||||
[HttpGet, Route("GetSnapshot")]
|
||||
public async Task<IActionResult> GetSnapshot(Guid channelId)
|
||||
{
|
||||
var channel = await _channelService.GetChannelByIdAsync(channelId);
|
||||
if (channel == null) return Json(new WebResponseContent().Error("通道不存在"));
|
||||
|
||||
var bytes = await _owlApi.GetSnapshotAsync(channel.OwlChannelId);
|
||||
return File(bytes, "image/jpeg");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止播放
|
||||
/// </summary>
|
||||
[HttpPost, Route("StopPlay")]
|
||||
public async Task<ActionResult> StopPlay([FromBody] StopPlayInput input)
|
||||
{
|
||||
var channel = await _channelService.GetChannelByIdAsync(input.ChannelId);
|
||||
if (channel == null) return Json(new WebResponseContent().Error("通道不存在"));
|
||||
|
||||
var result = await _owlApi.StopPlayAsync(channel.OwlChannelId);
|
||||
return Json(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **独立性说明**:`VideoStreamController`是**全新创建的文件**,不继承任何自动生成的控制器,也不修改任何框架文件。它通过构造函数注入`IOwlApiService`和`IVideo_ChannelService`,完全依赖IOC容器解耦。
|
||||
|
||||
### 4.4 设备同步机制
|
||||
|
||||
由于Owl是设备的真实数据源(GB28181设备直接注册到Owl),Vol.Pro数据库中的设备需要通过**定时同步**或**手动同步**保持与Owl一致。
|
||||
|
||||
**同步方案**:
|
||||
|
||||
1. **定时同步**(推荐):使用Vol.Pro内置的Quartz定时任务,每5分钟执行一次设备同步
|
||||
2. **手动同步**:管理端提供"同步设备"按钮,点击后立即拉取Owl设备列表
|
||||
3. **Webhook同步**(可选高级方案):Owl支持Webhook回调,当设备注册/注销时回调Vol.Pro接口
|
||||
|
||||
**同步逻辑**:
|
||||
```
|
||||
1. 调用 Owl GET /devices 获取设备列表
|
||||
2. 调用 Owl GET /channels 获取通道列表
|
||||
3. 对比本地数据库,执行增删改
|
||||
4. 更新 Video_Device 和 Video_Channel 表
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、前端对接方案
|
||||
|
||||
### 5.1 播放器选型
|
||||
|
||||
浏览器播放GB28181视频流,需要支持以下协议:
|
||||
|
||||
| 协议 | 延迟 | 浏览器兼容性 | 推荐场景 |
|
||||
|------|------|-------------|----------|
|
||||
| **WebRTC** | < 500ms | Chrome/Firefox/Edge | **实时监控首选** |
|
||||
| **WS-FLV** | 1-3s | 全浏览器 | 监控备用 |
|
||||
| **HTTP-FLV** | 1-3s | 全浏览器 | 简单场景 |
|
||||
| **HLS** | 5-10s | 全浏览器 | 回放首选 |
|
||||
| **RTMP** | 2-5s | 需Flash/插件 | 不推荐浏览器 |
|
||||
|
||||
**推荐方案**:
|
||||
- **实时监控**: WebRTC(延迟最低)+ WS-FLV(兼容性备用)
|
||||
- **历史回放**: HLS (m3u8)
|
||||
- **播放器库**:
|
||||
- [mpegts.js](https://github.com/xqq/mpegts.js) - 腾讯开源,支持FLV/HLS,纯JavaScript
|
||||
- [Jessibuca](https://github.com/langhuihui/jessibuca) - 纯H5播放器,支持FLV/HLS/WebRTC,wasm解码
|
||||
- [webrtc-streamer](https://github.com/mpromonet/webrtc-streamer) - WebRTC专用
|
||||
|
||||
**最终推荐**:使用 **Jessibuca** 或 **mpegts.js**(两者都是纯前端,无需插件,支持多协议切换)
|
||||
|
||||
> **⚠️ WebRTC配置前置条件**:
|
||||
> ZLMediaKit默认**不开启WebRTC**,需在`zlm-config.ini`中配置:
|
||||
> ```ini
|
||||
> [rtc]
|
||||
> port=8000
|
||||
> ```
|
||||
> 同时需要配置DTLS证书。如果部署环境不满足WebRTC条件,**默认使用WS-FLV**。
|
||||
>
|
||||
> **建议**:联调阶段先验证WS-FLV可用,再按需开启WebRTC。
|
||||
|
||||
### 5.2 管理端 (web.vite) 对接
|
||||
|
||||
在Vol.Pro管理端中,通过代码生成器生成设备/通道/录像的CRUD页面后,需要扩展以下功能。所有扩展均写在**独立的扩展文件**中,不修改代码生成器生成的文件:
|
||||
|
||||
**Vol.Pro前端扩展机制**:
|
||||
- 自动生成的代码:`views/模块/表名.vue` + `views/模块/表名/options.js`
|
||||
- 自定义业务代码:`extension/模块/表名.jsx` 或 `.vue`(通过`import extend from "@/extension/..."`引入)
|
||||
- **代码生成器重新生成时,不会覆盖`extension/`目录下的扩展文件**
|
||||
|
||||
1. **设备列表页扩展**(写在`extension/warehouse/Base_Device.jsx`):
|
||||
- 添加"同步设备"按钮(调用后端同步接口,仅对DeviceCategory=1显示)
|
||||
- 添加"在线状态"实时显示
|
||||
- 添加"查看通道"操作列
|
||||
- 添加`DeviceCategory`筛选条件(默认筛选视频监控设备)
|
||||
|
||||
2. **通道列表页扩展**(写在`extension/warehouse/Video_Channel.jsx`):
|
||||
- 添加"实时预览"按钮(弹窗播放视频)
|
||||
- 添加"云台控制"按钮(在弹窗中显示云台面板)
|
||||
- 添加"回放"按钮(跳转回放页面)
|
||||
|
||||
3. **新增视频播放页面**(`views/video/VideoPlayer.vue`,全新文件):
|
||||
- 使用Jessibuca播放器组件
|
||||
- 支持协议切换(WebRTC/FLV/HLS)
|
||||
- 集成云台控制面板
|
||||
|
||||
4. **新增回放查询页面**(`views/video/VideoPlayback.vue`,全新文件):
|
||||
- 日期时间选择器
|
||||
- 时间轴组件
|
||||
- HLS播放器
|
||||
|
||||
**扩展方式**:在代码生成后的 `.jsx` 扩展文件中编写自定义逻辑,参考Vol.Pro文档的 `view-grid` 扩展方式。
|
||||
|
||||
### 5.3 用户端 (warehouse) 对接
|
||||
|
||||
warehouse已有 `Live.vue`、`History.vue`、`VideoWall.vue`,但均为占位符。需要:
|
||||
|
||||
1. **Live.vue 改造**:
|
||||
- 替换硬编码数据为真实API调用
|
||||
- 集成Jessibuca播放器替换"正在加载..."占位符
|
||||
- 摄像头列表从 `Video_Channel` API获取
|
||||
- 点击摄像头获取流地址并播放
|
||||
|
||||
2. **History.vue 改造**:
|
||||
- 集成日期范围选择查询录像列表
|
||||
- 调用回放API获取HLS地址
|
||||
- 使用HLS播放器播放
|
||||
|
||||
3. **VideoWall.vue 改造**(最复杂):
|
||||
- 将随机图片替换为真实视频播放器实例
|
||||
- 每个格子是一个独立的Jessibuca实例
|
||||
- 布局切换时动态创建/销毁播放器
|
||||
- 云台控制按钮调用Vol.Pro后端PTZ API
|
||||
|
||||
**API调用示例**(warehouse前端):
|
||||
```typescript
|
||||
// 获取通道列表
|
||||
import http from '@/api/http'
|
||||
|
||||
// 获取通道列表(Vol.Pro标准分页接口)
|
||||
const getChannels = () => {
|
||||
return http.post('/api/Video_Channel/GetPageData', {
|
||||
page: 1,
|
||||
rows: 100,
|
||||
sort: 'CreateDate',
|
||||
order: 'desc'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取实时流地址
|
||||
const getLiveUrl = (channelId: string, protocol = 'ws_flv') => {
|
||||
return http.post('/api/VideoStream/GetLiveUrl', {
|
||||
channelId,
|
||||
protocol
|
||||
})
|
||||
}
|
||||
|
||||
// 云台控制
|
||||
const ptzControl = (channelId: string, direction: string, speed = 0.5) => {
|
||||
return http.post('/api/VideoStream/PtzControl', {
|
||||
channelId,
|
||||
action: 'continuous',
|
||||
direction,
|
||||
speed
|
||||
})
|
||||
}
|
||||
|
||||
// 获取回放地址
|
||||
const getPlaybackUrl = (channelId: string, startTime: string, endTime: string) => {
|
||||
return http.post('/api/VideoStream/GetPlaybackUrl', {
|
||||
channelId,
|
||||
startTime,
|
||||
endTime
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、关键API接口文档
|
||||
|
||||
### 6.1 Vol.Pro后端对外接口
|
||||
|
||||
#### 6.1.1 获取实时流地址
|
||||
|
||||
```
|
||||
POST /api/VideoStream/GetLiveUrl
|
||||
Authorization: Bearer <Vol.Pro JWT Token>
|
||||
|
||||
Request:
|
||||
{
|
||||
"channelId": "guid", // Vol.Pro通道ID
|
||||
"protocol": "ws_flv" // 可选: webrtc / ws_flv / http_flv / hls / rtmp / rtsp
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"status": true,
|
||||
"message": "ok",
|
||||
"data": {
|
||||
"channelId": "guid",
|
||||
"channelName": "仓库入口",
|
||||
"protocol": "ws_flv",
|
||||
"url": "ws://192.168.3.108/proxy/sms/rtp/gb_xxx.live.flv",
|
||||
"backupUrls": {
|
||||
"webrtc": "webrtc://192.168.3.108/proxy/sms/...",
|
||||
"hls": "http://192.168.3.108/proxy/sms/..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.1.2 获取回放流地址
|
||||
|
||||
```
|
||||
POST /api/VideoStream/GetPlaybackUrl
|
||||
Authorization: Bearer <Vol.Pro JWT Token>
|
||||
|
||||
Request:
|
||||
{
|
||||
"channelId": "guid",
|
||||
"startTime": "2026-04-28 08:00:00",
|
||||
"endTime": "2026-04-28 18:00:00"
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"status": true,
|
||||
"message": "ok",
|
||||
"data": {
|
||||
"channelId": "guid",
|
||||
"playlistUrl": "http://192.168.3.108/api/VideoStream/playback.m3u8?cid=xxx&start=...",
|
||||
"recordCount": 12,
|
||||
"totalDuration": 36000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.1.3 云台控制
|
||||
|
||||
```
|
||||
POST /api/VideoStream/PtzControl
|
||||
Authorization: Bearer <Vol.Pro JWT Token>
|
||||
|
||||
Request:
|
||||
{
|
||||
"channelId": "guid",
|
||||
"action": "continuous", // continuous / stop
|
||||
"direction": "up", // up / down / left / right / zoom_in / zoom_out
|
||||
"speed": 0.5 // 0.0 ~ 1.0
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"status": true,
|
||||
"message": "ok"
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.1.4 获取快照
|
||||
|
||||
```
|
||||
GET /api/VideoStream/GetSnapshot?channelId=guid
|
||||
Authorization: Bearer <Vol.Pro JWT Token>
|
||||
|
||||
Response: image/jpeg (二进制图片数据)
|
||||
```
|
||||
|
||||
### 6.2 Owl内部接口(Vol.Pro后端调用,不暴露给前端)
|
||||
|
||||
| 接口 | 说明 | 用途 |
|
||||
|------|------|------|
|
||||
| `GET /devices` | 设备列表 | 同步设备 |
|
||||
| `GET /channels` | 通道列表 | 同步通道 |
|
||||
| `POST /channels/:id/play` | 获取流地址 | 取流 |
|
||||
| `POST /channels/:id/stop` | 停止播放 | 停止 |
|
||||
| `POST /channels/:id/ptz/control` | 云台控制 | PTZ |
|
||||
| `GET /recordings?cid=xxx` | 录像查询 | 回放 |
|
||||
| `GET /channels/:id/snapshot` | 获取快照 | 截图 |
|
||||
|
||||
---
|
||||
|
||||
## 七、实施计划
|
||||
|
||||
> **所有代码均写在独立文件中,不修改任何框架自动生成的代码。框架升级时只需重新运行代码生成器,自定义业务完全保留。**
|
||||
|
||||
### 阶段一:数据库与代码生成(2天)
|
||||
|
||||
1. 执行建表SQL创建4张表(`Base_Device`、`Device_Video_Ext`、`Video_Channel`、`Video_Record`)
|
||||
2. 使用Vol.Pro代码生成器生成4个模块的CRUD代码(实体、Service、Controller、前端页面)
|
||||
3. 验证生成后的增删改查功能正常
|
||||
4. **验证**:确认`Partial/`和`extension/`目录未被覆盖
|
||||
|
||||
### 阶段二:Owl对接服务(3天)
|
||||
|
||||
**新建文件(完全不触碰框架代码)**:
|
||||
1. 创建 `Warehouse/Services/Owl/IOwlApiService.cs`(接口)
|
||||
2. 创建 `Warehouse/Services/Owl/OwlApiService.cs`(实现)
|
||||
3. 实现JWT Token获取与缓存(内存/Redis)
|
||||
4. 实现设备/通道同步逻辑(调用Owl `/devices`、`/channels`)
|
||||
5. 实现取流、回放、云台、快照接口
|
||||
6. 创建 `Warehouse/Controllers/VideoStreamController.cs`(全新Controller)
|
||||
7. 添加Quartz定时同步任务(写在`Partial`中扩展或新建Job)
|
||||
|
||||
### 阶段三:管理端前端扩展(2天)
|
||||
|
||||
**扩展文件(不修改生成的.vue/options.js)**:
|
||||
1. 引入Jessibuca播放器组件(npm install,全局注册)
|
||||
2. 创建 `web.vite/src/extension/warehouse/Base_Device.jsx`(扩展设备列表页,添加DeviceCategory=1筛选)
|
||||
3. 创建 `web.vite/src/extension/warehouse/Video_Channel.jsx`(扩展通道列表页)
|
||||
4. 创建 `web.vite/src/views/video/VideoPlayer.vue`(视频播放弹窗,全新文件)
|
||||
5. 创建 `web.vite/src/views/video/VideoPlayback.vue`(回放查询页面,全新文件)
|
||||
|
||||
### 阶段四:warehouse用户端改造(3天)
|
||||
|
||||
**修改warehouse自有代码(与Vol.Pro框架无关)**:
|
||||
1. 修改 `warehouse/src/view/video/Live.vue`:替换模拟数据为真实API调用,集成Jessibuca播放器
|
||||
2. 修改 `warehouse/src/view/video/History.vue`:实现回放查询和HLS播放
|
||||
3. 修改 `warehouse/src/view/video/VideoWall.vue`:将图片占位符替换为真实视频播放器实例
|
||||
4. 对接云台控制API(调用Vol.Pro后端的`/api/VideoStream/PtzControl`)
|
||||
|
||||
### 阶段五:联调测试(2天)
|
||||
|
||||
1. 设备注册到Owl后同步到Vol.Pro
|
||||
2. 实时播放测试(WebRTC/FLV)
|
||||
3. 回放功能测试
|
||||
4. 云台控制测试
|
||||
5. 多路视频墙测试
|
||||
6. **框架升级测试**:重新运行代码生成器,验证所有自定义代码未被覆盖
|
||||
|
||||
**总计预计工期**: 15个工作日(含3天联调缓冲)
|
||||
|
||||
### 代码独立性检查清单
|
||||
|
||||
实施过程中每完成一个模块,对照以下清单确认无框架代码被修改:
|
||||
|
||||
| 检查项 | 通过标准 |
|
||||
|--------|----------|
|
||||
| Controller扩展 | 新增接口写在`Partial/`或全新Controller文件中 |
|
||||
| Service扩展 | 业务逻辑写在`Services/模块/Partial/`中 |
|
||||
| Entity扩展 | 新增字段写在`DomainModels/模块/partial/`中,加`[NotMapped]` |
|
||||
| 前端扩展 | 自定义逻辑写在`extension/`目录下的`.jsx`或`.vue`中 |
|
||||
| 独立服务 | `OwlApiService`等新建服务实现`IDependency`,不修改框架文件 |
|
||||
| 配置修改 | 仅在`appsettings.json`中添加Owl相关配置,不修改框架其他配置 |
|
||||
|
||||
---
|
||||
|
||||
## 八、风险与注意事项
|
||||
|
||||
### 8.1 已知限制
|
||||
|
||||
1. **Owl云台控制不完整**: 仅支持 `continuous` 和 `stop`,预置位/绝对位置未实现
|
||||
2. **GB28181首次播放延迟**: SIP信令协商需要1-3秒,首次播放有延迟
|
||||
3. **WebRTC兼容性**: 旧版浏览器不支持WebRTC,必须准备FLV备用方案
|
||||
4. **HLS回放延迟**: HLS切片默认2秒,端到端延迟约5-10秒,不适合实时监控
|
||||
|
||||
### 8.2 安全建议
|
||||
|
||||
1. Owl的JWT Token(3天有效期)需要安全存储,建议用Redis缓存
|
||||
2. 播放地址应通过Vol.Pro后端代理,不要暴露Owl直连地址给前端
|
||||
3. ZLMediaKit的 `secret` 在生产环境必须修改
|
||||
4. 云台控制接口需要权限校验(Vol.Pro的 `[ApiActionPermission]`)
|
||||
|
||||
### 8.3 性能建议
|
||||
|
||||
1. 视频墙多路播放时,建议限制同时播放路数(如最大16路)
|
||||
2. 使用 `stop` 接口及时释放不再观看的流,减少服务器压力
|
||||
3. 快照图片可做CDN缓存
|
||||
4. 录像查询建议分页,避免大时间范围查询
|
||||
|
||||
---
|
||||
|
||||
## 附录A:统一设备管理与三维地图标记
|
||||
|
||||
### A.1 统一设备管理设计
|
||||
|
||||
本方案与MC4.0采集网关方案共用 **`Base_Device` 统一设备主表**,实现视频监控设备和采集网关设备的统一管理:
|
||||
|
||||
| 设备大类 (DeviceCategory) | 细分类型 (DeviceType) | 扩展表 | 对接系统 |
|
||||
|--------------------------|----------------------|--------|----------|
|
||||
| 1 = 视频监控 | 1=IPC, 2=NVR, 3=Platform | `Device_Video_Ext` | Owl + ZLMediaKit |
|
||||
| 2 = 采集网关 | 1=区域, 2=设备 | `Device_IoT_Ext` | MC4.0 采集网关 |
|
||||
|
||||
**原 `Video_Device` 表已拆分为**:
|
||||
- `Base_Device`:公共字段(设备名称、IP、位置、经纬度、MapModelId等)
|
||||
- `Device_Video_Ext`:视频特有字段(OwlDeviceId、Protocol、ChannelCount等)
|
||||
|
||||
### A.2 三维地图标记 (VgoMap)
|
||||
|
||||
所有设备通过 `Base_Device` 表中的 `MapModelId` 字段,在 warehouse 前端的 VgoMap 三维地图上进行统一标记。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| **MapModelId** | nvarchar(100) | 三维地图模型唯一标识 |
|
||||
| **MapModelScale** | float | 模型缩放比例 |
|
||||
| **MapModelRotation** | nvarchar(100) | 模型旋转角度(JSON) |
|
||||
|
||||
**前端集成要点**:
|
||||
```typescript
|
||||
// 加载所有设备到三维地图
|
||||
const devices = await http.post('/api/Base_Device/GetPageData', { ... });
|
||||
devices.forEach(d => {
|
||||
if (d.MapModelId) {
|
||||
window.$map.setModelData(d.MapModelId, {
|
||||
deviceId: d.DeviceId,
|
||||
deviceName: d.DeviceName,
|
||||
isOnline: d.IsOnline
|
||||
});
|
||||
window.$map.setModelColor(d.MapModelId,
|
||||
d.IsOnline === 1 ? '#67c23a' : '#f56c6c');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
详见《Vol.Pro_MC4.0_整合方案_v1.0.md》附录A.2完整说明。
|
||||
|
||||
---
|
||||
|
||||
> **文档结束**
|
||||
> **版本**: v1.0
|
||||
> **最后更新**: 2026-04-29
|
||||
> **状态**: 待主人确认后实施
|
||||
Reference in New Issue
Block a user