Files
SecMPS/doc/设计文档/对接网关设计文档.md

22 KiB
Raw Blame History

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 接口总览

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 转换
  • 弹性 ExtraDictionary<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

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对象树

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

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

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令牌桶

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 工厂

// 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 组,供管理端调用)

// 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 适配器日志

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 结构

{
  "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 部署)

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 单机部署

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 部署

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 运维命令

# 健康检查
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 按本设计实施网关项目骨架。