704 lines
22 KiB
Markdown
704 lines
22 KiB
Markdown
# 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 按本设计实施网关项目骨架。
|