Compare commits
5 Commits
29e12a235a
...
phase/0-in
| Author | SHA1 | Date | |
|---|---|---|---|
| 46b679ca4e | |||
| 7550903aa8 | |||
| 5fd71a20b9 | |||
| f0add6e0b9 | |||
| 9277330922 |
424
doc/整合方案/SecMPS_整合方案_v3.0.md
Normal file
424
doc/整合方案/SecMPS_整合方案_v3.0.md
Normal file
@@ -0,0 +1,424 @@
|
||||
# SecMPS 整合方案 v3.0
|
||||
|
||||
> 版本: 3.0
|
||||
> 日期: 2025-05-17
|
||||
> 状态: 草稿
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
SecMPS 需要将多套异构子系统(视频监控、IoT动环、门禁道闸、报警)统一整合到 Vol.Pro 管理端。v2.0 采用 Vol.Pro 直接对接各子系统 API,存在紧耦合、协议适配散落、扩展困难等问题。
|
||||
|
||||
v3.0 引入 **IntegrationGateway(整合网关)** 作为统一中间层,面向子系统做协议适配,面向 Vol.Pro 提供标准化 REST API,实现"适配一次,多处复用"。
|
||||
|
||||
### 1.1 核心原则
|
||||
|
||||
| 原则 | 说明 |
|
||||
|------|------|
|
||||
| 适配器隔离 | 每个子系统一个 Adapter,实现统一接口,互不干扰 |
|
||||
| 懒加载 | 适配器按需初始化,不阻塞网关启动 |
|
||||
| 分页语义统一 | 统一 page/size 分页,适配器内部转换 skip/limit |
|
||||
| 字段分治 | 网关负责"来源标识+在线状态",Vol.Pro 负责"设备名称+扩展属性",首次入库全量,后续仅更新网关字段 |
|
||||
|
||||
### 1.2 子系统清单
|
||||
|
||||
| 子系统 | 对接方式 | 适配器 | 核心能力 |
|
||||
|--------|----------|--------|----------|
|
||||
| Owl + ZLMediaKit | REST API | OwlAdapter | 视频设备发现、实时流、PTZ、录像回放、截图 |
|
||||
| MC4.0 | REST API | MC4Adapter | 对象树、实时点值、设备控制、告警查询/确认/结束 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构总览
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ Vol.Pro 管理端 │
|
||||
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │
|
||||
│ │ gateway_nodes│ │ base_device │ │ device_manager │ │
|
||||
│ │ Controller │ │ Controller │ │ (前端页面) │ │
|
||||
│ │ A1-A4 │ │ GetRegion..│ │ RegionTree + │ │
|
||||
│ │ │ │ GetDevices │ │ DeviceTable │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ └────────┬────────┘ │
|
||||
│ │ │ │ │
|
||||
│ │ GatewayClient │ │ HTTP │
|
||||
│ └────────┬────────┘ │ │
|
||||
│ │ B3 (REST) │ │
|
||||
└──────────────────┼─────────────────────────────┼──────────┘
|
||||
│ │
|
||||
┌──────────────────┼─────────────────────────────┼──────────┐
|
||||
│ IntegrationGateway (net8.0) │ │
|
||||
│ │ │ │
|
||||
│ ┌───────────────┴──────────────────────────────┴───────┐ │
|
||||
│ │ AdapterRegistry │ │
|
||||
│ │ ┌─────────────┐ ┌─────────────┐ │ │
|
||||
│ │ │ OwlAdapter │ │ MC4Adapter │ ... │ │
|
||||
│ │ │ IHasFlat.. │ │ IHasOwnTree │ │ │
|
||||
│ │ │ IHasStreams │ │ IHasPoints │ │ │
|
||||
│ │ │ IHasPtz │ │ IHasAlarms │ │ │
|
||||
│ │ └──────┬──────┘ └──────┬──────┘ │ │
|
||||
│ └─────────┼────────────────┼───────────────────────────┘ │
|
||||
│ │ │ │
|
||||
│ ┌────────┴────────┐ ┌───┴──────────┐ │
|
||||
│ │ Owl + ZLMediaKit│ │ MC4.0 │ │
|
||||
│ │ (视频监控) │ │ (动环/IoT) │ │
|
||||
│ └─────────────────┘ └──────────────┘ │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.1 数据流
|
||||
|
||||
```
|
||||
MC4.0 对象树 → Mc4Adapter.GetObjectTree()
|
||||
→ SyncEngine.ProcessMc4Tree()
|
||||
→ type=1 节点 → warehouse_regions (区域匹配)
|
||||
→ type=2 节点 → base_device (Upsert, 字段分治)
|
||||
```
|
||||
|
||||
### 2.2 网关端口约定
|
||||
|
||||
| 服务 | 端口 | 说明 |
|
||||
|------|------|------|
|
||||
| IntegrationGateway | 5100 | 设备管理 REST API |
|
||||
| Owl + ZLMediaKit | 15123 | 视频流/录像 |
|
||||
| MC4.0 | 3000 | 动环数据/告警 |
|
||||
| Vol.Pro | 9991 | 管理端后端 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心接口体系
|
||||
|
||||
### 3.1 能力接口(Capability Interfaces)
|
||||
|
||||
```csharp
|
||||
// 适配器按需实现,网关通过反射或接口判断自动发现能力
|
||||
|
||||
interface IHasFlatDevices { Task<PagedResult<StandardDevice>> GetDevicesAsync(int page, int size, string? keyword); }
|
||||
interface IHasOwnDeviceTree { Task<List<DeviceTreeNode>> GetObjectTreeAsync(); }
|
||||
interface IHasStreams { Task<StreamUrls> GetLiveUrlAsync(string channelId); Task<StreamUrls> GetPlaybackUrlAsync(string channelId, DateTime start, DateTime end); }
|
||||
interface IHasPtz { Task PtzControlAsync(string channelId, string direction, float speed); }
|
||||
interface IHasRecordings { Task<PagedResult<StandardRecording>> GetRecordingsAsync(string channelId, DateTime start, DateTime end, int page, int size); }
|
||||
interface IHasPoints { Task<List<PointValue>> GetRealtimeValuesAsync(string sourceDeviceId); Task SetPointValueAsync(string sourceDeviceId, int pointIndex, double value); }
|
||||
interface IHasAlarms { Task<PagedResult<StandardAlarm>> GetAlarmsAsync(int page, int size, DateTime from, DateTime to, ...); Task ConfirmAlarmAsync(string alarmId); Task EndAlarmAsync(string alarmId); }
|
||||
interface IAcceptsMetadataPush { Task<MetadataPushResult> PushMetadataAsync(string sourceDeviceId, MetadataChangeSet changes); }
|
||||
```
|
||||
|
||||
### 3.2 网关统一模型
|
||||
|
||||
```csharp
|
||||
class StandardDevice {
|
||||
int DeviceId; // Vol.Pro 侧主键(同步后回填)
|
||||
string AdapterCode; // "Owl:main" / "MC4:31ku"
|
||||
string SourceId; // 子系统原始 ID
|
||||
string DeviceName;
|
||||
string DeviceCategory; // 摄像机/温湿度变送器/...
|
||||
string DeviceGroup; // 视频设备/IoT设备/门禁设备/...
|
||||
bool IsParent;
|
||||
string ParentSourceId;
|
||||
bool IsOnline;
|
||||
string IpAddress;
|
||||
int? Port;
|
||||
Dictionary<string,object?> Extra; // 子系统扩展属性
|
||||
}
|
||||
|
||||
class StandardAlarm {
|
||||
string AlarmId;
|
||||
string DeviceId;
|
||||
string AdapterCode;
|
||||
string Level; // 提示/普通/重要/紧急
|
||||
string Title;
|
||||
string Content;
|
||||
DateTime OccurTime;
|
||||
string Status; // 未确认/已确认/已结束
|
||||
double? ActualValue;
|
||||
double? ThresholdValue;
|
||||
}
|
||||
|
||||
class DeviceTreeNode {
|
||||
string SourceId;
|
||||
string Name;
|
||||
int NodeType; // 1=区域, 2=设备
|
||||
int ObjectType;
|
||||
string Tag;
|
||||
Dictionary<string,object?> Option;
|
||||
List<DeviceTreeNode> Children;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 订阅接口(B3,供 Vol.Pro 回调)
|
||||
|
||||
| 路由 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| `/api/gateway/register` | POST | 网关注册 (Upsert) |
|
||||
| `/api/gateway/heartbeat` | POST | 心跳 |
|
||||
| `/api/gateway/sync/devices` | POST | 设备数据同步 |
|
||||
| `/api/gateway/sync/alarms` | POST | 告警同步 |
|
||||
|
||||
### 3.4 网关暴露接口
|
||||
|
||||
| 路由 | 适配器能力 | 说明 |
|
||||
|------|-----------|------|
|
||||
| `GET /devices?adapter={code}&page=&size=` | IHasFlatDevices | 分页获取设备列表 |
|
||||
| `GET /tree?adapter={code}` | IHasOwnDeviceTree | 获取对象树 |
|
||||
| `GET /streams/{adapter}/{deviceId}/live` | IHasStreams | 获取实时流地址 |
|
||||
| `GET /streams/{adapter}/{deviceId}/playback` | IHasStreams | 获取回放地址 |
|
||||
| `POST /streams/{adapter}/{deviceId}/snapshot` | IHasStreams | 获取截图 |
|
||||
| `POST /streams/{adapter}/{deviceId}/ptz` | IHasPtz | 云台控制 |
|
||||
| `GET /recordings/{adapter}/{deviceId}` | IHasRecordings | 获取录像列表 |
|
||||
| `GET /points/{adapter}/{deviceId}` | IHasPoints | 获取实时点值 |
|
||||
| `POST /points/{adapter}/{deviceId}` | IHasPoints | 控制写值 |
|
||||
| `GET /alarms/{adapter}` | IHasAlarms | 分页获取告警 |
|
||||
| `POST /alarms/{adapter}/{id}/confirm` | IHasAlarms | 确认告警 |
|
||||
| `POST /alarms/{adapter}/{id}/end` | IHasAlarms | 结束告警 |
|
||||
| `GET /health` | - | 适配器健康状态 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据库设计
|
||||
|
||||
### 4.1 新增表(5 张,Vol.Pro 侧)
|
||||
|
||||
```sql
|
||||
-- 网关节点表
|
||||
CREATE TABLE gateway_nodes (
|
||||
NodeId INT IDENTITY PRIMARY KEY,
|
||||
NodeCode NVARCHAR(50) NOT NULL,
|
||||
NodeName NVARCHAR(100),
|
||||
NodeToken NVARCHAR(200),
|
||||
AdapterTypes NVARCHAR(200), -- "Owl:main,MC4:31ku"
|
||||
BaseUrl NVARCHAR(500),
|
||||
IsOnline NVARCHAR(10), -- 在线/离线
|
||||
Enable NVARCHAR(10), -- 启用/禁用
|
||||
LastHeartbeat DATETIME,
|
||||
CreateDate DATETIME,
|
||||
CreateID INT,
|
||||
Creator NVARCHAR(50),
|
||||
ModifyDate DATETIME,
|
||||
ModifyID INT,
|
||||
Modifier NVARCHAR(50)
|
||||
);
|
||||
|
||||
-- 统一设备表
|
||||
CREATE TABLE base_device (
|
||||
DeviceId INT IDENTITY,
|
||||
AdapterCode NVARCHAR(50) NOT NULL, -- 联合主键
|
||||
DeviceName NVARCHAR(100),
|
||||
SourceId NVARCHAR(100) NOT NULL, -- 联合主键 (AdapterCode + SourceId = 唯一)
|
||||
DeviceCategory NVARCHAR(50), -- 摄像机/硬盘录像机/温湿度变送器/...
|
||||
DeviceGroup NVARCHAR(50), -- 视频设备/IoT设备/门禁设备/...
|
||||
PointId INT NULL, -- FK → warehouse_devicepoint
|
||||
GatewayNodeId INT NULL, -- FK → gateway_nodes
|
||||
IsParent NVARCHAR(10),
|
||||
ParentDeviceId INT NULL,
|
||||
IsOnline NVARCHAR(10),
|
||||
Enable NVARCHAR(10),
|
||||
Protocol NVARCHAR(50),
|
||||
IpAddress NVARCHAR(50),
|
||||
Port INT NULL,
|
||||
LastSyncTime DATETIME,
|
||||
MapModelId NVARCHAR(100),
|
||||
MapModelScale NVARCHAR(50),
|
||||
MapModelRotation NVARCHAR(200),
|
||||
ExtraData NVARCHAR(MAX), -- JSON 扩展属性
|
||||
CreateDate DATETIME,
|
||||
CreateID INT,
|
||||
Creator NVARCHAR(50),
|
||||
ModifyDate DATETIME,
|
||||
ModifyID INT,
|
||||
Modifier NVARCHAR(50),
|
||||
PRIMARY KEY (DeviceId, AdapterCode)
|
||||
);
|
||||
|
||||
-- 数据归档表
|
||||
CREATE TABLE iot_devicedata (
|
||||
DataId INT IDENTITY PRIMARY KEY,
|
||||
DeviceId INT NULL,
|
||||
AdapterCode NVARCHAR(50),
|
||||
PointIndex INT,
|
||||
Value FLOAT,
|
||||
UpdateTime DATETIME,
|
||||
CreateDate DATETIME
|
||||
);
|
||||
|
||||
-- 告警记录表
|
||||
CREATE TABLE iot_alarm (
|
||||
AlarmId INT IDENTITY PRIMARY KEY,
|
||||
SourceAlarmId NVARCHAR(100),
|
||||
DeviceId INT NULL,
|
||||
AdapterCode NVARCHAR(50),
|
||||
AlarmLevel NVARCHAR(20),
|
||||
AlarmDesc NVARCHAR(500),
|
||||
AlarmValue FLOAT,
|
||||
ThresholdValue FLOAT,
|
||||
StartTime DATETIME,
|
||||
EndTime DATETIME,
|
||||
ConfirmTime DATETIME,
|
||||
State NVARCHAR(20), -- 未确认/已确认/已结束
|
||||
ConfirmUser NVARCHAR(50),
|
||||
CreateDate DATETIME
|
||||
);
|
||||
|
||||
-- 视频通道表(base_device 子表)
|
||||
CREATE TABLE video_channel (
|
||||
ChannelId INT NOT NULL,
|
||||
DeviceId INT NOT NULL, -- FK → base_device
|
||||
ChannelNo NVARCHAR(50),
|
||||
SourceId NVARCHAR(100),
|
||||
ChannelName NVARCHAR(100),
|
||||
IsOnline NVARCHAR(10),
|
||||
CreateDate DATETIME,
|
||||
PRIMARY KEY (ChannelId, DeviceId)
|
||||
);
|
||||
|
||||
-- 录像文件表
|
||||
CREATE TABLE video_record (
|
||||
RecordId INT IDENTITY PRIMARY KEY,
|
||||
ChannelId INT NULL,
|
||||
FilePath NVARCHAR(500),
|
||||
Size BIGINT,
|
||||
StartTime DATETIME,
|
||||
EndTime DATETIME,
|
||||
Duration FLOAT,
|
||||
CreateDate DATETIME
|
||||
);
|
||||
```
|
||||
|
||||
### 4.2 字典初始化
|
||||
|
||||
```sql
|
||||
-- 设备分类字典
|
||||
INSERT INTO Sys_Dictionary (DicName, DicValue, DicNo, Config) VALUES
|
||||
('设备分组', '视频设备', 'device_group_1', '视频设备'),
|
||||
('设备分组', 'IoT设备', 'device_group_2', 'IoT设备'),
|
||||
('设备分组', '门禁设备', 'device_group_3', '门禁设备'),
|
||||
('设备分组', '道闸设备', 'device_group_4', '道闸设备'),
|
||||
('设备分组', '报警设备', 'device_group_5', '报警设备');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 管理端前端
|
||||
|
||||
### 5.1 页面结构
|
||||
|
||||
```
|
||||
/web.vite/src/views/warehouse/DeviceManager/
|
||||
├── index.vue 左右分栏主页面
|
||||
├── api/
|
||||
│ └── deviceManager.js API 封装
|
||||
└── components/
|
||||
├── RegionTree.vue 区域→点位树 (左侧)
|
||||
├── DeviceTable.vue 设备列表 (右侧)
|
||||
├── VideoDeviceActions.vue 视频设备操作按钮组
|
||||
├── DeviceLivePreview.vue 实时预览弹窗
|
||||
├── PtzControlPanel.vue 云台方向键面板
|
||||
├── MapBindingPanel.vue 地图绑定面板
|
||||
└── DeviceEditDialog.vue 设备编辑弹窗
|
||||
```
|
||||
|
||||
### 5.2 路由
|
||||
|
||||
```
|
||||
/device-manager → DeviceManager/index.vue
|
||||
```
|
||||
|
||||
### 5.3 功能矩阵
|
||||
|
||||
| 功能 | 触发条件 | 组件 | 说明 |
|
||||
|------|---------|------|------|
|
||||
| 区域树展开 | 页面加载 | RegionTree | 调 GetRegionTree API |
|
||||
| 设备列表 | 点击点位 | DeviceTable | 调 GetDevicesByPoint API |
|
||||
| 实时预览 | 视频设备→预览 | DeviceLivePreview | WS-FLV 播放 |
|
||||
| 云台控制 | 视频设备→云台 | PtzControlPanel | ↑↓←→ + ZOOM |
|
||||
| 查看回放 | 视频设备→回放 | (待实现) | 录像时间轴 |
|
||||
| 获取快照 | 视频设备→快照 | (待实现) | JPEG 快照 |
|
||||
| 地图绑定 | 任意设备→地图 | MapBindingPanel | 模型 ID/缩放/旋转 |
|
||||
| 编辑设备 | 非视频设备→编辑 | DeviceEditDialog | 名称/种类/分组/启用 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 实施计划
|
||||
|
||||
### Phase 0: 基础设施(Day 1-2)
|
||||
|
||||
| Day | 内容 | 产出 |
|
||||
|-----|------|------|
|
||||
| 1 | 网关项目骨架 (net8.0) | IntegrationGateway.sln, Core/Host 项目, 7 个能力接口, 10 个统一模型, 3 个基础设施 (Registry/RateLimiter/HttpClientFactory) |
|
||||
| 2 | Vol.Pro 侧集成 | GatewayClient, gateway_nodesController(A1-A4骨架), base_deviceController(骨架), Quartz Job, db_init.sql, 代码生成器产物 |
|
||||
|
||||
### Phase 1: Owl 适配器 + 管理端(Day 3-5)
|
||||
|
||||
| Day | 内容 | 产出 |
|
||||
|-----|------|------|
|
||||
| 3 | OwlAdapter | RSA 加密登录, 3 个接口实现 (IHasFlatDevices + IHasStreams + IAcceptsMetadataPush) |
|
||||
| 4 | DeviceManager 页面框架 | RegionTree + DeviceTable + router |
|
||||
| 5 | 视频组件 | 预览/云台/地图绑定/编辑弹窗 |
|
||||
|
||||
### Phase 2: MC4 适配器 + 联调(Day 6-11)
|
||||
|
||||
| Day | 内容 | 产出 |
|
||||
|-----|------|------|
|
||||
| 6 | 联调验证 (Owl + MC4) | 需子系统就绪 |
|
||||
| 7 | MC4Adapter | Token 认证, IHasOwnDeviceTree + IHasPoints + IHasAlarms |
|
||||
| 8 | 区域自动匹配 | SyncEngine, 字段分治, parentSourceId 映射 |
|
||||
| 9 | 物联网操作接口 | 实时值读取, 控制写值 API |
|
||||
| 10 | 告警集成 + SignalR | 告警查询/确认/结束, SignalR 实时推送 |
|
||||
| 11 | 地图绑定 + Quartz | MapBindingPanel, SyncDevicesJob, HeartbeatMonitorJob |
|
||||
|
||||
### Phase 3: Warehouse 端(Day 12-17)
|
||||
|
||||
| Day | 内容 | 产出 |
|
||||
|-----|------|------|
|
||||
| 12 | 钥匙柜管理 | 钥匙柜设备 + 钥匙借还流程 |
|
||||
| 13 | 巡更管理 | 巡更路径/点位/排班 |
|
||||
| 14 | 门禁管理 | 门禁一体机 + 授权 |
|
||||
| 15 | 道闸管理 | 人行道闸 + 车辆道闸 |
|
||||
| 16 | 报警管理 | 紧急报警 + 离线报警 |
|
||||
| 17 | 仓库页面整合 | 菜单 + 权限 |
|
||||
|
||||
### Phase 4: 验证发布(Day 18-20)
|
||||
|
||||
| Day | 内容 | 产出 |
|
||||
|-----|------|------|
|
||||
| 18 | 系统测试 | 功能测试 + 边界测试 |
|
||||
| 19 | 性能优化 | 流性能 + 并发 + 缓存 |
|
||||
| 20 | 文档 + 发布 | 部署手册, Dockerfile |
|
||||
|
||||
---
|
||||
|
||||
## 7. 风险与约束
|
||||
|
||||
| 风险 | 缓解措施 |
|
||||
|------|----------|
|
||||
| Vol.Pro 框架 API 与自主代码不兼容 | 网关层独立编译(dotnet build),Vol.Pro 后端逻辑骨架化,联调时一边调一边补 |
|
||||
| Owl 接口不稳定 | 统一 2 QPS 限流 + 重试 |
|
||||
| MC4 字段映射复杂 | 对象树 Option 弹性 JSON 字段,扩容不破坏现有映射 |
|
||||
| 设备数量大导致同步慢 | 增量同步 + 批量 Upsert |
|
||||
|
||||
---
|
||||
|
||||
## 8. 环境配置
|
||||
|
||||
### 8.1 网关 appsettings.json
|
||||
|
||||
```json
|
||||
{
|
||||
"Owl": {
|
||||
"BaseUrl": "http://owl_host:15123",
|
||||
"Username": "admin",
|
||||
"Password": "your_owl_password"
|
||||
},
|
||||
"MC4": {
|
||||
"BaseUrl": "http://mc4_host:3000"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 启动顺序
|
||||
|
||||
1. Owl + ZLMediaKit → 端口 15123
|
||||
2. MC4.0 → 端口 3000
|
||||
3. IntegrationGateway → 端口 5100 (`dotnet run --project src/IntegrationGateway.Host`)
|
||||
4. Vol.Pro 后端 → 端口 9991
|
||||
5. web.vite 前端 → 端口 9000 (`npm run dev`)
|
||||
531
doc/整合方案/SecMPS_最终整合方案_v3.0.md
Normal file
531
doc/整合方案/SecMPS_最终整合方案_v3.0.md
Normal file
@@ -0,0 +1,531 @@
|
||||
# SecMPS 整合方案(最终版):IntegrationGateway + 统一设备管理
|
||||
|
||||
> **版本**: v3.0
|
||||
> **日期**: 2026-05-16
|
||||
> **状态**: 待实施
|
||||
> **基准**: 基于 v2.0 + Owl/ZLMediaKit 双文档验证 + 14 项修正
|
||||
|
||||
---
|
||||
|
||||
## 一、总体架构
|
||||
|
||||
```
|
||||
前端层
|
||||
web.vite 管理端(设备管理+网关管理) warehouse 大屏(Map/Live/IoT/Alarm)
|
||||
| HTTP REST | HTTP REST + SignalR
|
||||
v v
|
||||
Vol.Pro 后端 (api_sqlsugar)
|
||||
DeviceManagerController / GatewayNodeController / SignalR Hubs
|
||||
Quartz: SyncDevicesJob / RealtimePollJob / AlarmPollJob
|
||||
数据库: 6 张表 (base_device / video_channel / video_record / iot_devicedata / iot_alarm / gateway_nodes)
|
||||
| 注册/下发设备/心跳/同步数据
|
||||
v
|
||||
IntegrationGateway 实例A (:5100) IntegrationGateway 实例B (:5101)
|
||||
NodeCode: gw-31ku NodeCode: gw-11ku
|
||||
Adapters: MC4 / Owl Adapters: MC4 / HikvisionISC
|
||||
| HTTP | HTTP
|
||||
v v
|
||||
MC4.0 (:3000) Owl (:15123) MC4.0 (:3000) 海康ISC (:80)
|
||||
|
||||
注: Owl 管理端口为 15123,ZLMediaKit 由 Owl 内部管理(:8000),网关不直接接触
|
||||
```
|
||||
|
||||
### 核心设计原则
|
||||
|
||||
- **网关无状态**:配置仅 NodeCode/Token/VolProUrl,挂了重装即恢复
|
||||
- **AdapterCode 双段标识**:`"MC4:31ku"` 区分同类型多实例,格式 `{AdapterType}:{InstanceName}`,InstanceName 仅字母数字下划线
|
||||
- **DeviceGroup 路由**:基类表用字典字段决定适配器和行为,不依赖扩展表
|
||||
- **ExtraData JSON**:所有适配器特有字段存入 ExtraData,新增适配器不增表
|
||||
- **心跳机制**:网关 15s 心跳,Vol.Pro 超 30s 级联设备离线
|
||||
- **字段分治**:网关字段(ExtraData/IsOnline/IsParent/ParentDeviceId)同步覆盖,管理员字段(DeviceName/Category/Location/MapModelId)首次写入后不再覆盖
|
||||
|
||||
---
|
||||
|
||||
## 二、网关架构(方案 C+)
|
||||
|
||||
### 2.1 网关注册与心跳流程
|
||||
|
||||
```
|
||||
管理端: gateway_nodes 表新增 → 生成 NodeCode + Token
|
||||
网关配置: { NodeCode, Token, VolProUrl }
|
||||
网关启动 → POST /register { nodeCode,token,adapterTypes,baseUrl }
|
||||
Vol.Pro 校验 Token:
|
||||
NodeCode 匹配 → Upsert: 更新 AdapterTypes/BaseUrl/IsOnline=在线 → 返回已有 NodeId
|
||||
NodeCode 不匹配且 Token 验证通过 → 插入新记录 → 返回新 NodeId
|
||||
验证失败 → 401
|
||||
响应: { gatewayNodeId, devices: [base_device WHERE GatewayNodeId=当前] }
|
||||
网关按 AdapterCode 分流 → Adapter 连接第三方 → 发现子设备
|
||||
网关 → POST /sync/devices → Vol.Pro 写入/更新 base_device(首次写全量,后续仅更新网关字段)
|
||||
网关每 15s → POST /heartbeat { nodeCode, token }
|
||||
Vol.Pro Job: IsOnline=在线 且 LastHeartbeat < now-30s → IsOnline=离线 → 级联: base_device.IsOnline=离线 WHERE GatewayNodeId=该节点
|
||||
```
|
||||
|
||||
### 2.2 网关配置
|
||||
|
||||
```json
|
||||
{
|
||||
"VolProBaseUrl": "http://localhost:9100",
|
||||
"NodeCode": "gw-31ku",
|
||||
"NodeToken": "xxxxxxxxxx"
|
||||
}
|
||||
```
|
||||
|
||||
AdapterCode 格式规范:`{AdapterType}:{InstanceName}`,例如 `"MC4:31ku"`、`"Owl:main"`。
|
||||
AdapterType = 网关注册的 Adapter 类名(字母数字),InstanceName = 网关实例名(字母数字下划线),分隔符 `:`。
|
||||
|
||||
### 2.3 适配器能力矩阵
|
||||
|
||||
| 接口 | Owl | MC4.0 | 门禁(未来) |
|
||||
|------|:---:|:-----:|:----------:|
|
||||
| IHasOwnDeviceTree | ❌ | ✅ | ❌ |
|
||||
| IHasFlatDevices | ✅ | ❌ | ✅ |
|
||||
| IHasPoints | ❌ | ✅ | ❌ |
|
||||
| IHasStreams | ✅ | ❌ | ❌ |
|
||||
| IHasAlarms | ⚠️(AI事件可选) | ✅ | ✅ |
|
||||
| IAcceptsMetadataPush | ✅ | ❌ | ⚠️ |
|
||||
|
||||
> Owl PTZ 限制:仅支持 continuous(方向移动) + stop(停止),不支持预设位/绝对定位/相对定位。ONVIF PTZ 未实现。
|
||||
|
||||
### 2.4 双向同步引擎
|
||||
|
||||
| 方向 | 说明 | MC4.0 | Owl |
|
||||
|------|------|-------|-----|
|
||||
| PullToVolPro | 第三方→Vol.Pro | FullReplace | Merge |
|
||||
| PushToSource | Vol.Pro→第三方 | 告警确认/控制 | 元数据/PTZ |
|
||||
| Bidirectional | 先写第三方再更新本地 | 告警确认 | — |
|
||||
|
||||
### 2.5 对接 API 规范
|
||||
|
||||
网关与 Vol.Pro 之间有两组接口,调用方向不同。
|
||||
|
||||
#### A. 网关 → Vol.Pro(网关主动调用)
|
||||
|
||||
| # | 接口 | 说明 |
|
||||
|---|------|------|
|
||||
| A1 | `POST /api/gateway/register` | 网关启动注册(Upsert逻辑),获取所管设备列表 |
|
||||
| A2 | `POST /api/gateway/heartbeat` | 心跳(每 15s) |
|
||||
| A3 | `POST /api/gateway/sync/devices` | 上送设备数据(新增/状态变更),仅更新网关字段 |
|
||||
| A4 | `POST /api/gateway/sync/alarms` | 上送告警数据 |
|
||||
|
||||
**A1 注册** — 认证: NodeToken, 逻辑: Upsert
|
||||
|
||||
```
|
||||
Request: { nodeCode, token, adapterTypes, baseUrl }
|
||||
Response: { nodeId, devices: [ 当前网关的顶层设备 ] }
|
||||
Error: 401 认证失败(Token 错误或 NodeCode 不存在且 Token 无效)
|
||||
```
|
||||
|
||||
**A2 心跳** — 认证: NodeToken
|
||||
|
||||
```
|
||||
Request: { nodeCode, token }
|
||||
Response: { status: "ok" }
|
||||
```
|
||||
|
||||
**A3 设备同步** — 认证: NodeToken
|
||||
|
||||
```
|
||||
Request: { nodeCode, token, devices: [{ adapterCode, sourceId, name, category, group,
|
||||
isParent, parentSourceId, isOnline, ipAddress, port, extraData }] }
|
||||
Response: { added, updated, removed }
|
||||
```
|
||||
|
||||
> 字段分治规则:首次入库(DeviceId==0)写全量;已有记录仅更新 IsOnline、IsParent、ParentDeviceId、ExtraData、LastSyncTime、IpAddress、Port。DeviceName/DeviceCategory/DeviceGroup/Location/MapModelId 仅在首次入库时写入。
|
||||
> parentSourceId 解析:同步前按 (AdapterCode, SourceId) 批量查询已有 DeviceId 映射表,将 parentSourceId 转为 ParentDeviceId。
|
||||
|
||||
**A4 告警同步** — 认证: NodeToken
|
||||
|
||||
```
|
||||
Request: { nodeCode, token, alarms: [{ sourceAlarmId, deviceSourceId, adapterCode,
|
||||
level, desc, value, startTime }] }
|
||||
Response: { added }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### B. Vol.Pro / 管理端 → 网关(查询与控制)
|
||||
|
||||
| # | 接口 | 说明 |
|
||||
|---|------|------|
|
||||
| B1 | `GET /api/gateway/health` | 网关及所有适配器状态 |
|
||||
| B2 | `GET /api/gateway/devices?adapter=&page=&size=` | 设备列表 |
|
||||
| B3 | `POST /api/gateway/devices/sync?adapter=` | 手动触发全量同步 |
|
||||
| B4 | `GET /api/gateway/realtime/{adapter}/{deviceId}` | 实时点位值 |
|
||||
| B5 | `POST /api/gateway/realtime/{adapter}/control` | 反向控制 |
|
||||
| B6a | `GET /api/gateway/streams/{adapter}/{id}/live` | 实时取流 |
|
||||
| B6b | `GET /api/gateway/streams/{adapter}/{id}/playback?start=&end=` | 回放取流(HLS VOD) |
|
||||
| B7 | `POST /api/gateway/streams/{adapter}/{id}/ptz` | 云台控制(仅方向移动+停止) |
|
||||
| B8 | `GET /api/gateway/alarms/{adapter}?from=&to=&page=&size=` | 告警查询 |
|
||||
| B9 | `POST /api/gateway/alarms/{adapter}/{alarmId}/confirm` | 告警确认(写回第三方+更新本地State) |
|
||||
|
||||
> B 组接口由管理端或 Vol.Pro 后端直接调用网关,认证方式为内网直连或网关侧 IP 白名单。
|
||||
> B6a 实时取流 → Owl `POST /channels/:id/play`;B6b 回放取流 → Owl `GET /recordings/channels/:cid/index.m3u8?start_ms=&end_ms=`
|
||||
> B7 仅支持 `continuous`(方向移动: up/down/left/right/zoom_in/zoom_out) + `stop`,不支持预设位
|
||||
> B9 确认第三方成功后,同步更新本地 iot_alarm.State='已确认'
|
||||
|
||||
---
|
||||
|
||||
## 三、数据模型(6 张表)
|
||||
|
||||
### 3.1 区域表 warehouse_regions(现有)
|
||||
|
||||
层级: warehouse_regions(区域) → warehouse_devicepoint(点位) → base_device(设备)
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| Id | int PK |
|
||||
| RegionName | nvarchar(255) |
|
||||
| ParentId | int? (自引用树) |
|
||||
|
||||
### 3.2 网关节点表 gateway_nodes
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| NodeId | INT AUTO_INCREMENT | 网关节点ID |
|
||||
| NodeCode | NVARCHAR(50) | 网关唯一编码 |
|
||||
| NodeName | NVARCHAR(100) | 网关名称 |
|
||||
| NodeToken | NVARCHAR(100) | 认证令牌 |
|
||||
| AdapterTypes | NVARCHAR(200) | 适配器类型(网关上报) |
|
||||
| BaseUrl | NVARCHAR(200) | 网关地址(网关上报) |
|
||||
| LastHeartbeat | DATETIME | 上次心跳时间 |
|
||||
| IsOnline | NVARCHAR(20) | 在线状态(字典: 在线/离线) |
|
||||
| Enable | NVARCHAR(20) | 启用状态(字典: 启用/禁用) |
|
||||
|
||||
### 3.3 统一设备主表 base_device
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| DeviceId | INT AUTO_INCREMENT | Vol.Pro内部ID |
|
||||
| DeviceName | NVARCHAR(100) | 设备名称(管理员字段) |
|
||||
| AdapterCode | NVARCHAR(50) | "MC4:31ku"(类型:实例) |
|
||||
| SourceId | NVARCHAR(100) | 源系统设备ID |
|
||||
| DeviceCategory | NVARCHAR(50) | 设备种类(字典) |
|
||||
| DeviceGroup | NVARCHAR(20) | 设备分组(字典) |
|
||||
| PointId | INT? | 所属点位ID |
|
||||
| GatewayNodeId | INT? | 所属网关节点ID |
|
||||
| IsParent | NVARCHAR(20) | 是否父设备(字典: 是/否) |
|
||||
| ParentDeviceId | INT? | 父设备自引用 |
|
||||
| IsOnline | NVARCHAR(20) | 在线状态(网关字段) |
|
||||
| MapModelId | NVARCHAR(100) | VgoMap模型ID(管理员字段) |
|
||||
| MapModelScale | FLOAT | |
|
||||
| MapModelRotation | NVARCHAR(100) | |
|
||||
| ExtraData | TEXT | ★ 适配器扩展JSON(网关字段) |
|
||||
| LastSyncTime | DATETIME | 上次同步时间 |
|
||||
| Enable | NVARCHAR(20) | 启用状态(字典: 启用/禁用) |
|
||||
|
||||
唯一约束: (AdapterCode, SourceId)
|
||||
|
||||
> 网关字段(同步覆盖):ExtraData/IsOnline/IsParent/ParentDeviceId/IpAddress/Port/LastSyncTime
|
||||
> 管理员字段(首次写入后不覆盖):DeviceName/DeviceCategory/DeviceGroup/PointId/Location/Lat/Lng/MapModelId/MapModelScale/MapModelRotation
|
||||
|
||||
### 3.4 DeviceGroup 分组规则
|
||||
|
||||
| DeviceGroup | 网关适配器 | 前端按钮组 | 同步模式 |
|
||||
|:---:|------|------|:---:|
|
||||
| 视频设备 | OwlAdapter → IHasStreams | 实时预览/云台/回放/快照 | Merge |
|
||||
| IoT设备 | Mc4Adapter → IHasPoints | 实时数据/控制/告警 | FullReplace |
|
||||
| 门禁设备 | AccessAdapter | 远程开门/记录/告警 | Merge |
|
||||
| 道闸设备 | BarrierAdapter | 抬杆/落杆/记录 | Merge |
|
||||
| 报警设备 | AlarmAdapter | 查看告警/布防撤防 | Merge |
|
||||
|
||||
### 3.5 ExtraData 格式示例
|
||||
|
||||
```json
|
||||
// 摄像机 (Owl)
|
||||
{ "owlDeviceId": "gb_xxx", "protocol": "GB28181", "manufacturer": "海康" }
|
||||
|
||||
// 温湿度变送器 (MC4子设备)
|
||||
{ "mc4DeviceId": 1001, "pointIndex": 0, "unit": "℃", "isControlPoint": false }
|
||||
|
||||
// 空调控制器 (MC4子设备)
|
||||
{ "mc4DeviceId": 1002, "pointIndex": 2, "unit": null, "isControlPoint": true }
|
||||
```
|
||||
|
||||
### 3.6 层级示例
|
||||
|
||||
```
|
||||
gateway_nodes: gw-31ku
|
||||
|
||||
warehouse_regions → warehouse_devicepoint → base_device
|
||||
|
||||
例: 厂区 → 新库区 → 31号库房(点位) → 设备
|
||||
|
||||
base_device (PointId=点位ID, GatewayNodeId=gw-31ku.NodeId):
|
||||
NVR-01 (DeviceCategory=硬盘录像机, DeviceGroup=视频设备, IsParent=是)
|
||||
├── 通道01 (ParentDeviceId=NVR, video_channel.DeviceId=通道01.DeviceId, OwlStreamApp="rtp")
|
||||
├── 通道02 (ParentDeviceId=NVR, video_channel.DeviceId=通道02.DeviceId, OwlStreamApp="rtp")
|
||||
东北角高位摄像机 (DeviceCategory=摄像机, DeviceGroup=视频设备)
|
||||
动环采集器 (DeviceCategory=动环采集器, DeviceGroup=IoT设备, IsParent=是)
|
||||
├── 温湿度变送器 (ParentDeviceId=采集器, ExtraData={pointIndex:0,unit:"℃"})
|
||||
├── 空调控制器 (ParentDeviceId=采集器, ExtraData={pointIndex:2,isControlPoint:true})
|
||||
└── 紧急报警按钮 (DeviceGroup=报警设备, ParentDeviceId=采集器)
|
||||
|
||||
video_channel 表(通道Owl流信息):
|
||||
{ ChannelId=1, DeviceId=通道01.DeviceId(→base_device), OwlStreamApp="rtp", HasPtz=1 }
|
||||
{ ChannelId=2, DeviceId=通道02.DeviceId(→base_device), OwlStreamApp="rtp", HasPtz=0 }
|
||||
|
||||
> 通道 = base_device 子记录(名称/在线/层级) + video_channel 扩展记录(流地址/云台能力/录像模式)
|
||||
> video_channel 的 DeviceId 指向通道自身的 base_device.DeviceId(不是 NVR 的 DeviceId)
|
||||
> video_channel 的 OwlStreamApp/OwlStreamName/SnapshotUrl 为缓存,首次取流后写入,后续优先读缓存
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、Vol.Pro 同步接口(新增适配器零改动)
|
||||
|
||||
```csharp
|
||||
// POST /api/gateway/sync/devices
|
||||
public async Task SyncDevices(string nodeCode, List<StandardDevice> devices)
|
||||
{
|
||||
var node = await _db.gateway_nodes.FirstAsync(n => n.NodeCode == nodeCode);
|
||||
|
||||
// ★ 批量查询已有设备映射表 (用于 parentSourceId 解析)
|
||||
var existingIds = await _db.base_device
|
||||
.Where(x => x.GatewayNodeId == node.NodeId)
|
||||
.ToDictionaryAsync(x => (x.AdapterCode, x.SourceId), x => x.DeviceId);
|
||||
|
||||
foreach (var d in devices)
|
||||
{
|
||||
var key = (d.AdapterCode, d.SourceId);
|
||||
existingIds.TryGetValue(key, out var existingId);
|
||||
var entity = existingId > 0
|
||||
? await _db.base_device.FindAsync(existingId)
|
||||
: new base_device { AdapterCode = d.AdapterCode, SourceId = d.SourceId };
|
||||
|
||||
bool isNew = entity.DeviceId == 0;
|
||||
|
||||
// ★ 解析 parentSourceId → ParentDeviceId
|
||||
int? parentDeviceId = null;
|
||||
if (!string.IsNullOrEmpty(d.ParentSourceId))
|
||||
{
|
||||
existingIds.TryGetValue((d.AdapterCode, d.ParentSourceId), out var pid);
|
||||
if (pid > 0) parentDeviceId = pid;
|
||||
}
|
||||
|
||||
// 仅首次入库写管理员字段
|
||||
if (isNew)
|
||||
{
|
||||
entity.DeviceName = d.Name;
|
||||
entity.DeviceCategory = d.Category;
|
||||
entity.DeviceGroup = d.Group;
|
||||
}
|
||||
|
||||
// 每次同步写网关字段
|
||||
entity.IsOnline = d.IsOnline ? "在线" : "离线";
|
||||
entity.IsParent = d.IsParent ? "是" : "否";
|
||||
entity.ParentDeviceId = parentDeviceId;
|
||||
entity.ExtraData = d.ExtraDataJson;
|
||||
entity.GatewayNodeId = node.NodeId;
|
||||
entity.IpAddress = d.IpAddress;
|
||||
entity.Port = d.Port;
|
||||
entity.LastSyncTime = DateTime.UtcNow;
|
||||
|
||||
if (isNew) _db.base_device.Add(entity);
|
||||
else _db.base_device.Update(entity);
|
||||
}
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、管理端设备操作集成
|
||||
|
||||
> **设计决策**: 不再需要独立的设备管理页面。Vol.Pro 框架自带三级主从表显示能力(warehouse_regions → warehouse_devicepoint → base_device),可直接在框架生成的 base_device 列表页面中嵌入操作按钮,按 DeviceGroup 动态渲染。
|
||||
|
||||
### 5.1 操作按钮矩阵
|
||||
|
||||
| DeviceGroup | 操作按钮 | 说明 |
|
||||
|:---:|------|------|
|
||||
| 视频设备 | 实时预览 / 云台控制(仅方向键) / 查看回放 / 获取快照 / 同步通道 | 全部通过网关 B 组接口代理到 Owl |
|
||||
| IoT设备 | 查看实时数据 / 设备控制 / 刷新点位 / 查看告警 | 通过网关 B4/B5 代理到 MC4.0 |
|
||||
| 门禁设备 | 远程开门 / 查看记录 / 查看告警 | Phase 3 接入海康ISC后启用 |
|
||||
| 道闸设备 | 抬杆 / 落杆 / 查看记录 | Phase 3 接入 |
|
||||
| 报警设备 | 查看告警 / 布防撤防 | Phase 3 接入 |
|
||||
|
||||
### 5.2 前端按钮嵌入方案
|
||||
|
||||
框架生成的 base_device 列表页面的"操作"列默认只有"编辑/删除"。在 **base_deviceController Partial** 中扩展前端分组逻辑,替换默认操作列为自定义渲染。
|
||||
|
||||
```javascript
|
||||
// web.vite/src/extension/warehouse/base_device.jsx 或 views/warehouse/base_device/components/ActionColumn.vue
|
||||
const actionMap = {
|
||||
'视频设备': VideoDeviceActions, // 实时预览/云台/回放/快照/同步通道
|
||||
'IoT设备': IoTDeviceActions, // 实时数据/控制/刷新/告警
|
||||
'门禁设备': AccessDeviceActions, // 远程开门/记录/告警
|
||||
'道闸设备': BarrierDeviceActions,// 抬杆/落杆/记录
|
||||
'报警设备': AlarmDeviceActions, // 告警/布防撤防
|
||||
}
|
||||
|
||||
// 操作列模板中根据 row.deviceGroup 动态渲染对应组件
|
||||
<template v-for="(row, idx) in data">
|
||||
<component :is="actionMap[row.deviceGroup] || DefaultActions" :row="row" />
|
||||
</template>
|
||||
```
|
||||
|
||||
**要点**:
|
||||
- 框架"操作"列通过自定义插槽替换,不修改框架生成的 Vue 文件本体
|
||||
- 组件放在 `views/warehouse/base_device/components/` 下,防代码生成覆盖
|
||||
- 五个分组组件各自独立(~80-150 行),新增分组仅加一个文件
|
||||
|
||||
### 5.3 前端操作组件清单
|
||||
|
||||
| 文件 | 大小 | 说明 |
|
||||
|------|:---:|------|
|
||||
| `VideoDeviceActions.vue` | ~120行 | 预览/云台/回放/快照按钮组,预览弹窗内嵌 Jessibuca 播放器(或 <video> 标签回退),云台仅 ↑↓←→+ZOOM+停止 |
|
||||
| `IoTDeviceActions.vue` | ~100行 | 实时数据表格弹窗(轮询 B4),控制面板(B5 写值),告警快捷入口 |
|
||||
| `AccessDeviceActions.vue` | ~60行 | 远程开门按钮 + 记录查询入口 |
|
||||
| `BarrierDeviceActions.vue` | ~60行 | 抬杆/落杆按钮 |
|
||||
| `AlarmDeviceActions.vue` | ~60行 | 告警列表 + 布防/撤防开关 |
|
||||
|
||||
### 5.4 后端操作 API
|
||||
|
||||
> 以下 API 由网关 B 组接口代理,Vol.Pro 前端不直连子系统,统一经网关中转。
|
||||
|
||||
| 接口 | 方法 | 说明 | 对应网关接口 |
|
||||
|------|:---:|------|------|
|
||||
| `/api/gateway/streams/{adapter}/{sourceId}/live` | GET | 获取实时流地址 | B6a |
|
||||
| `/api/gateway/streams/{adapter}/{sourceId}/playback?start=&end=` | GET | 获取回放地址 | B6b |
|
||||
| `/api/gateway/streams/{adapter}/{sourceId}/snapshot` | POST | 获取截图 | — |
|
||||
| `/api/gateway/streams/{adapter}/{sourceId}/ptz` | POST | 云台方向控制 | B7 |
|
||||
| `/api/gateway/realtime/{adapter}/{sourceId}` | GET | 获取实时点位值 | B4 |
|
||||
| `/api/gateway/realtime/{adapter}/control` | POST | 设备控制写值 | B5 |
|
||||
| `/api/gateway/alarms/{adapter}` | GET | 分页查询告警 | B8 |
|
||||
| `/api/gateway/alarms/{adapter}/{alarmId}/confirm` | POST | 确认告警 | B9 |
|
||||
|
||||
> Vol.Pro 前端直接请求网关(:5100),不经过 Vol.Pro 后端中转。跨域问题通过网关 CORS 或 nginx 反向代理解决。
|
||||
|
||||
---
|
||||
|
||||
## 六、同步策略
|
||||
|
||||
### MC4.0 → 区域树+设备
|
||||
- type=1 节点 → 名称匹配 warehouse_regions → 绑区或新建
|
||||
- type=2 节点 → Upsert base_device (DeviceGroup=IoT设备, ExtraData 存点位属性)
|
||||
- 子设备在线状态由网关按 MC4.0 数据如实上报,Vol.Pro 不做推断
|
||||
- 模式: FullReplace, 频率限制: 2次/秒
|
||||
|
||||
### Owl → 设备
|
||||
- GET /devices → Upsert base_device (DeviceGroup=视频设备, IsParent=是)
|
||||
- GET /channels → Upsert base_device (ParentDeviceId=NVR) + video_channel 扩展记录
|
||||
- Owl 无区域概念 → PointId=NULL, 管理员手动分配
|
||||
- 可手动触发 `POST /devices/:id/catalog` 刷新通道目录
|
||||
- 模式: Merge
|
||||
|
||||
### 录像同步
|
||||
- 管理端点击"查看回放"时,网关实时调 Owl `GET /recordings` → 返回给前端 + 同步写入 video_record
|
||||
- Phase 3 可选:网关定时(每 10 分钟)后台同步录像记录
|
||||
|
||||
### Owl AI 事件(可选)
|
||||
- OwlAdapter 可选实现 IHasAlarms,将 Owl `GET /events` 的 YOLO AI 检测事件映射为 StandardAlarm,走 A4 告警同步
|
||||
|
||||
### 反方向写回
|
||||
|
||||
| 操作 | Owl | MC4.0 |
|
||||
|------|:---:|:-----:|
|
||||
| 设备改名 | ✅ PUT /devices/:id | ❌ |
|
||||
| 告警确认 | ⚠️ | ✅ |
|
||||
| 设备控制 | ✅ PTZ(仅方向键) | ✅ 点位写值 |
|
||||
|
||||
---
|
||||
|
||||
## 七、部署拓扑
|
||||
|
||||
```
|
||||
Docker: Owl+ZLM (:15123) MC4.0-1 (:3000) MC4.0-2 (:3000)
|
||||
| | |
|
||||
+----------+--------------+-------------------+
|
||||
|
|
||||
+----------+-----------+
|
||||
| Gateway gw-31ku | Gateway gw-11ku
|
||||
| :5100 | :5101
|
||||
+----------+-----------+
|
||||
|
|
||||
+----------+-----------+
|
||||
| VolPro.WebApi |
|
||||
| :9100 |
|
||||
| MySQL / Redis |
|
||||
+----------+-----------+
|
||||
|
|
||||
+--------------+---------------+
|
||||
| web.vite :9000 warehouse :9200 |
|
||||
+--------------------------------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、实施路线
|
||||
|
||||
| 阶段 | 工期 | 内容 |
|
||||
|------|------|------|
|
||||
| Phase 0 | Day 1-2 | Gateway骨架 + 6张表建表 + 代码生成 + 字典初始化 |
|
||||
| Phase 1 | Day 3-6 | OwlAdapter + base_device操作按钮组件(视频) + [可选]AI事件接入 |
|
||||
| Phase 2 | Day 7-11 | Mc4Adapter + IoT管理 + 区域树匹配 + SignalR |
|
||||
| Phase 3 | Day 12-17 | warehouse端改造 + 全链路联调 |
|
||||
| Phase 4 | Day 18-20 | 验证 + 缓冲 |
|
||||
|
||||
总计: 18-20 个工作日
|
||||
|
||||
---
|
||||
|
||||
## 九、新增整合流程
|
||||
|
||||
以接入「海康门禁」为例:
|
||||
1. 新建 IntegrationGateway.Adapters.HikvisionAccess 项目
|
||||
2. 实现 IHasFlatDevices + IHasAlarms → 设备同步时填充 DeviceGroup=门禁设备
|
||||
3. 管理端字典加一条"门禁设备"分组 → 按钮自动出现
|
||||
4. Vol.Pro 同步接口零改动(ExtraData 承载门禁字段)
|
||||
5. 前端新增 AccessDeviceActions.vue (~80行)
|
||||
总工作量: 1-2 天
|
||||
|
||||
---
|
||||
|
||||
## 十、代码组织规范
|
||||
|
||||
| 代码类型 | 路径 | 被覆盖? |
|
||||
|----------|------|:---:|
|
||||
| 第三方对接 | gateway/ | ❌ |
|
||||
| 扩展Controller | Controllers/*/Partial/ | ❌ |
|
||||
| Entity扩展 | DomainModels/*/partial/ | ❌ |
|
||||
| 前端业务逻辑 | extension/warehouse/*.jsx | ❌ |
|
||||
| 自定义页面 | views/warehouse/DeviceManager/ | ❌ |
|
||||
| 自动生成代码 | 生成器默认路径 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 附录 A:字典初始化清单
|
||||
|
||||
Phase 0 建表后需在 Vol.Pro 管理端创建以下数据字典:
|
||||
|
||||
| 字典名称 | 字典值 |
|
||||
|----------|--------|
|
||||
| 设备种类 | 门磁/空调/智能断路器/人行道闸/车辆道闸/485钥匙柜/网络钥匙柜/紧急报警按钮/红外报警器/门禁一体机/除湿_恒湿机/空调控制器/烟雾报警器/气体报警器/温湿度变送器/摄像机/硬盘录像机/动环采集器 |
|
||||
| 设备分组 | 视频设备/IoT设备/门禁设备/道闸设备/报警设备 |
|
||||
| 是否父设备 | 是/否 |
|
||||
| 在线状态 | 在线/离线 |
|
||||
| 启用状态 | 启用/禁用 |
|
||||
| 是否控制点 | 只读/可写 |
|
||||
| 告警等级 | 提示/普通/重要/紧急 |
|
||||
| 告警状态 | 未确认/已确认/已结束 |
|
||||
|
||||
---
|
||||
|
||||
## 附录 B:AdapterCode 格式规范
|
||||
|
||||
```
|
||||
格式: {AdapterType}:{InstanceName}
|
||||
示例: "MC4:31ku"、"Owl:main"、"HikvisionISC:center"
|
||||
规则:
|
||||
- AdapterType: 网关注册的 Adapter 类名,仅字母数字
|
||||
- InstanceName: 网关实例名称,仅字母数字下划线
|
||||
- 分隔符: ':'
|
||||
- base_device.AdapterCode 存储完整双段标识
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **版本历史**:
|
||||
> - v1.0 (2026-04-29) — 初始 Owl+MC4 独立方案
|
||||
> - v2.0 (2026-05-16) — 整合 Gateway 架构 + 数据字典 + ExtraData JSON
|
||||
> - v3.0 (2026-05-16) — Owl/ZLMediaKit 双文档验证 + 14 项修正(P0/P1/P2 修复)
|
||||
> - v3.1 (2026-05-17) — 第五章修订:取消独立设备管理页面,改为框架主从表嵌入操作按钮
|
||||
512
doc/设计文档/VolPro框架改造方案.md
Normal file
512
doc/设计文档/VolPro框架改造方案.md
Normal 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_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;
|
||||
}
|
||||
|
||||
/// <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_deviceController(B 组代理 + 设备树)
|
||||
|
||||
```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 框架规范
|
||||
260
doc/设计文档/VolPro框架改造方案_任务清单.md
Normal file
260
doc/设计文档/VolPro框架改造方案_任务清单.md
Normal file
@@ -0,0 +1,260 @@
|
||||
# Vol.Pro 框架前后端改造 — 任务清单
|
||||
|
||||
> **基准文档**: VolPro框架改造方案 v1.0
|
||||
> **分支**: phase/0-infrastructure
|
||||
> **原则**: 所有改动在 Partial/extension 目录,严禁修改框架生成文件
|
||||
|
||||
---
|
||||
|
||||
## Phase V0: 数据库与代码生成(预计 0.5 天)
|
||||
|
||||
### V0.1 建表
|
||||
- [ ] 在 SQL Server 执行 `doc/db_init.sql`,创建 6 张表:
|
||||
- `gateway_nodes`(网关节点)
|
||||
- `base_device`(统一设备主表,AdapterCode+SourceId 联合主键)
|
||||
- `video_channel`(视频通道扩展)
|
||||
- `video_record`(录像文件)
|
||||
- `iot_alarm`(告警记录)
|
||||
- `iot_devicedata`(数据归档)
|
||||
|
||||
### V0.2 字典初始化
|
||||
- [ ] 在 Vol.Pro 管理端 → 字典管理,创建 8 组数据字典:
|
||||
- 设备分组:视频设备/IoT设备/门禁设备/道闸设备/报警设备
|
||||
- 设备种类:摄像机/硬盘录像机/温湿度变送器/空调控制器/...(18 项)
|
||||
- 在线状态:在线/离线
|
||||
- 启用状态:启用/禁用
|
||||
- 是否父设备:是/否
|
||||
- 是否控制点:只读/可写
|
||||
- 告警等级:提示/普通/重要/紧急
|
||||
- 告警状态:未确认/已确认/已结束
|
||||
|
||||
### V0.3 代码生成
|
||||
- [ ] 在 Vol.Pro 代码生成器中选择 `device_manager` 数据源,生成 6 张表的全套代码:
|
||||
- `VolPro.Entity/DomainModels/device_manager/` — 6 个 Entity
|
||||
- `Warehouse/IRepositories/device_manager/` — 6 个 Repository 接口
|
||||
- `Warehouse/Repositories/device_manager/` — 6 个 Repository 实现
|
||||
- `Warehouse/IServices/device_manager/` — 6 个 Service 接口 + 6 个 Partial 接口
|
||||
- `Warehouse/Services/device_manager/` — 6 个 Service 实现 + 6 个 Partial Service
|
||||
- `VolPro.WebApi/Controllers/Warehouse/` — 6 个 Controller
|
||||
|
||||
### V0.4 配置主从表
|
||||
- [ ] 代码生成器配置 base_device 的 DetailTable:
|
||||
- 关联 video_channel(DeviceId=DeviceId)
|
||||
- 关联 iot_devicedata(DeviceId=DeviceId)
|
||||
- 关联 iot_alarm(DeviceId=DeviceId)
|
||||
- [ ] 代码生成器配置 base_device 的 ParentId 自引用字段(ParentDeviceId)
|
||||
|
||||
### V0.5 构建验证
|
||||
- [ ] VS 编译 Vol.Pro 解决方案,确认 0 错误
|
||||
- [ ] 管理端访问 base_device 页面 → 框架默认主从表三 Tab 渲染正常
|
||||
|
||||
> **V0 提交点**: `PhaseV0_db_codegen — 6 张表建表 + 字典 + 代码生成完毕,框架默认页面可访问`
|
||||
|
||||
---
|
||||
|
||||
## Phase V1: Entity 与 Service 扩展(预计 0.5 天)
|
||||
|
||||
### V1.1 Entity Partial
|
||||
- [ ] 创建 `VolPro.Entity/DomainModels/device_manager/partial/base_device.cs`
|
||||
- 添加导航属性:VideoChannel、Alarms、DeviceData
|
||||
- 添加网关字段白名单常量 `GatewayFields`
|
||||
- [ ] 创建 `VolPro.Entity/DomainModels/device_manager/partial/gateway_nodes.cs`
|
||||
- 添加 `AdapterList` 属性(从 AdapterTypes 逗号分隔解析)
|
||||
- [ ] 其他 4 个 Entity Partial 留空(框架生成即够用)
|
||||
|
||||
### V1.2 Service Partial — gateway_nodesService
|
||||
- [ ] 编辑 `Warehouse/Services/device_manager/Partial/gateway_nodesService.cs`
|
||||
- 注入 `Igateway_nodesRepository` + `IHttpContextAccessor`(`[ActivatorUtilitiesConstructor]`)
|
||||
- 实现 `RegisterNodeAsync(nodeCode, token, adapterTypes, baseUrl)` — Upsert 逻辑
|
||||
- 实现 `UpdateHeartbeatAsync(nodeCode, token)` — 更新心跳
|
||||
- 实现 `SyncDevicesAsync(gatewayNodeId, List<StandardDevice>)` — 字段分治 + parentSourceId 映射
|
||||
|
||||
### V1.3 Service Partial — base_deviceService
|
||||
- [ ] 编辑 `Warehouse/Services/device_manager/Partial/base_deviceService.cs`
|
||||
- 注入 `Ibase_deviceRepository`
|
||||
- 实现 `GetDevicesByGatewayNodeAsync(gatewayNodeId)` — 网关注册时返回设备列表
|
||||
- 实现 `UpsertDeviceAsync(StandardDevice, gatewayNodeId, existingIds)` — 字段分治
|
||||
|
||||
### V1.4 Service Partial — iot_alarmService
|
||||
- [ ] 编辑 `Warehouse/Services/device_manager/Partial/iot_alarmService.cs`
|
||||
- 注入 `Iiot_alarmRepository`
|
||||
- 实现 `UpsertAlarmAsync(StandardAlarm)` — SourceAlarmId 去重
|
||||
|
||||
### V1.5 构建验证
|
||||
- [ ] VS 编译 → 0 错误
|
||||
|
||||
> **V1 提交点**: `PhaseV1_entity_service — Entity 导航属性 + Service 同步方法全部就绪`
|
||||
|
||||
---
|
||||
|
||||
## Phase V2: Controller 扩展(预计 1 天)
|
||||
|
||||
### V2.1 gateway_nodesController — A1 网关注册
|
||||
- [ ] 编辑 `Controllers/Warehouse/Partial/gateway_nodesController.cs`
|
||||
- 添加 `[HttpPost, Route("/api/gateway/register"), AllowAnonymous]`
|
||||
- 认证:NodeToken 验证
|
||||
- Upsert:存在则更新 AdapterTypes/BaseUrl/IsOnline,不存在且 Token 有效则 Insert
|
||||
- 返回:`{ nodeId, devices }` — 当前网关的顶层设备列表
|
||||
|
||||
### V2.2 gateway_nodesController — A2 心跳
|
||||
- [ ] 添加 `[HttpPost, Route("/api/gateway/heartbeat"), AllowAnonymous]`
|
||||
- 认证:NodeToken
|
||||
- 更新:IsOnline="在线" + LastHeartbeat=now
|
||||
- 返回:`{ status: "ok", serverTime }`
|
||||
|
||||
### V2.3 gateway_nodesController — A3 设备同步
|
||||
- [ ] 添加 `[HttpPost, Route("/api/gateway/sync/devices"), AllowAnonymous]`
|
||||
- 认证:NodeToken
|
||||
- 批量查已有 DeviceId 映射表
|
||||
- 遍历设备:字段分治写入(首次全量,后续仅网关字段)
|
||||
- parentSourceId → ParentDeviceId 解析
|
||||
- 返回:`{ added, updated, removed }`
|
||||
|
||||
### V2.4 gateway_nodesController — A4 告警同步
|
||||
- [ ] 添加 `[HttpPost, Route("/api/gateway/sync/alarms"), AllowAnonymous]`
|
||||
- 认证:NodeToken
|
||||
- 批量查 deviceSourceId → DeviceId 映射
|
||||
- SourceAlarmId 去重
|
||||
- 写入 iot_alarm,State="未确认"
|
||||
- 返回:`{ added }`
|
||||
|
||||
### V2.5 base_deviceController — 设备树
|
||||
- [ ] 编辑 `Controllers/Warehouse/Partial/base_deviceController.cs`
|
||||
- [ ] 添加 `[HttpGet, Route("/api/DeviceManager/GetRegionTree")]`
|
||||
- 查询:warehouse_regions JOIN warehouse_devicepoint
|
||||
- 构建树形结构(region → point,含 deviceCount)
|
||||
- 返回:`[{ id, label, type, children, deviceCount }]`
|
||||
- [ ] 添加 `[HttpGet, Route("/api/DeviceManager/GetDevicesByPoint")]`
|
||||
- 查询:base_device WHERE PointId=pointId(含子设备递归)
|
||||
- 分页返回:`{ items, total }`
|
||||
|
||||
### V2.6 权限配置
|
||||
- [ ] A1-A4 接口加 `[AllowAnonymous]`(内部 Token 二次认证)
|
||||
- [ ] GetRegionTree/GetDevicesByPoint 走框架 JWT 权限
|
||||
|
||||
### V2.7 构建验证
|
||||
- [ ] VS 编译 → 0 错误
|
||||
- [ ] Postman 测试 A1-A4(Mock Token)
|
||||
- [ ] 管理端访问 GetRegionTree → 返回 JSON
|
||||
|
||||
> **V2 提交点**: `PhaseV2_controller — 6 个 API 全部就绪,A1-A4 AllowAnonymous,设备树可查`
|
||||
|
||||
---
|
||||
|
||||
## Phase V3: 基础设施与定时任务(预计 0.5 天)
|
||||
|
||||
### V3.1 GatewayClient
|
||||
- [ ] 创建 `Warehouse/Services/GatewayClient.cs`
|
||||
- 实现 `TriggerFullSyncAsync(baseUrl, adapterTypes)` — 调网关 B3
|
||||
- 实现 `GetRealtimeAsync(baseUrl, adapter, deviceId)` — 调网关 B4
|
||||
- 实现 `ControlDeviceAsync(baseUrl, adapter, deviceId, pointIndex, value)` — 调网关 B5
|
||||
|
||||
### V3.2 Quartz Job — SyncDevicesJob
|
||||
- [ ] 创建 `Warehouse/Services/SyncDevicesJob.cs`
|
||||
- 实现 `IJob` 接口
|
||||
- 遍历 `gateway_nodes WHERE IsOnline=在线 AND Enable=启用`
|
||||
- 逐个调 `GatewayClient.TriggerFullSyncAsync`
|
||||
|
||||
### V3.3 Quartz Job — HeartbeatMonitorJob
|
||||
- [ ] 创建 `Warehouse/Services/HeartbeatMonitorJob.cs`
|
||||
- 扫描心跳超时 30s 的网关 → 标记离线
|
||||
- 级联:`base_device WHERE GatewayNodeId=离线节点Id → IsOnline=离线`
|
||||
|
||||
### V3.4 Quartz Job — RealtimePollJob(Phase 2)
|
||||
- [ ] 创建 `Warehouse/Services/RealtimePollJob.cs`(骨架,Phase 2 完善)
|
||||
|
||||
### V3.5 Startup.cs 注册
|
||||
- [ ] 注册 `IHttpClientFactory`(Named: "VolPro")
|
||||
- [ ] 注册 `GatewayClient` 为 Singleton
|
||||
- [ ] 确认 Quartz Job 在管理端可配置(JobDataMap 传入 ServiceProvider)
|
||||
|
||||
### V3.6 Job 注册
|
||||
- [ ] 在 Vol.Pro 管理端 → Quartz 管理 → 新建 3 个 Job:
|
||||
- SyncDevicesJob:Cron "0 */5 * * * ?"
|
||||
- HeartbeatMonitorJob:Cron "0/15 * * * * ?"
|
||||
- RealtimePollJob:Cron "0/10 * * * * ?"
|
||||
|
||||
### V3.7 构建验证
|
||||
- [ ] VS 编译 → 0 错误
|
||||
- [ ] GatewayClient 可通过 Swagger 测试
|
||||
|
||||
> **V3 提交点**: `PhaseV3_infrastructure — GatewayClient + 3 Job + Startup 注册完毕`
|
||||
|
||||
---
|
||||
|
||||
## Phase V4: 前端改造(预计 1.5 天)
|
||||
|
||||
### V4.1 目录创建
|
||||
- [ ] 创建 `web.vite/src/views/warehouse/base_device/components/` 目录
|
||||
- [ ] 创建 `web.vite/src/extension/warehouse/` 目录
|
||||
- [ ] 创建 `web.vite/src/api/gateway.js` — 网关 B 组 API 封装
|
||||
|
||||
### V4.2 gateway.js API 封装
|
||||
- [ ] `getStreamUrl(adapter, deviceId)` → GET 流地址
|
||||
- [ ] `ptzControl(adapter, deviceId, direction)` → POST 云台控制
|
||||
- [ ] `getRealtime(adapter, deviceId)` → GET 实时点值
|
||||
- [ ] `controlDevice(adapter, deviceId, pointIndex, value)` → POST 控制
|
||||
- [ ] `getAlarms(adapter, params)` → GET 告警列表
|
||||
- [ ] `confirmAlarm(adapter, alarmId)` → POST 告警确认
|
||||
|
||||
### V4.3 视频操作组件
|
||||
- [ ] 创建 `VideoDeviceActions.vue` — 按钮组(预览/云台/回放/快照/同步通道)
|
||||
- [ ] 创建 `DeviceLivePreview.vue` — Jessibuca 播放器弹窗(<video> 回退)
|
||||
- 调 `getStreamUrl` → 获取 WS-FLV/HLS 地址
|
||||
- [ ] 创建 `PtzControlPanel.vue` — 方向键面板(↑↓←→+ZOOM+停止)
|
||||
- mousedown 开始移动,mouseup 停止
|
||||
|
||||
### V4.4 IoT 操作组件
|
||||
- [ ] 创建 `IoTDeviceActions.vue` — 按钮组(实时数据/控制/刷新/告警)
|
||||
- [ ] 创建 `RealtimeDataPanel.vue` — 实时数值弹窗(5s 自动轮询 B4)
|
||||
- [ ] 创建 `DeviceControlPanel.vue` — 控制写值面板(B5)
|
||||
|
||||
### V4.5 通用操作组件
|
||||
- [ ] 创建 `DeviceEditDialog.vue` — 设备编辑弹窗(管理员字段)
|
||||
- [ ] 创建 `MapBindingPanel.vue` — 地图模型绑定面板(模型ID/缩放/旋转)
|
||||
|
||||
### V4.6 未来组件(骨架)
|
||||
- [ ] 创建 `AccessDeviceActions.vue` — 门禁按钮组(远程开门)
|
||||
- [ ] 创建 `BarrierDeviceActions.vue` — 道闸按钮组(抬杆/落杆)
|
||||
- [ ] 创建 `AlarmDeviceActions.vue` — 报警按钮组(告警/布防撤防)
|
||||
|
||||
### V4.7 扩展注入
|
||||
- [ ] 创建 `web.vite/src/extension/warehouse/base_device.jsx`
|
||||
- 注册操作列插槽 `col-action`
|
||||
- 按 `row.deviceGroup` 动态渲染对应按钮组件
|
||||
- 视频设备 → VideoDeviceActions
|
||||
- IoT 设备 → IoTDeviceActions
|
||||
- 其他 → 框架默认编辑/删除按钮
|
||||
|
||||
### V4.8 构建验证
|
||||
- [ ] 前端 `npm run dev` 启动
|
||||
- [ ] 访问 base_device 页面 → 操作列根据 DeviceGroup 显示不同按钮
|
||||
- [ ] 点击"预览"→ 弹窗打开 → 获取流地址
|
||||
|
||||
> **V4 提交点**: `PhaseV4_frontend — 11 个组件 + extension 注入完成,操作列动态渲染`
|
||||
|
||||
---
|
||||
|
||||
## Phase V5: 联调验证(需网关+子系统就绪,预计 1 天)
|
||||
|
||||
### V5.1 端到端同步
|
||||
- [ ] 启动网关 → 调 A1 注册 → gateway_nodes 表新增记录
|
||||
- [ ] 网关调 A3 上送设备 → base_device 表有新设备
|
||||
- [ ] 管理端 base_device 页面 → 列表中显示新设备
|
||||
|
||||
### V5.2 视频操作
|
||||
- [ ] 视频设备行 → 点击"实时预览" → 弹窗播放 Owl 视频流
|
||||
- [ ] 视频设备行 → 云台方向键操作 → Owl PTZ 响应
|
||||
|
||||
### V5.3 IoT 操作
|
||||
- [ ] IoT 设备行 → 点击"实时数据" → 显示 MC4.0 温湿度值
|
||||
|
||||
### V5.4 告警确认
|
||||
- [ ] MC4.0 触发告警 → 网关 A4 上送 → iot_alarm 表新增
|
||||
- [ ] 管理端确认告警 → 通过 B9 写回 MC4.0
|
||||
|
||||
> **V5 提交点**: `PhaseV5_integration — 端到端同步 + 视频操作 + IoT 数据 + 告警确认全链路跑通`
|
||||
|
||||
---
|
||||
|
||||
> **总周期**: V0-V5 预计 5 个工作日(不含联调等待时间)
|
||||
703
doc/设计文档/对接网关设计文档.md
Normal file
703
doc/设计文档/对接网关设计文档.md
Normal file
@@ -0,0 +1,703 @@
|
||||
# IntegrationGateway 对接网关详细设计文档
|
||||
|
||||
> **版本**: 1.0
|
||||
> **日期**: 2025-05-17
|
||||
> **基准**: SecMPS 整合方案 v3.0
|
||||
> **作者**: 架构组
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
IntegrationGateway 是 SecMPS 整合方案 v3.0 的核心组件,定位为 Vol.Pro 管理端与各物联子系统之间的**协议适配中间层**。网关对外提供统一 REST API,对内通过适配器模式对接异构子系统,实现"适配一次,多处复用"。
|
||||
|
||||
### 1.1 设计目标
|
||||
|
||||
| 目标 | 度量 |
|
||||
|------|------|
|
||||
| 适配器热插拔 | 新增子系统不改网关核心,仅加 Adapter 项目 |
|
||||
| 故障隔离 | 任一适配器故障不影响其他适配器和网关注册 |
|
||||
| 无状态部署 | 网关不存数据库,配置仅 NodeCode/Token/VolProUrl |
|
||||
| 编译独立性 | `dotnet build` 0 错误,不依赖 Vol.Pro 运行时 |
|
||||
|
||||
### 1.2 技术栈
|
||||
|
||||
| 层面 | 选型 |
|
||||
|------|------|
|
||||
| 运行时 | .NET 8 |
|
||||
| Web 框架 | ASP.NET Core Minimal API |
|
||||
| HTTP 客户端 | IHttpClientFactory + SocketsHttpHandler |
|
||||
| 序列化 | System.Text.Json |
|
||||
| 容器化 | Docker (可选) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 项目结构
|
||||
|
||||
```
|
||||
gateway/
|
||||
├── IntegrationGateway.sln
|
||||
└── src/
|
||||
├── IntegrationGateway.Core/ # 核心抽象(被所有项目引用)
|
||||
│ ├── Abstractions/ # 7 个能力接口
|
||||
│ │ ├── IHasOwnDeviceTree.cs
|
||||
│ │ ├── IHasFlatDevices.cs
|
||||
│ │ ├── IHasPoints.cs
|
||||
│ │ ├── IHasStreams.cs
|
||||
│ │ ├── IHasAlarms.cs
|
||||
│ │ ├── IHasRecordings.cs
|
||||
│ │ └── IAcceptsMetadataPush.cs
|
||||
│ ├── Models/ # 统一模型
|
||||
│ │ ├── StandardDevice.cs
|
||||
│ │ ├── StandardAlarm.cs
|
||||
│ │ ├── StandardRecording.cs
|
||||
│ │ ├── DeviceTreeNode.cs
|
||||
│ │ ├── PointValue.cs
|
||||
│ │ ├── StreamUrls.cs
|
||||
│ │ ├── PagedResult.cs
|
||||
│ │ ├── AdapterCapabilities.cs
|
||||
│ │ └── MetadataChangeSet.cs
|
||||
│ └── Infrastructure/ # 基础设施
|
||||
│ ├── AdapterRegistry.cs # 适配器注册中心
|
||||
│ ├── RateLimiter.cs # 令牌桶限流器
|
||||
│ └── GatewayClientFactory.cs # HTTP 客户端工厂
|
||||
│
|
||||
├── IntegrationGateway.Adapters.Owl/ # Owl 适配器
|
||||
│ ├── OwlAdapter.cs # 实现 IHasFlatDevices + IHasStreams
|
||||
│ └── OwlAuthHelper.cs # RSA 加密登录
|
||||
│
|
||||
├── IntegrationGateway.Adapters.MC4/ # MC4.0 适配器
|
||||
│ ├── Mc4Adapter.cs # 实现 IHasOwnDeviceTree + IHasPoints + IHasAlarms
|
||||
│ └── Mc4AuthHelper.cs # Token 认证
|
||||
│
|
||||
└── IntegrationGateway.Host/ # 宿主(启动项目)
|
||||
├── Program.cs # 路由注册 + 适配器初始化
|
||||
└── appsettings.json # 适配器连接配置
|
||||
```
|
||||
|
||||
### 2.1 依赖关系
|
||||
|
||||
```
|
||||
Host → Adapters.Owl → Core
|
||||
Host → Adapters.MC4 → Core
|
||||
Host → Core
|
||||
```
|
||||
|
||||
适配器项目不互相引用,Core 项目零外部依赖(仅 Microsoft.Extensions.*)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心接口体系
|
||||
|
||||
### 3.1 接口总览
|
||||
|
||||
```csharp
|
||||
namespace IntegrationGateway.Core.Abstractions
|
||||
{
|
||||
/// <summary>所有适配器必须实现的基础接口</summary>
|
||||
public interface IGatewayAdapter
|
||||
{
|
||||
string AdapterCode { get; } // "Owl:main" / "MC4:31ku"
|
||||
string DisplayName { get; } // 人类可读名称
|
||||
AdapterCapabilities Capabilities { get; } // 能力声明
|
||||
Task InitializeAsync(); // 懒加载初始化
|
||||
Task<bool> HealthCheckAsync(); // 健康检查
|
||||
}
|
||||
|
||||
/// <summary>扁平设备列表(Owl/门禁/道闸)</summary>
|
||||
public interface IHasFlatDevices : IGatewayAdapter
|
||||
{
|
||||
Task<PagedResult<StandardDevice>> GetDevicesAsync(int page, int size, string? keyword);
|
||||
}
|
||||
|
||||
/// <summary>自有对象树(MC4.0)</summary>
|
||||
public interface IHasOwnDeviceTree : IGatewayAdapter
|
||||
{
|
||||
Task<List<DeviceTreeNode>> GetObjectTreeAsync();
|
||||
}
|
||||
|
||||
/// <summary>实时点位值(MC4.0 动环)</summary>
|
||||
public interface IHasPoints : IGatewayAdapter
|
||||
{
|
||||
Task<List<PointValue>> GetRealtimeValuesAsync(string sourceDeviceId);
|
||||
Task SetPointValueAsync(string sourceDeviceId, int pointIndex, double value);
|
||||
}
|
||||
|
||||
/// <summary>视频流(Owl)</summary>
|
||||
public interface IHasStreams : IGatewayAdapter
|
||||
{
|
||||
Task<StreamUrls> GetLiveUrlAsync(string channelId);
|
||||
Task<StreamUrls> GetPlaybackUrlAsync(string channelId, DateTime start, DateTime end);
|
||||
Task PtzControlAsync(string channelId, string direction, float speed);
|
||||
Task PtzStopAsync(string channelId);
|
||||
Task<StreamUrls> GetSnapshotAsync(string channelId);
|
||||
}
|
||||
|
||||
/// <summary>告警(MC4.0 + Owl AI可选)</summary>
|
||||
public interface IHasAlarms : IGatewayAdapter
|
||||
{
|
||||
Task<PagedResult<StandardAlarm>> GetAlarmsAsync(
|
||||
int page, int size, DateTime from, DateTime to,
|
||||
string? level = null, string? state = null);
|
||||
Task ConfirmAlarmAsync(string alarmId);
|
||||
Task EndAlarmAsync(string alarmId);
|
||||
}
|
||||
|
||||
/// <summary>录像回放(Owl)</summary>
|
||||
public interface IHasRecordings : IGatewayAdapter
|
||||
{
|
||||
Task<PagedResult<StandardRecording>> GetRecordingsAsync(
|
||||
string channelId, DateTime start, DateTime end, int page, int size);
|
||||
}
|
||||
|
||||
/// <summary>元数据回写(Owl 设备改名等)</summary>
|
||||
public interface IAcceptsMetadataPush : IGatewayAdapter
|
||||
{
|
||||
Task<MetadataPushResult> PushMetadataAsync(string sourceDeviceId, MetadataChangeSet changes);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 接口设计原则
|
||||
|
||||
- **显式优于隐式**:每个接口明确声明一种能力,适配器按需实现,网关通过 `is` 检查自动发现路由
|
||||
- **异步优先**:所有方法返回 `Task`/`Task<T>`,避免阻塞线程
|
||||
- **统一分页**:`PagedResult<T>` 统一 page/size 语义,适配器内部完成 skip/limit 转换
|
||||
- **弹性 Extra**:`Dictionary<string, object?>` 承载适配器特有属性,不污染核心模型
|
||||
|
||||
### 3.3 适配器能力矩阵
|
||||
|
||||
```
|
||||
Owl MC4.0 门禁(未来)
|
||||
IGatewayAdapter ✅ ✅ ✅
|
||||
IHasOwnDeviceTree - ✅ -
|
||||
IHasFlatDevices ✅ - ✅
|
||||
IHasPoints - ✅ -
|
||||
IHasStreams ✅ - -
|
||||
IHasAlarms ⚠️ ✅ ✅
|
||||
IHasRecordings ✅ - -
|
||||
IAcceptsMetadata ✅ - ⚠️
|
||||
```
|
||||
|
||||
### 3.4 接口扩展规则
|
||||
|
||||
新增子系统时:
|
||||
1. 如果现有接口能覆盖 → 直接实现对应接口,零网关改动
|
||||
2. 如果现有接口不覆盖 → Core 中新增接口(如 `IHasFaceRecognition`),不能改已有接口签名
|
||||
3. 新增能力接口后 → Controller 加一个 `if (adapter is INewFeature)` 分支即可
|
||||
|
||||
---
|
||||
|
||||
## 4. 统一模型设计
|
||||
|
||||
### 4.1 StandardDevice
|
||||
|
||||
```csharp
|
||||
public class StandardDevice
|
||||
{
|
||||
public int DeviceId { get; set; } // Vol.Pro 侧主键(同步后回填)
|
||||
public string AdapterCode { get; set; } = ""; // "Owl:main"
|
||||
public string SourceId { get; set; } = ""; // 子系统原始ID (GB28181编码 / MC4 sid)
|
||||
public string Name { get; set; } = ""; // 设备名称
|
||||
public string Category { get; set; } = ""; // 摄像机/温湿度变送器/...
|
||||
public string Group { get; set; } = ""; // 视频设备/IoT设备/...
|
||||
public bool IsParent { get; set; } // 是否有子设备
|
||||
public string? ParentSourceId { get; set; } // 父设备SourceId(层级关系)
|
||||
public bool IsOnline { get; set; } // 在线状态
|
||||
public string? IpAddress { get; set; } // IP地址
|
||||
public int? Port { get; set; } // 端口
|
||||
public Dictionary<string, object?>? Extra { get; set; } // 适配器扩展JSON
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 字段映射规则(字段分治)
|
||||
|
||||
| StandardDevice | base_device | 写入策略 |
|
||||
|:---|---|:---:|
|
||||
| Name | DeviceName | 仅首次 |
|
||||
| Category | DeviceCategory | 仅首次 |
|
||||
| Group | DeviceGroup | 仅首次 |
|
||||
| IsOnline | IsOnline | 每次覆盖 |
|
||||
| IsParent | IsParent | 每次覆盖 |
|
||||
| ParentSourceId | ParentDeviceId | 每次覆盖(解析映射) |
|
||||
| IpAddress | IpAddress | 每次覆盖 |
|
||||
| Port | Port | 每次覆盖 |
|
||||
| Extra | ExtraData | 每次覆盖 |
|
||||
| AdapterCode + SourceId | (AdapterCode, SourceId) | 联合唯一键 |
|
||||
|
||||
### 4.3 DeviceTreeNode(对象树)
|
||||
|
||||
```csharp
|
||||
public class DeviceTreeNode
|
||||
{
|
||||
public int Id { get; set; } // MC4.0 原始ID
|
||||
public string SourceId { get; set; } = ""; // 转换为 string 的源ID
|
||||
public string Name { get; set; } = ""; // 节点名称
|
||||
public int Type { get; set; } // 1=区域, 2=设备
|
||||
public int ObjectType { get; set; } // MC4.0 对象类型
|
||||
public string? Tag { get; set; } // 标签(温湿度/烟雾/门磁...)
|
||||
public Dictionary<string, object?>? Option { get; set; } // 扩展属性
|
||||
public List<DeviceTreeNode> Children { get; set; } = new();
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 PagedResult<T>
|
||||
|
||||
```csharp
|
||||
public class PagedResult<T>
|
||||
{
|
||||
public List<T> Items { get; set; } = new();
|
||||
public int Total { get; set; }
|
||||
public int Page => 0; // 由调用方设置
|
||||
public int Size => 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 4.5 其他模型
|
||||
|
||||
```
|
||||
StreamUrls → { WsFlv, HttpFlv, Hls, WebRtc, Rtmp, Rtsp }
|
||||
StandardAlarm → { AlarmId, DeviceId, AdapterCode, Level, Title, Content, OccurTime, Status, ... }
|
||||
StandardRecording → { Id, ChannelId, StartedAt, EndedAt, Duration, FilePath, Size }
|
||||
PointValue → { SourceDeviceId, PointIndex, Value, UpdateTime, Interval }
|
||||
MetadataChangeSet → { Name?, Category?, Group?, Extra? }
|
||||
AdapterCapabilities → { HasObjectTree, HasPoints, HasStreams, HasAlarms, HasRecordings, AcceptsControl, AcceptsMetadataPush }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 基础设施设计
|
||||
|
||||
### 5.1 AdapterRegistry
|
||||
|
||||
```csharp
|
||||
public class AdapterRegistry
|
||||
{
|
||||
private readonly List<IGatewayAdapter> _adapters = new();
|
||||
|
||||
public void Register(IGatewayAdapter adapter) => _adapters.Add(adapter);
|
||||
|
||||
public async Task InitializeAllAsync()
|
||||
{
|
||||
// 并行初始化,单个失败不影响其他
|
||||
await Task.WhenAll(_adapters.Select(a => Task.Run(async () =>
|
||||
{
|
||||
try { await a.InitializeAsync(); }
|
||||
catch (Exception ex) { Log.Error($"Adapter {a.AdapterCode} init failed: {ex.Message}"); }
|
||||
})));
|
||||
}
|
||||
|
||||
public IReadOnlyList<IGatewayAdapter> All => _adapters.AsReadOnly();
|
||||
|
||||
public T? FindByCode<T>(string adapterCode) where T : class, IGatewayAdapter
|
||||
=> _adapters.FirstOrDefault(a => a.AdapterCode == adapterCode && a is T) as T;
|
||||
|
||||
public IGatewayAdapter? FindByCode(string adapterCode)
|
||||
=> _adapters.FirstOrDefault(a => a.AdapterCode == adapterCode);
|
||||
}
|
||||
```
|
||||
|
||||
**设计要点**:
|
||||
- 列表存储,O(1) 注册,O(n) 查找(n≤5,可接受)
|
||||
- 初始化失败不回滚,适配器标记为离线
|
||||
- 网关启动时通过 `POST /register` 上报 `AdapterTypes` 给 Vol.Pro
|
||||
|
||||
### 5.2 RateLimiter(令牌桶)
|
||||
|
||||
```csharp
|
||||
public class RateLimiter
|
||||
{
|
||||
private readonly SemaphoreSlim _semaphore;
|
||||
private readonly int _tokensPerSecond;
|
||||
|
||||
public RateLimiter(int tokensPerSecond)
|
||||
{
|
||||
_tokensPerSecond = tokensPerSecond;
|
||||
_semaphore = new SemaphoreSlim(tokensPerSecond, tokensPerSecond);
|
||||
}
|
||||
|
||||
public async Task WaitAsync(CancellationToken ct = default)
|
||||
{
|
||||
await _semaphore.WaitAsync(ct);
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1000 / _tokensPerSecond);
|
||||
_semaphore.Release();
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**配置策略**:
|
||||
| 适配器 | QPS 限制 | 原因 |
|
||||
|--------|:---:|------|
|
||||
| Owl | 5 | 按文档推荐 |
|
||||
| MC4.0 | 2 | MC4.0 API 限频 |
|
||||
|
||||
### 5.3 HttpClient 工厂
|
||||
|
||||
```csharp
|
||||
// Program.cs 注册
|
||||
builder.Services.AddHttpClient("VolPro", c =>
|
||||
{
|
||||
c.BaseAddress = new Uri("http://localhost:9100");
|
||||
c.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||
c.Timeout = TimeSpan.FromSeconds(30);
|
||||
}).ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
|
||||
{
|
||||
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
|
||||
MaxConnectionsPerServer = 10
|
||||
});
|
||||
```
|
||||
|
||||
**策略**:
|
||||
- 命名 HttpClient(`"VolPro"` 用于调 Vol.Pro,适配器内部自行创建 AuthenticatedClient)
|
||||
- 连接池复用,5 分钟生命周期
|
||||
- 超时 30 秒,防止第三方 API 慢响应阻塞
|
||||
|
||||
---
|
||||
|
||||
## 6. 认证与安全
|
||||
|
||||
### 6.1 网关注册认证(A1-A4)
|
||||
|
||||
```
|
||||
网关持有: NodeCode + NodeToken(管理端分配)
|
||||
注册流程:
|
||||
1. 网关 → POST /api/gateway/register { nodeCode, token, ... }
|
||||
2. Vol.Pro 查询 gateway_nodes WHERE NodeCode = req.NodeCode
|
||||
- 存在 → 比对 NodeToken
|
||||
- 不存在 → NodeToken 验证通过后 Insert
|
||||
3. 认证失败 → 401
|
||||
4. 成功后返回 NodeId + 设备列表
|
||||
```
|
||||
|
||||
### 6.2 Owl 认证(RSA 加密)
|
||||
|
||||
```
|
||||
1. GET /login/key → 获取 RSA 公钥 (Base64)
|
||||
2. 用公钥加密 { username, password } → Base64
|
||||
3. POST /login { data: <Base64密文> } → 获取 JWT Token
|
||||
4. Token 有效期: 3 天
|
||||
5. 后续请求: Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**安全要点**:
|
||||
- Token 内存缓存,不落盘
|
||||
- Token 过期前 1 小时自动刷新(懒刷新策略)
|
||||
- 认证失败不清除缓存 → 重试 3 次后 `Invalidate()`
|
||||
|
||||
### 6.3 MC4.0 认证
|
||||
|
||||
```
|
||||
1. POST /api/central/auth/conf/get → 获取临时 Token
|
||||
2. Token 有效期: 8 小时(保守估计)
|
||||
3. 后续请求: header["token"] = <token>
|
||||
```
|
||||
|
||||
### 6.4 网关内部接口(B 组)
|
||||
|
||||
B 组接口供管理端或 Vol.Pro 内部调用,认证方式:
|
||||
- 内网部署:IP 白名单(Simple)
|
||||
- 外网部署:共享 Secret Key(HMAC 签名),Phase 4 实现
|
||||
|
||||
---
|
||||
|
||||
## 7. 路由设计
|
||||
|
||||
### 7.1 网关主动接口(调 Vol.Pro,不暴露给外部)
|
||||
|
||||
网关内部通过 `VolProClient` 调用,不在 Minimal API 中注册路由。
|
||||
|
||||
```
|
||||
POST {VolProBaseUrl}/api/gateway/register A1
|
||||
POST {VolProBaseUrl}/api/gateway/heartbeat A2
|
||||
POST {VolProBaseUrl}/api/gateway/sync/devices A3
|
||||
POST {VolProBaseUrl}/api/gateway/sync/alarms A4
|
||||
```
|
||||
|
||||
### 7.2 网关暴露接口(B 组,供管理端调用)
|
||||
|
||||
```csharp
|
||||
// Program.cs 路由注册伪代码
|
||||
app.MapGet("/api/gateway/health", async (AdapterRegistry reg) => {
|
||||
var results = await Task.WhenAll(reg.All.Select(async a => new {
|
||||
a.AdapterCode, a.DisplayName,
|
||||
Healthy = await a.HealthCheckAsync(),
|
||||
a.Capabilities
|
||||
}));
|
||||
return Results.Ok(results);
|
||||
});
|
||||
|
||||
app.MapGet("/api/gateway/devices", async (string adapter, int page, int size, string? keyword, AdapterRegistry reg) => {
|
||||
var a = reg.FindByCode<IHasFlatDevices>(adapter);
|
||||
if (a == null) return Results.NotFound();
|
||||
return Results.Ok(await a.GetDevicesAsync(page, size, keyword));
|
||||
});
|
||||
|
||||
app.MapGet("/api/gateway/tree", async (string adapter, AdapterRegistry reg) => {
|
||||
var a = reg.FindByCode<IHasOwnDeviceTree>(adapter);
|
||||
if (a == null) return Results.NotFound();
|
||||
return Results.Ok(await a.GetObjectTreeAsync());
|
||||
});
|
||||
|
||||
app.MapGet("/api/gateway/streams/{adapter}/{deviceId}/live", async (string adapter, string deviceId, AdapterRegistry reg) => {
|
||||
var a = reg.FindByCode<IHasStreams>(adapter);
|
||||
if (a == null) return Results.NotFound();
|
||||
return Results.Ok(await a.GetLiveUrlAsync(deviceId));
|
||||
});
|
||||
|
||||
app.MapPost("/api/gateway/streams/{adapter}/{deviceId}/ptz", async (string adapter, string deviceId, PtzRequest req, AdapterRegistry reg) => {
|
||||
var a = reg.FindByCode<IHasStreams>(adapter);
|
||||
if (a == null) return Results.NotFound();
|
||||
if (req.Action == "stop") await a.PtzStopAsync(deviceId);
|
||||
else await a.PtzControlAsync(deviceId, req.Direction, req.Speed);
|
||||
return Results.Ok();
|
||||
});
|
||||
|
||||
app.MapGet("/api/gateway/alarms/{adapter}", async (string adapter, int page, int size, DateTime from, DateTime to, AdapterRegistry reg) => {
|
||||
var a = reg.FindByCode<IHasAlarms>(adapter);
|
||||
if (a == null) return Results.NotFound();
|
||||
return Results.Ok(await a.GetAlarmsAsync(page, size, from, to));
|
||||
});
|
||||
|
||||
app.MapPost("/api/gateway/alarms/{adapter}/{alarmId}/confirm", async (string adapter, string alarmId, AdapterRegistry reg) => {
|
||||
var a = reg.FindByCode<IHasAlarms>(adapter);
|
||||
if (a == null) return Results.NotFound();
|
||||
await a.ConfirmAlarmAsync(alarmId);
|
||||
return Results.Ok();
|
||||
});
|
||||
|
||||
// ... 更多 B 组接口
|
||||
```
|
||||
|
||||
### 7.3 路由设计原则
|
||||
|
||||
- **适配器参数前置**:所有 B 组接口第一个路径参数都是 `{adapter}`,通过注册中心查找
|
||||
- **能力检查懒加载**:请求到达时才检查适配器是否实现对应接口
|
||||
- **404 语义**:适配器不存在或未实现对应能力 → 404(而非 500)
|
||||
- **统一错误格式**:`{ "error": "ADAPTER_NOT_FOUND", "message": "Adapter 'xxx' not found" }`
|
||||
|
||||
---
|
||||
|
||||
## 8. 同步流程设计
|
||||
|
||||
### 8.1 网关启动同步
|
||||
|
||||
```
|
||||
1. 网关启动 → 加载配置 (NodeCode, Token, VolProBaseUrl)
|
||||
2. 初始化适配器 (并行: Owl + MC4)
|
||||
3. POST /api/gateway/register → 获取 NodeId + 已有设备列表
|
||||
4. 按 AdapterCode 分流已有设备 → 适配器对比差异
|
||||
5. 各适配器发现子设备 → 构建 StandardDevice[] 列表
|
||||
6. POST /api/gateway/sync/devices → Vol.Pro Upsert 设备
|
||||
7. 开启 15s 心跳定时器
|
||||
```
|
||||
|
||||
### 8.2 手动全量同步(B3)
|
||||
|
||||
```
|
||||
管理端 → POST /api/gateway/devices/sync?adapter=MC4:31ku
|
||||
网关:
|
||||
1. 找到 Mc4Adapter
|
||||
2. (MC4) GetObjectTree() → 解析区域+设备 → StandardDevice[]
|
||||
3. (Owl) GetDevices() + GetChannels() → StandardDevice[]
|
||||
4. POST /api/gateway/sync/devices → Vol.Pro
|
||||
5. 返回 { added, updated, removed }
|
||||
```
|
||||
|
||||
### 8.3 MC4.0 同步(FullReplace 模式)
|
||||
|
||||
```
|
||||
MC4.0 对象树遍历:
|
||||
type=1 (区域) → 名称匹配 warehouse_regions → 新建或绑区
|
||||
type=2 (设备) → Upsert base_device
|
||||
- 首次写入: DeviceName/Category/Group/ExtraData 全量
|
||||
- 后续同步: 仅更新 IsOnline/ExtraData/ParentDeviceId
|
||||
type=2 子节点 → parentSourceId 解析 → ParentDeviceId
|
||||
```
|
||||
|
||||
### 8.4 Owl 同步(Merge 模式)
|
||||
|
||||
```
|
||||
Owl 设备列表遍历:
|
||||
GET /devices → NVR 设备 (IsParent=是, DeviceGroup=视频设备)
|
||||
GET /channels → 通道 (ParentDeviceId=NVR, IsParent=否)
|
||||
通道额外写 video_channel 扩展记录 (OwlStreamApp/OwlStreamName)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 错误处理
|
||||
|
||||
### 9.1 错误码规范
|
||||
|
||||
| HTTP 状态码 | error_code | 场景 |
|
||||
|:---:|------|------|
|
||||
| 200 | - | 正常 |
|
||||
| 400 | `INVALID_PARAMETER` | 参数缺失或格式错误 |
|
||||
| 401 | `UNAUTHORIZED` | Token 验证失败 |
|
||||
| 404 | `ADAPTER_NOT_FOUND` | 适配器不存在 |
|
||||
| 404 | `CAPABILITY_NOT_SUPPORTED` | 适配器未实现该能力 |
|
||||
| 502 | `UPSTREAM_ERROR` | 第三方 API 返回错误 |
|
||||
| 503 | `ADAPTER_OFFLINE` | 适配器健康检查失败 |
|
||||
| 504 | `UPSTREAM_TIMEOUT` | 第三方 API 超时 |
|
||||
| 500 | `INTERNAL_ERROR` | 网关内部错误 |
|
||||
|
||||
### 9.2 适配器日志
|
||||
|
||||
```csharp
|
||||
public void Log(string adapterCode, string operation, string detail, Exception? ex = null)
|
||||
{
|
||||
var level = ex != null ? "ERROR" : "INFO";
|
||||
var msg = $"[{DateTime.UtcNow:O}] [{level}] [{adapterCode}] {operation}: {detail}";
|
||||
if (ex != null) msg += $"\n{ex}";
|
||||
Console.WriteLine(msg);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 配置管理
|
||||
|
||||
### 10.1 appsettings.json 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": { "Default": "Information" }
|
||||
},
|
||||
"Owl": {
|
||||
"BaseUrl": "http://localhost:15123",
|
||||
"Username": "admin",
|
||||
"Password": "your_password"
|
||||
},
|
||||
"MC4": {
|
||||
"BaseUrl": "http://localhost:3000"
|
||||
},
|
||||
"Gateway": {
|
||||
"VolProBaseUrl": "http://localhost:9100",
|
||||
"NodeCode": "gw-31ku",
|
||||
"NodeToken": "xxxxxxxxxx",
|
||||
"HeartbeatIntervalSec": 15,
|
||||
"AdapterInitTimeoutSec": 30
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 10.2 环境变量覆盖(Docker 部署)
|
||||
|
||||
```bash
|
||||
GATEWAY__OWL__BASEURL=http://192.168.1.100:15123
|
||||
GATEWAY__OWL__PASSWORD=prod_password
|
||||
GATEWAY__GATEWAY__NODETOKEN=prod_token
|
||||
```
|
||||
|
||||
### 10.3 配置验证
|
||||
|
||||
启动时验证必填项:
|
||||
```
|
||||
Gateway.VolProBaseUrl ✓
|
||||
Gateway.NodeCode ✓ (长度 1-50)
|
||||
Gateway.NodeToken ✓ (长度 8-100)
|
||||
Owl.BaseUrl ✓ (格式 http(s)://...)
|
||||
Owl.Username ✓
|
||||
Owl.Password ✓
|
||||
MC4.BaseUrl ✓
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 部署方案
|
||||
|
||||
### 11.1 单机部署
|
||||
|
||||
```bash
|
||||
cd gateway
|
||||
dotnet publish src/IntegrationGateway.Host -c Release -o publish
|
||||
cd publish
|
||||
./IntegrationGateway.Host --urls http://0.0.0.0:5100
|
||||
```
|
||||
|
||||
### 11.2 Docker 部署
|
||||
|
||||
```dockerfile
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
||||
WORKDIR /app
|
||||
COPY publish/ .
|
||||
ENV ASPNETCORE_URLS=http://+:5100
|
||||
ENTRYPOINT ["dotnet", "IntegrationGateway.Host.dll"]
|
||||
```
|
||||
|
||||
### 11.3 双实例部署
|
||||
|
||||
```
|
||||
实例A: gw-31ku :5100 → MC4.0(31号库) + Owl(仓库视频)
|
||||
实例B: gw-11ku :5101 → MC4.0(11号库) + 海康ISC(门禁)
|
||||
```
|
||||
|
||||
### 11.4 运维命令
|
||||
|
||||
```bash
|
||||
# 健康检查
|
||||
curl http://localhost:5100/api/gateway/health
|
||||
|
||||
# 手动同步
|
||||
curl -X POST "http://localhost:5100/api/gateway/devices/sync?adapter=MC4:31ku"
|
||||
|
||||
# 查看日志
|
||||
docker logs -f integration-gateway
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. 性能指标
|
||||
|
||||
| 指标 | 目标值 | 说明 |
|
||||
|------|:---:|------|
|
||||
| 网关启动时间 | < 5s | 含适配器并行初始化 |
|
||||
| 设备同步吞吐 | 100 设备/s | 含 HTTP 往返 |
|
||||
| 实时取流响应 | < 500ms | 从请求到返回流地址 |
|
||||
| 内存占用 | < 100MB | 空载状态 |
|
||||
| 并发连接数 | 50 | 同时处理的管理端请求 |
|
||||
|
||||
---
|
||||
|
||||
## 13. 测试策略
|
||||
|
||||
### 13.1 单元测试(Phase 4)
|
||||
|
||||
```
|
||||
IntegrationGateway.Core.Tests/
|
||||
├── AdapterRegistryTests # 注册/查找/初始化
|
||||
├── RateLimiterTests # 令牌桶行为
|
||||
└── ModelSerializationTests # JSON 序列化往返
|
||||
```
|
||||
|
||||
### 13.2 集成测试(需子系统 Mock)
|
||||
|
||||
```
|
||||
适配器层 Mock → 验证 Controller 路由分发
|
||||
Vol.Pro API Mock → 验证网关注册/心跳/同步流程
|
||||
```
|
||||
|
||||
### 13.3 边界测试
|
||||
|
||||
| 场景 | 预期行为 |
|
||||
|------|----------|
|
||||
| Owl 离线 | HealthCheck → false, 设备 IsOnline 不变 |
|
||||
| MC4.0 超时 | 返回 504, 不影响 Owl 适配器 |
|
||||
| 并发取流 | RateLimiter 排队, 不丢请求 |
|
||||
| 配置错误 | 启动时校验失败, 拒绝启动 |
|
||||
|
||||
---
|
||||
|
||||
## 14. 版本历史
|
||||
|
||||
| 版本 | 日期 | 变更 |
|
||||
|------|------|------|
|
||||
| 1.0 | 2025-05-17 | 初版详细设计 |
|
||||
|
||||
---
|
||||
|
||||
> **下一步**: Phase 0 Day 1 按本设计实施网关项目骨架。
|
||||
203
doc/设计文档/对接网关设计文档_任务清单.md
Normal file
203
doc/设计文档/对接网关设计文档_任务清单.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# IntegrationGateway 对接网关详细设计 — 任务清单
|
||||
|
||||
> **基准文档**: 对接网关设计文档 v1.0
|
||||
> **分支**: phase/0-infrastructure
|
||||
> **原则**: 每阶段产出可编译、可提交的独立成果
|
||||
|
||||
---
|
||||
|
||||
## Phase G0: 项目骨架与核心抽象(预计 1 天)
|
||||
|
||||
### G0.1 解决方案与项目结构
|
||||
- [ ] 创建 `gateway/IntegrationGateway.sln`(dotnet new sln)
|
||||
- [ ] 创建 `src/IntegrationGateway.Core/` 类库项目(net8.0)
|
||||
- [ ] 创建 `src/IntegrationGateway.Host/` Web 项目(net8.0, ASP.NET Core Minimal API)
|
||||
- [ ] Host 引用 Core 项目
|
||||
- [ ] 确认 `dotnet build` 0 错误
|
||||
|
||||
### G0.2 能力接口体系(7 个接口)
|
||||
- [ ] 创建 `Core/Abstractions/IGatewayAdapter.cs` — 基础接口(AdapterCode/DisplayName/Capabilities/InitializeAsync/HealthCheckAsync)
|
||||
- [ ] 创建 `Core/Abstractions/IHasOwnDeviceTree.cs` — 对象树接口
|
||||
- [ ] 创建 `Core/Abstractions/IHasFlatDevices.cs` — 扁平设备列表接口
|
||||
- [ ] 创建 `Core/Abstractions/IHasPoints.cs` — 实时点位值 + 控制接口
|
||||
- [ ] 创建 `Core/Abstractions/IHasStreams.cs` — 视频流 + PTZ + 截图接口
|
||||
- [ ] 创建 `Core/Abstractions/IHasAlarms.cs` — 告警查询 + 确认 + 结束接口
|
||||
- [ ] 创建 `Core/Abstractions/IHasRecordings.cs` — 录像回放接口
|
||||
- [ ] 创建 `Core/Abstractions/IAcceptsMetadataPush.cs` — 元数据回写接口
|
||||
- [ ] 确认 `dotnet build` 0 错误
|
||||
|
||||
### G0.3 统一模型(10 个类)
|
||||
- [ ] 创建 `Core/Models/StandardDevice.cs`
|
||||
- [ ] 创建 `Core/Models/StandardAlarm.cs`
|
||||
- [ ] 创建 `Core/Models/StandardRecording.cs`
|
||||
- [ ] 创建 `Core/Models/DeviceTreeNode.cs`
|
||||
- [ ] 创建 `Core/Models/PointValue.cs`
|
||||
- [ ] 创建 `Core/Models/StreamUrls.cs`
|
||||
- [ ] 创建 `Core/Models/PagedResult.cs`(泛型分页容器)
|
||||
- [ ] 创建 `Core/Models/AdapterCapabilities.cs`
|
||||
- [ ] 创建 `Core/Models/MetadataChangeSet.cs`
|
||||
- [ ] 创建 `Core/Models/MetadataPushResult.cs`
|
||||
- [ ] 确认 `dotnet build` 0 错误
|
||||
|
||||
### G0.4 基础设施(3 个类)
|
||||
- [ ] 创建 `Core/Infrastructure/AdapterRegistry.cs` — 注册/查找/并行初始化
|
||||
- [ ] 创建 `Core/Infrastructure/RateLimiter.cs` — 令牌桶限流器
|
||||
- [ ] 创建 `Core/Infrastructure/GatewayClientFactory.cs` — Vol.Pro HTTP 客户端工厂
|
||||
- [ ] 确认 `dotnet build` 0 错误
|
||||
|
||||
> **G0 提交点**: `PhaseG0_gateway_core — Core 项目编译通过,7 接口 + 10 模型 + 3 基础设施`
|
||||
|
||||
---
|
||||
|
||||
## Phase G1: Host 宿主与路由注册(预计 0.5 天)
|
||||
|
||||
### G1.1 配置文件
|
||||
- [ ] 创建 `appsettings.json` — Owl/MC4/Gateway 三段配置
|
||||
- [ ] 配置验证:启动时检查必填项(VolProBaseUrl/NodeCode/NodeToken/Owl/MC4)
|
||||
|
||||
### G1.2 启动与注册
|
||||
- [ ] 实现 `Program.cs` — IHttpClientFactory 注册("VolPro" 命名客户端)
|
||||
- [ ] 实现适配器初始化(从配置读取生成 OwlAdapter/MC4Adapter 实例 → 注册到 AdapterRegistry)
|
||||
- [ ] 实现并行初始化(`Task.WhenAll`,单个失败不影响其他)
|
||||
|
||||
### G1.3 B 组路由(14 个端点)
|
||||
- [ ] `GET /api/gateway/health` — 返回所有适配器健康状态
|
||||
- [ ] `GET /api/gateway/devices?adapter=&page=&size=&keyword=` — 扁平设备列表
|
||||
- [ ] `GET /api/gateway/tree?adapter=` — 对象树
|
||||
- [ ] `GET /api/gateway/streams/{adapter}/{deviceId}/live` — 实时流地址
|
||||
- [ ] `GET /api/gateway/streams/{adapter}/{deviceId}/playback?start=&end=` — 回放地址
|
||||
- [ ] `POST /api/gateway/streams/{adapter}/{deviceId}/snapshot` — 截图
|
||||
- [ ] `POST /api/gateway/streams/{adapter}/{deviceId}/ptz` — 云台控制
|
||||
- [ ] `GET /api/gateway/realtime/{adapter}/{deviceId}` — 实时点位值
|
||||
- [ ] `POST /api/gateway/realtime/{adapter}/control` — 设备控制写值
|
||||
- [ ] `GET /api/gateway/alarms/{adapter}?from=&to=&page=&size=` — 告警查询
|
||||
- [ ] `POST /api/gateway/alarms/{adapter}/{alarmId}/confirm` — 告警确认
|
||||
- [ ] `POST /api/gateway/alarms/{adapter}/{alarmId}/end` — 告警结束
|
||||
- [ ] `GET /api/gateway/recordings/{adapter}/{deviceId}` — 录像列表
|
||||
- [ ] `POST /api/gateway/devices/sync?adapter=` — 手动触发同步
|
||||
- [ ] 确认 `dotnet build` 0 错误
|
||||
|
||||
### G1.4 错误处理中间件
|
||||
- [ ] 统一错误响应格式:`{ error, message }`
|
||||
- [ ] 适配器未找到 → 404 `ADAPTER_NOT_FOUND`
|
||||
- [ ] 能力不支持 → 404 `CAPABILITY_NOT_SUPPORTED`
|
||||
- [ ] 第三方超时 → 504 `UPSTREAM_TIMEOUT`
|
||||
|
||||
> **G1 提交点**: `PhaseG1_gateway_host — Host 编译通过,14 个路由骨架就绪,健康检查可响应`
|
||||
|
||||
---
|
||||
|
||||
## Phase G2: OwlAdapter(预计 1 天)
|
||||
|
||||
### G2.1 项目创建
|
||||
- [ ] 创建 `src/IntegrationGateway.Adapters.Owl/` 类库项目(net8.0)
|
||||
- [ ] 引用 Core 项目
|
||||
- [ ] Host 引用 Adapters.Owl
|
||||
- [ ] 加入解决方案
|
||||
|
||||
### G2.2 OwlAuthHelper
|
||||
- [ ] 实现 RSA 公钥获取 → 加密登录 → JWT Token 缓存
|
||||
- [ ] Token 过期前自动刷新(懒刷新)
|
||||
- [ ] 提供 `GetAuthenticatedClientAsync()` 方法
|
||||
|
||||
### G2.3 OwlAdapter 核心实现
|
||||
- [ ] 实现 `IGatewayAdapter` — AdapterCode="Owl:main"
|
||||
- [ ] 实现 `IHasFlatDevices` — `GetDevicesAsync` 分页 + `GetAllDevicesAsync` 全量
|
||||
- [ ] 实现通道管理 — `GetChannelsAsync` + `GetAllChannelsAsync`
|
||||
- [ ] 设备映射 — `OwlDevice → StandardDevice`, `OwlChannel → StandardDevice`
|
||||
|
||||
### G2.4 OwlAdapter 视频流实现
|
||||
- [ ] 实现 `IHasStreams.GetLiveUrlAsync` — POST /channels/:id/play → StreamUrls
|
||||
- [ ] 实现 `IHasStreams.GetPlaybackUrlAsync` — 构建 HLS VOD URL
|
||||
- [ ] 实现 `IHasStreams.PtzControlAsync` — continuous + stop(仅方向键)
|
||||
- [ ] 实现 `IHasStreams.GetSnapshotAsync` — POST /channels/:id/snapshot
|
||||
|
||||
### G2.5 OwlAdapter 录像与 Metadata
|
||||
- [ ] 实现 `IHasRecordings.GetRecordingsAsync` — GET /recordings
|
||||
- [ ] 实现 `IAcceptsMetadataPush.PushMetadataAsync` — PUT /devices/:id
|
||||
|
||||
### G2.6 HealthCheck
|
||||
- [ ] 实现 `HealthCheckAsync` — 调 /health 端点
|
||||
- [ ] Owl 认证失败 → HealthCheck = false
|
||||
|
||||
### G2.7 构建验证
|
||||
- [ ] 确认 `dotnet build` 0 错误
|
||||
- [ ] 确认 `POST /register` 返回 OwlAdapter 在适配器列表中
|
||||
|
||||
> **G2 提交点**: `PhaseG2_owl_adapter — OwlAdapter 编译通过,3 个接口实现完整(FlatDevices+Streams+Recordings+MetadataPush)`
|
||||
|
||||
---
|
||||
|
||||
## Phase G3: MC4Adapter(预计 1 天)
|
||||
|
||||
### G3.1 项目创建
|
||||
- [ ] 创建 `src/IntegrationGateway.Adapters.MC4/` 类库项目(net8.0)
|
||||
- [ ] 引用 Core 项目
|
||||
- [ ] Host 引用 Adapters.MC4
|
||||
- [ ] 加入解决方案
|
||||
|
||||
### G3.2 Mc4AuthHelper
|
||||
- [ ] 实现 POST /api/central/auth/conf/get → Token 缓存(8 小时)
|
||||
- [ ] 提供 `GetAuthenticatedClientAsync()` 方法(header["token"])
|
||||
|
||||
### G3.3 MC4Adapter 对象树
|
||||
- [ ] 实现 `IGatewayAdapter` — AdapterCode="MC4:31ku"
|
||||
- [ ] 实现 `IHasOwnDeviceTree.GetObjectTreeAsync` — POST /api/central/object/tree → DeviceTreeNode[]
|
||||
- [ ] MC4 节点映射:`Mc4TreeNode → DeviceTreeNode`(Type=1区域/2设备)
|
||||
|
||||
### G3.4 MC4Adapter 实时数据
|
||||
- [ ] 实现 `IHasPoints.GetRealtimeValuesAsync` — POST /api/central/device/point/value/get → PointValue[]
|
||||
- [ ] 实现 `IHasPoints.GetMultiPointValuesAsync` — POST /api/central/point/multi/value/get
|
||||
- [ ] 实现 `IHasPoints.SetPointValueAsync` — POST /api/central/point/value/set
|
||||
|
||||
### G3.5 MC4Adapter 告警
|
||||
- [ ] 实现 `IHasAlarms.GetAlarmsAsync` — POST /api/central/alarm/query(skip/limit 转 page/size)
|
||||
- [ ] 实现 `IHasAlarms.ConfirmAlarmAsync` — POST /api/central/alarm/confirm
|
||||
- [ ] 实现 `IHasAlarms.EndAlarmAsync` — POST /api/central/alarm/end
|
||||
- [ ] 告警去重:`SourceAlarmId` 唯一
|
||||
|
||||
### G3.6 限流与验证
|
||||
- [ ] RateLimiter = new RateLimiter(2)(MC4.0 QPS 限制)
|
||||
- [ ] 确认 `dotnet build` 0 错误
|
||||
|
||||
> **G3 提交点**: `PhaseG3_mc4_adapter — MC4Adapter 编译通过,3 个接口实现完整(OwnDeviceTree+Points+Alarms)`
|
||||
|
||||
---
|
||||
|
||||
## Phase G4: 联调验证(需子系统就绪,预计 1 天)
|
||||
|
||||
### G4.1 Owl 联调
|
||||
- [ ] 启动 Owl+ZLM → 网关 RSA 登录成功
|
||||
- [ ] `GET /api/gateway/devices?adapter=Owl:main` → 返回设备列表
|
||||
- [ ] `GET /api/gateway/streams/Owl:main/{channelId}/live` → 返回 WS-FLV 地址
|
||||
- [ ] `POST /api/gateway/streams/Owl:main/{channelId}/ptz` → 云台响应
|
||||
|
||||
### G4.2 MC4.0 联调
|
||||
- [ ] `GET /api/gateway/tree?adapter=MC4:31ku` → 返回对象树 JSON
|
||||
- [ ] `GET /api/gateway/realtime/MC4:31ku/{deviceId}` → 返回点位值
|
||||
- [ ] `GET /api/gateway/alarms/MC4:31ku` → 返回告警列表
|
||||
|
||||
### G4.3 故障隔离验证
|
||||
- [ ] 停止 MC4.0 → `GET /health` 中 Owl=OK, MC4=FAIL
|
||||
- [ ] Owl 设备查询仍正常返回
|
||||
|
||||
> **G4 提交点**: `PhaseG4_integration — 联调通过,健康检查报告真实状态`
|
||||
|
||||
---
|
||||
|
||||
## Phase G5: 部署与文档(预计 0.5 天)
|
||||
|
||||
### G5.1 发布
|
||||
- [ ] `dotnet publish` Release 模式
|
||||
- [ ] Dockerfile 编写与构建
|
||||
|
||||
### G5.2 运维文档
|
||||
- [ ] 启动脚本(单机/Docker)
|
||||
- [ ] 配置说明(环境变量覆盖)
|
||||
- [ ] 健康检查命令
|
||||
|
||||
> **G5 提交点**: `PhaseG5_deploy — 可发布的网关二进制 + 运维文档`
|
||||
|
||||
---
|
||||
|
||||
> **总周期**: G0-G5 预计 4 个工作日(不含联调等待时间)
|
||||
Reference in New Issue
Block a user