从phase分支恢复设计文档

This commit is contained in:
2026-05-17 04:39:29 +08:00
parent 36495b7cbb
commit a113d86cea
5 changed files with 1831 additions and 158 deletions

View File

@@ -0,0 +1,512 @@
# Vol.Pro 框架前后端改造方案
> **版本**: 1.0
> **日期**: 2025-05-17
> **基准**: SecMPS 整合方案 v3.1 + Vol.Pro 框架官方文档
> **核心原则**: 所有改动必须在 Partial/extension 目录中,严禁修改框架生成代码
---
## 1. 改造总览
### 1.1 改造清单
| 层面 | 改造项 | 位置 | 是否破坏可升级性 |
|------|--------|------|:---:|
| 数据库 | 5 张新表 + 字典数据 | SQL 脚本 | ❌ |
| 后端-Entity | 6 个实体 Partial 类 | `DomainModels/*/partial/` | ❌ |
| 后端-Service | 6 个 Service Partial 类 | `Services/*/Partial/` | ❌ |
| 后端-Controller | 3 个 Controller Partial 类 | `Controllers/*/Partial/` | ❌ |
| 后端-Job | 3 个 Quartz 定时任务 | `Warehouse/Services/` | ❌ |
| 后端-Config | 注册网关 HttpClient | `Startup.cs` | ⚠️ 需手动合并 |
| 前端-主表 | 自定义操作列插槽 | `extension/warehouse/` | ❌ |
| 前端-组件 | 5 个设备操作组件 | `views/warehouse/base_device/components/` | ❌ |
| 网关 | GatewayClient HTTP 客户端 | `Warehouse/Services/` | ❌ |
### 1.2 框架扩展点总览
```
Vol.Pro 框架约定:
自动生成代码 (代码生成器覆盖) 自定义代码 (不被覆盖)
───────────────────────────── ──────────────────────────
Controllers/xxxController.cs Controllers/xxx/Partial/xxxController.cs
Services/xxxService.cs Services/xxx/Partial/xxxService.cs
IServices/IxxxService.cs IServices/xxx/Partial/IxxxService.cs
Repositories/xxxRepository.cs (无需自定义)
IRepositories/IxxxRepository.cs (无需自定义)
DomainModels/xxx/xxx.cs DomainModels/xxx/partial/xxx.cs
前端 views/xxx/xxx.vue extension/xxx.jsx + views/xxx/components/
```
---
## 2. 数据库改造
### 2.1 新增表
执行 `doc/db_init.sql`,创建 5 张表:
| 表名 | 说明 | 层级 |
|------|------|------|
| `gateway_nodes` | 网关节点 | 顶层 |
| `base_device` | 统一设备主表 | 核心AdapterCode+SourceId 联合唯一 |
| `video_channel` | 视频通道扩展 | base_device 子表 |
| `video_record` | 录像文件 | video_channel 子表 |
| `iot_alarm` | 告警记录 | base_device 子表 |
| `iot_devicedata` | 数据归档 | base_device 子表 |
### 2.2 字典初始化
Phase 0 需在 Vol.Pro 管理端 → 字典管理 中创建 8 组数据字典:
| 字典名称 | 字典编号 | 字典值 |
|----------|:---:|------|
| 设备种类 | — | 摄像机/硬盘录像机/温湿度变送器/空调控制器/... (18 项) |
| 设备分组 | device_group | 视频设备/IoT设备/门禁设备/道闸设备/报警设备 |
| 是否父设备 | — | 是/否 |
| 在线状态 | — | 在线/离线 |
| 启用状态 | — | 启用/禁用 |
| 是否控制点 | — | 只读/可写 |
| 告警等级 | — | 提示/普通/重要/紧急 |
| 告警状态 | — | 未确认/已确认/已结束 |
### 2.3 区-点位-设备 三级关联
框架支持主从表三级显示。利用现有表关系:
```
warehouse_regions (区域) ← 代码生成器已有
└── warehouse_devicepoint (点位) ← 代码生成器已有
└── base_device (设备) ← 新建,用 PointId 关联点位
├── video_channel ← 新建子表
├── iot_devicedata ← 新建子表
└── iot_alarm ← 新建子表
```
**关键**:代码生成器配置 base_device 的 `DetailTable` 属性,关联 video_channel/iot_devicedata/iot_alarm 作为子表,框架自动渲染主从表 Tab。
---
## 3. 后端改造
### 3.1 Entity 扩展Partial 类)
**原则**:框架生成的 Entity 在 `DomainModels/device_manager/base_device.cs`,不可修改。仅在 `partial/` 中添加。
```
VolPro.Entity/DomainModels/device_manager/partial/
├── base_device.cs # 添加导航属性 + AdapterCode/SourceId 唯一约束注解
├── gateway_nodes.cs # 网关特有业务属性
├── video_channel.cs # 通道流信息缓存
├── video_record.cs # (留空,框架生成即够用)
├── iot_devicedata.cs # (留空)
└── iot_alarm.cs # 告警确认/结束方法
```
**base_device Partial 示例**
```csharp
namespace VolPro.Entity.DomainModels
{
public partial class base_device
{
// 导航属性(用于主从表)
[Navigate(NavigateType.OneToOne, nameof(DeviceId), nameof(video_channel.DeviceId))]
public video_channel? VideoChannel { get; set; }
[Navigate(NavigateType.OneToMany, nameof(DeviceId), nameof(iot_alarm.DeviceId))]
public List<iot_alarm>? Alarms { get; set; }
[Navigate(NavigateType.OneToMany, nameof(DeviceId), nameof(iot_devicedata.DeviceId))]
public List<iot_devicedata>? DeviceData { get; set; }
// 网关字段标识(供同步时判断哪些字段可覆盖)
public static readonly HashSet<string> GatewayFields = new()
{
nameof(IsOnline), nameof(IsParent), nameof(ParentDeviceId),
nameof(ExtraData), nameof(IpAddress), nameof(Port), nameof(LastSyncTime)
};
}
}
```
### 3.2 Service 扩展Partial 类)
```
Warehouse/Services/device_manager/Partial/
├── base_deviceService.cs # GetRegionTree / GetDevicesByPoint / 字典查询辅助
├── gateway_nodesService.cs # 网关注册/心跳/同步入口
├── video_channelService.cs # (留空)
├── video_recordService.cs # (留空)
├── iot_devicedataService.cs # (留空)
└── iot_alarmService.cs # (留空)
```
**gateway_nodesService Partial**(核心同步入口):
```csharp
namespace Warehouse.Services
{
public partial class gateway_nodesService
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly Igateway_nodesRepository _repository;
[ActivatorUtilitiesConstructor]
public gateway_nodesService(
Igateway_nodesRepository dbRepository,
IHttpContextAccessor httpContextAccessor
) : base(dbRepository)
{
_httpContextAccessor = httpContextAccessor;
_repository = dbRepository;
}
// 网关注册 (Upsert) — 被 Controller A1 调用
public async Task<gateway_nodes> RegisterNodeAsync(string nodeCode, string token, string adapterTypes, string baseUrl)
{
// 实现: Upsert 逻辑
}
// 心跳更新 — 被 Controller A2 调用
public async Task UpdateHeartbeatAsync(string nodeCode, string token)
{
// 实现: 更新 LastHeartbeat + IsOnline
}
// 设备同步 — 被 Controller A3 调用
public async Task<(int added, int updated)> SyncDevicesAsync(int gatewayNodeId, List<StandardDevice> devices)
{
// 实现: 字段分治 + parentSourceId 映射
}
}
}
```
### 3.3 Controller 扩展Partial 类)
```
VolPro.WebApi/Controllers/Warehouse/Partial/
├── base_deviceController.cs # GetRegionTree / GetDevicesByPoint / 操作代理
├── gateway_nodesController.cs # A1-A4 网关 API
├── video_channelController.cs # (留空)
├── video_recordController.cs # (留空)
├── iot_devicedataController.cs # (留空)
└── iot_alarmController.cs # (留空)
```
#### 3.3.1 gateway_nodesControllerA 组 API
**核心改造**:在框架生成的 `gateway_nodesController` 基础上Partial 中添加 4 个不受权限控制的网关回调接口。
```csharp
namespace Warehouse.Controllers
{
public partial class gateway_nodesController
{
private readonly IHttpContextAccessor _httpContextAccessor;
[ActivatorUtilitiesConstructor]
public gateway_nodesController(
Igateway_nodesService service,
IHttpContextAccessor httpContextAccessor
) : base(service)
{
_httpContextAccessor = httpContextAccessor;
}
/// <summary>A1: 网关注册 (Upsert)</summary>
[HttpPost, Route("/api/gateway/register"), AllowAnonymous]
public async Task<IActionResult> RegisterGateway([FromBody] GatewayRegisterRequest req)
{
// 实现
}
/// <summary>A2: 心跳</summary>
[HttpPost, Route("/api/gateway/heartbeat"), AllowAnonymous]
public async Task<IActionResult> GatewayHeartbeat([FromBody] GatewayHeartbeatRequest req)
{
// 实现
}
/// <summary>A3: 设备数据同步 (字段分治)</summary>
[HttpPost, Route("/api/gateway/sync/devices"), AllowAnonymous]
public async Task<IActionResult> SyncDevices([FromBody] SyncDevicesRequest req)
{
// 实现
}
/// <summary>A4: 告警同步</summary>
[HttpPost, Route("/api/gateway/sync/alarms"), AllowAnonymous]
public async Task<IActionResult> SyncAlarms([FromBody] SyncAlarmsRequest req)
{
// 实现
}
}
}
```
**权限模型**A 组接口加 `[AllowAnonymous]`,内部通过 `NodeToken` 二次认证。B 组接口走框架 JWT 权限。
#### 3.3.2 base_deviceControllerB 组代理 + 设备树)
```csharp
namespace Warehouse.Controllers
{
public partial class base_deviceController
{
/// <summary>区域→点位→设备树(管理端左侧树形控件)</summary>
[HttpGet, Route("/api/DeviceManager/GetRegionTree")]
public async Task<IActionResult> GetRegionTree()
{
// SELECT warehouse_regions JOIN warehouse_devicepoint → 树形结构
// 每个点位下统计 base_device 数量
}
/// <summary>点位下设备列表(含子设备)</summary>
[HttpGet, Route("/api/DeviceManager/GetDevicesByPoint")]
public async Task<IActionResult> GetDevicesByPoint(int pointId, int page = 1, int size = 20)
{
// SELECT base_device WHERE PointId = pointId
// Include 子设备ParentDeviceId
}
}
}
```
### 3.4 网关 HTTP 客户端GatewayClient
```
Warehouse/Services/GatewayClient.cs
```
```csharp
public class GatewayClient
{
private readonly IHttpClientFactory _httpFactory;
private readonly IConfiguration _config;
public GatewayClient(IHttpClientFactory httpFactory, IConfiguration config)
{
_httpFactory = httpFactory;
_config = config;
}
/// <summary>调网关 B3: 手动触发全量同步</summary>
public async Task TriggerFullSyncAsync(string baseUrl, string adapterTypes)
{
var http = _httpFactory.CreateClient();
// POST {baseUrl}/api/gateway/devices/sync?adapter={adapterTypes}
}
/// <summary>调网关 B4: 获取实时点位值</summary>
public async Task<List<PointValue>> GetRealtimeAsync(string baseUrl, string adapter, string deviceId)
{
// GET {baseUrl}/api/gateway/realtime/{adapter}/{deviceId}
}
/// <summary>调网关 B5: 设备控制</summary>
public async Task ControlDeviceAsync(string baseUrl, string adapter, string deviceId, int pointIndex, double value)
{
// POST {baseUrl}/api/gateway/realtime/{adapter}/control
}
}
```
### 3.5 Quartz 定时任务
| Job | 触发器 | 职责 |
|-----|:---:|------|
| `SyncDevicesJob` | 每 5 分钟 | 遍历所有在线网关 → 调 GatewayClient.TriggerFullSyncAsync() |
| `RealtimePollJob` | 每 10 秒 | 轮询 MC4.0 IoT 设备实时值 → 更新 iot_devicedata |
| `HeartbeatMonitorJob` | 每 15 秒 | 扫描 gateway_nodes 心跳超时 → 标记离线 + 级联设备离线 |
**注册方式**:在 Vol.Pro 管理端 → Quartz 任务管理 → 新建任务,指定 Job 类全名。
### 3.6 Startup.cs 注册
```csharp
// 在 Startup.cs 或 Program.cs 中注册
builder.Services.AddHttpClient("VolPro", c =>
{
c.Timeout = TimeSpan.FromSeconds(30);
});
builder.Services.AddSingleton<GatewayClient>();
```
---
## 4. 前端改造
### 4.1 架构决策
| 方案 | 描述 | 是否破坏可升级性 |
|------|------|:---:|
| ❌ 修改生成的 .vue | 直接改 views/warehouse/base_device/ 下框架生成文件 | ✅ 破坏 |
| ✅ extension + components | 通过扩展注入 + 自定义组件实现操作列 | ❌ 不破坏 |
### 4.2 自定义操作列插槽
框架生成的 base_device 列表页面默认操作列只有"编辑/删除"。通过 **前端扩展文件** 自定义替换:
```
web.vite/src/extension/warehouse/base_device.jsx
```
```javascript
// 自定义操作列渲染
import VideoDeviceActions from '@/views/warehouse/base_device/components/VideoDeviceActions.vue'
import IoTDeviceActions from '@/views/warehouse/base_device/components/IoTDeviceActions.vue'
// 注册自定义组件
export default {
components: { VideoDeviceActions, IoTDeviceActions },
// 替换框架默认操作列
slots: {
// 操作列插槽
'col-action': (h, { row }) => {
const comp = row.deviceGroup === '视频设备' ? 'VideoDeviceActions'
: row.deviceGroup === 'IoT设备' ? 'IoTDeviceActions'
: null
if (comp) return h(comp, { props: { row } })
// fallback 到框架默认按钮
return null
}
}
}
```
### 4.3 组件目录
```
web.vite/src/views/warehouse/base_device/components/
├── VideoDeviceActions.vue # 实时预览/云台/回放/快照/同步通道
├── IoTDeviceActions.vue # 实时数据/控制/刷新/告警
├── AccessDeviceActions.vue # 远程开门 (Phase 3)
├── BarrierDeviceActions.vue # 抬杆/落杆 (Phase 3)
├── AlarmDeviceActions.vue # 告警/布防撤防 (Phase 3)
├── DeviceLivePreview.vue # Jessibuca 播放器弹窗
├── PtzControlPanel.vue # ↑↓←→+ZOOM+停止
├── RealtimeDataPanel.vue # 实时点位值表格弹窗
├── DeviceControlPanel.vue # 控制写值面板
├── DeviceEditDialog.vue # 设备编辑弹窗 (扩展字段)
└── MapBindingPanel.vue # VgoMap 模型绑定
```
### 4.4 组件设计要点
**VideoDeviceActions.vue**(核心组件):
```
Props: row (base_device)
按钮组:
[实时预览] → 打开 DeviceLivePreview.vue 弹窗
→ GET /api/gateway/streams/{adapterCode}/{sourceId}/live → 流地址
→ 内嵌 Jessibuca 播放器fallback: <video> 标签 + flv.js
[云台控制] → 打开 PtzControlPanel.vue 弹窗
→ POST /api/gateway/streams/{adapterCode}/{sourceId}/ptz
→ 仅方向键: ↑↓←→ + ZOOM +/- + 停止
→ mousedown 开始移动, mouseup 发送停止
[查看回放] → 打开录像时间轴弹窗
→ GET /api/gateway/streams/{adapterCode}/{sourceId}/playback?start=&end=
[获取快照] → 下载快照图片
[同步通道] → 触发网关 B3 重新同步此 NVR 的通道
限制:
- channel 设备显示前 4 个按钮(无"同步通道"
- NVR(IsParent=是) 显示全部 5 个按钮
```
**IoTDeviceActions.vue**
```
[查看实时数据] → GET /api/gateway/realtime/{adapterCode}/{sourceId}
→ 仪表盘或数值显示(温度/湿度/电压...
→ 5s 自动轮询
[设备控制] → 仅 isControlPoint=true 的设备显示
→ 空调温度设定/开关/模式切换
[刷新点位] → 强制刷新当前点位所有设备数据
[查看告警] → GET /api/gateway/alarms/{adapterCode}
→ 跳转或内嵌告警列表
```
### 4.5 API 封装
```
web.vite/src/api/deviceManager.js # (已存在于框架中)
web.vite/src/api/gateway.js # 新增:网关 B 组接口封装
```
```javascript
// gateway.js
import request from '@/uitils/request'
// 基础: 直连网关
const gwBase = 'http://localhost:5100/api/gateway'
export const getStreamUrl = (adapter, deviceId) =>
request({ url: `${gwBase}/streams/${adapter}/${deviceId}/live`, method: 'get' })
export const ptzControl = (adapter, deviceId, direction, speed = 0.5) =>
request({ url: `${gwBase}/streams/${adapter}/${deviceId}/ptz`, method: 'post', data: { direction, speed } })
export const getRealtime = (adapter, deviceId) =>
request({ url: `${gwBase}/realtime/${adapter}/${deviceId}`, method: 'get' })
export const controlDevice = (adapter, deviceId, pointIndex, value) =>
request({ url: `${gwBase}/realtime/${adapter}/control`, method: 'post', data: { deviceId, pointIndex, value } })
export const getAlarms = (adapter, params) =>
request({ url: `${gwBase}/alarms/${adapter}`, method: 'get', params })
export const confirmAlarm = (adapter, alarmId) =>
request({ url: `${gwBase}/alarms/${adapter}/${alarmId}/confirm`, method: 'post' })
```
> 前端直连网关(:5100跨域问题通过 Vol.Pro nginx 反向代理 `/api/gateway/*` → `http://localhost:5100` 解决,或网关配置 CORS。
---
## 5. 可升级性保障清单
| 检查项 | 状态 |
|--------|:---:|
| 不修改框架生成的 C# 基础类 | ✅ |
| 所有自定义代码在 Partial 目录 | ✅ |
| 不修改框架生成的 .vue 文件 | ✅ |
| 前端业务逻辑在 extension 目录 | ✅ |
| 新增组件在 views/xxx/components/ 下 | ✅ |
| 不修改框架 NuGet 包版本 | ✅ |
| 网关调用通过独立 GatewayClient 类 | ✅ |
| A 组 API 用 `[AllowAnonymous]` + Token 二次认证 | ✅ |
| Dictionary/Entity 属性通过代码生成器配置,不手写 | ✅ |
| Startup.cs 注册仅用 `AddHttpClient`/`AddSingleton` | ✅ |
---
## 6. 实施顺序
| 步骤 | 内容 | 依赖 |
|:---:|------|------|
| 1 | 代码生成器生成 device_manager 模块 6 张表 | Phase 0 |
| 2 | 创建 Entity Partial 类 | 步骤 1 |
| 3 | 创建 Service Partial 类 | 步骤 1 |
| 4 | 创建 Controller Partial 类 (A1-A4 + GetRegionTree) | 步骤 3 |
| 5 | 注册 GatewayClient + HttpClient | 步骤 4 |
| 6 | 创建 Quartz Job (3 个) | 步骤 5 |
| 7 | 创建前端组件 (5 个按钮组 + 5 个弹窗) | 步骤 4 |
| 8 | 编写 extension/warehouse/base_device.jsx | 步骤 7 |
| 9 | nginx 配置网关反向代理 | 步骤 8 |
---
> **版本历史**:
> - v1.0 (2025-05-17) — 初版,基于整合方案 v3.1 + Vol.Pro 框架规范

View 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_channelDeviceId=DeviceId
- 关联 iot_devicedataDeviceId=DeviceId
- 关联 iot_alarmDeviceId=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_alarmState="未确认"
- 返回:`{ 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-A4Mock 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 — RealtimePollJobPhase 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
- SyncDevicesJobCron "0 */5 * * * ?"
- HeartbeatMonitorJobCron "0/15 * * * * ?"
- RealtimePollJobCron "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 个工作日(不含联调等待时间)

View 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 KeyHMAC 签名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 按本设计实施网关项目骨架。

View 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/queryskip/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 个工作日(不含联调等待时间)