# 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 { /// 所有适配器必须实现的基础接口 public interface IGatewayAdapter { string AdapterCode { get; } // "Owl:main" / "MC4:31ku" string DisplayName { get; } // 人类可读名称 AdapterCapabilities Capabilities { get; } // 能力声明 Task InitializeAsync(); // 懒加载初始化 Task HealthCheckAsync(); // 健康检查 } /// 扁平设备列表(Owl/门禁/道闸) public interface IHasFlatDevices : IGatewayAdapter { Task> GetDevicesAsync(int page, int size, string? keyword); } /// 自有对象树(MC4.0) public interface IHasOwnDeviceTree : IGatewayAdapter { Task> GetObjectTreeAsync(); } /// 实时点位值(MC4.0 动环) public interface IHasPoints : IGatewayAdapter { Task> GetRealtimeValuesAsync(string sourceDeviceId); Task SetPointValueAsync(string sourceDeviceId, int pointIndex, double value); } /// 视频流(Owl) public interface IHasStreams : IGatewayAdapter { Task GetLiveUrlAsync(string channelId); Task GetPlaybackUrlAsync(string channelId, DateTime start, DateTime end); Task PtzControlAsync(string channelId, string direction, float speed); Task PtzStopAsync(string channelId); Task GetSnapshotAsync(string channelId); } /// 告警(MC4.0 + Owl AI可选) public interface IHasAlarms : IGatewayAdapter { Task> GetAlarmsAsync( int page, int size, DateTime from, DateTime to, string? level = null, string? state = null); Task ConfirmAlarmAsync(string alarmId); Task EndAlarmAsync(string alarmId); } /// 录像回放(Owl) public interface IHasRecordings : IGatewayAdapter { Task> GetRecordingsAsync( string channelId, DateTime start, DateTime end, int page, int size); } /// 元数据回写(Owl 设备改名等) public interface IAcceptsMetadataPush : IGatewayAdapter { Task PushMetadataAsync(string sourceDeviceId, MetadataChangeSet changes); } } ``` ### 3.2 接口设计原则 - **显式优于隐式**:每个接口明确声明一种能力,适配器按需实现,网关通过 `is` 检查自动发现路由 - **异步优先**:所有方法返回 `Task`/`Task`,避免阻塞线程 - **统一分页**:`PagedResult` 统一 page/size 语义,适配器内部完成 skip/limit 转换 - **弹性 Extra**:`Dictionary` 承载适配器特有属性,不污染核心模型 ### 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? 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? Option { get; set; } // 扩展属性 public List Children { get; set; } = new(); } ``` ### 4.4 PagedResult ```csharp public class PagedResult { public List 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 _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 All => _adapters.AsReadOnly(); public T? FindByCode(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: } → 获取 JWT Token 4. Token 有效期: 3 天 5. 后续请求: Authorization: Bearer ``` **安全要点**: - Token 内存缓存,不落盘 - Token 过期前 1 小时自动刷新(懒刷新策略) - 认证失败不清除缓存 → 重试 3 次后 `Invalidate()` ### 6.3 MC4.0 认证 ``` 1. POST /api/central/auth/conf/get → 获取临时 Token 2. Token 有效期: 8 小时(保守估计) 3. 后续请求: header["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(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(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(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(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(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(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 按本设计实施网关项目骨架。