From 1ad76ae33b54660a3091e33430bfbb807aca3743 Mon Sep 17 00:00:00 2001 From: g82tt Date: Wed, 3 Jun 2026 22:58:36 +0800 Subject: [PATCH] =?UTF-8?q?Owl=E6=95=B4=E6=94=B9=E8=B5=B7=E7=82=B9:=20?= =?UTF-8?q?=E6=95=B4=E6=94=B9=E6=96=B9=E6=A1=88v1.0+=E6=A3=80=E6=9F=A5?= =?UTF-8?q?=E6=8A=A5=E5=91=8A=E5=B0=B1=E7=BB=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/对接文档/GoWVP接口文档.md | 3419 ++++++++++++++++++++++ doc/设计文档/网关Owl模块整改方案_v1.0.md | 423 +++ 2 files changed, 3842 insertions(+) create mode 100644 doc/对接文档/GoWVP接口文档.md create mode 100644 doc/设计文档/网关Owl模块整改方案_v1.0.md diff --git a/doc/对接文档/GoWVP接口文档.md b/doc/对接文档/GoWVP接口文档.md new file mode 100644 index 0000000..55166c0 --- /dev/null +++ b/doc/对接文档/GoWVP接口文档.md @@ -0,0 +1,3419 @@ +--- +title: 默认模块 +language_tabs: + - shell: Shell + - http: HTTP + - javascript: JavaScript + - ruby: Ruby + - python: Python + - php: PHP + - java: Java + - go: Go +toc_footers: [] +includes: [] +search: true +code_clipboard: true +highlight_theme: darkula +headingLevel: 2 +generator: "@tarslib/widdershins v4.0.30" + +--- + +# 默认模块 + +统一的设备管理和通道管理接口,支持 GB28181、ONVIF 等多种协议 + +Base URLs: + +* 测试环境: http://test-cn.your-api-server.com + +# Authentication + +- HTTP Authentication, scheme: bearer + +# 控制台 + +## GET 服务器状态 + +GET /stats + +> 返回示例 + +> 200 Response + +```json +{"cpu":[{"time":"2024-12-29 16:28:26","use":10.41666666668772},{"time":"2024-12-29 16:28:28","use":2.296450939451207},{"time":"2024-12-29 16:28:29","use":2.7956989247408885},{"time":"2024-12-29 16:28:30","use":1.7838405036729184},{"time":"2024-12-29 16:28:31","use":1.145833333329228},{"time":"2024-12-29 16:28:33","use":0.9443861490000186},{"time":"2024-12-29 16:28:34","use":2.7225130890060782},{"time":"2024-12-29 16:28:35","use":2.7513227513264837},{"time":"2024-12-29 16:28:36","use":5.3684210526338765},{"time":"2024-12-29 16:28:37","use":3.676470588238385},{"time":"2024-12-29 16:28:39","use":1.3584117032388177},{"time":"2024-12-29 16:28:40","use":3.055848261325749},{"time":"2024-12-29 16:28:41","use":4.302203567680006},{"time":"2024-12-29 16:28:42","use":3.343782654122},{"time":"2024-12-29 16:28:43","use":2.492211838007481},{"time":"2024-12-29 16:28:45","use":1.1542497376755163},{"time":"2024-12-29 16:28:46","use":3.0398322851123263},{"time":"2024-12-29 16:28:47","use":1.7726798748656805},{"time":"2024-12-29 16:28:48","use":5.02564102564345},{"time":"2024-12-29 16:28:50","use":7.356076759076739},{"time":"2024-12-29 16:28:51","use":1.3485477178462189}],"disk":[{"name":"/app","total":134681202688,"used":50874556416}],"mem":[{"time":"2024-12-29 16:28:26","use":85.20020682708717},{"time":"2024-12-29 16:28:28","use":85.20020682708717},{"time":"2024-12-29 16:28:29","use":85.1969191132737},{"time":"2024-12-29 16:28:30","use":85.2031956578267},{"time":"2024-12-29 16:28:31","use":85.2031956578267},{"time":"2024-12-29 16:28:33","use":85.2031956578267},{"time":"2024-12-29 16:28:34","use":85.20917331930575},{"time":"2024-12-29 16:28:35","use":85.21535023616744},{"time":"2024-12-29 16:28:36","use":85.47667367049337},{"time":"2024-12-29 16:28:37","use":85.48534127963799},{"time":"2024-12-29 16:28:39","use":85.58736003554715},{"time":"2024-12-29 16:28:40","use":85.6506236195338},{"time":"2024-12-29 16:28:41","use":85.64982659800326},{"time":"2024-12-29 16:28:42","use":85.64922883185537},{"time":"2024-12-29 16:28:43","use":85.64922883185537},{"time":"2024-12-29 16:28:45","use":85.63627723198407},{"time":"2024-12-29 16:28:46","use":85.63627723198407},{"time":"2024-12-29 16:28:47","use":85.64235452115445},{"time":"2024-12-29 16:28:48","use":85.64863106570745},{"time":"2024-12-29 16:28:50","use":85.64743553341164},{"time":"2024-12-29 16:28:51","use":85.65371207796466}],"net":[{"time":"2024-12-29 16:28:26","use":0,"down":3040},{"time":"2024-12-29 16:28:28","use":0,"up":7072,"down":15160},{"time":"2024-12-29 16:28:29","use":0},{"time":"2024-12-29 16:28:30","use":0},{"time":"2024-12-29 16:28:31","use":0},{"time":"2024-12-29 16:28:33","use":0,"up":11680,"down":10424},{"time":"2024-12-29 16:28:34","use":0},{"time":"2024-12-29 16:28:35","use":0,"up":14280,"down":10424},{"time":"2024-12-29 16:28:36","use":0},{"time":"2024-12-29 16:28:37","use":0},{"time":"2024-12-29 16:28:39","use":0},{"time":"2024-12-29 16:28:40","use":0,"up":18720,"down":9896},{"time":"2024-12-29 16:28:41","use":0,"up":20768,"down":10424},{"time":"2024-12-29 16:28:42","use":0},{"time":"2024-12-29 16:28:43","use":0},{"time":"2024-12-29 16:28:45","use":0},{"time":"2024-12-29 16:28:46","use":0,"up":25720,"down":10424},{"time":"2024-12-29 16:28:47","use":0},{"time":"2024-12-29 16:28:48","use":0},{"time":"2024-12-29 16:28:50","use":0}]} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» cpu|[object]|true|none||none| +|»» time|string|true|none||none| +|»» used|number|true|none||none| +|» disk|[object]|true|none||none| +|»» name|string|false|none||none| +|»» total|integer|false|none||none| +|»» used|integer|false|none||none| +|» mem|[object]|true|none||none| +|»» time|string|true|none||none| +|»» used|number|true|none||none| +|» net|[object]|true|none||none| +|»» time|string|true|none||none| +|»» used|integer|true|none||none| +|»» down|integer|true|none||none| +|»» up|integer|true|none||none| + +# 推流列表 + +## POST 添加推流通道 + +POST /stream_pushs + +> Body 请求参数 + +```json +{ + "name": "123123", + "app": "live", + "stream": "123", + "gbId": "1241231231233", + "longitude": null, + "latitude": null +} +``` + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|body|body|object| 否 ||none| +|» name|body|string| 否 | 自定义名称|none| +|» app|body|string| 是 | 推流应用|none| +|» stream|body|string| 是 | 流唯一标识|none| +|» is_auth_enabled|body|boolean| 是 | 是否开启推流鉴权|none| + +> 返回示例 + +> 200 Response + +```json +{"id":"rsbpbgr","created_at":"2025-01-19 07:56:26","updated_at":"2025-01-19 07:56:26","name":"测试添加","pushed_at":"1970-01-01 00:00:00","stopped_at":"1970-01-01 00:00:00","app":"liv1","stream":"110","media_server_id":"","server_id":"","status":""} +``` + +> 400 Response + +```json +{ + "msg": "string", + "reason": "string", + "trace_id": "string" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» id|string|true|none||none| +|» created_at|string|true|none||none| +|» updated_at|string|true|none||none| +|» name|string|true|none||none| +|» pushed_at|string|true|none||none| +|» stopped_at|string|true|none||none| +|» app|string|true|none||none| +|» stream|string|true|none||none| +|» media_server_id|string|true|none||none| +|» server_id|string|true|none||none| +|» status|string|true|none||none| + +状态码 **400** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» msg|string|true|none||none| +|» reason|string|true|none||none| +|» trace_id|string|true|none||none| + +## GET 查询推流通道列表 + +GET /stream_pushs + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|page|query|integer| 是 ||分页| +|size|query|integer| 是 ||每页数量| +|key|query|string| 否 || 关键字模糊搜索,可输入 id,app,stream。| +|status|query|string| 否 || 推流状态过滤(pushing,stopped)| + +> 返回示例 + +> 200 Response + +```json +{"items":[{"id":"r7ve90","created_at":"2025-01-18 17:31:41","updated_at":"2025-01-19 01:49:57","name":"123123","pushed_at":"1970-01-01 00:00:00","stopped_at":"1970-01-01 00:00:00","app":"live","stream":"123","media_server_id":"local","server_id":"","status":"PUSHING"},{"id":"aj3psw","created_at":"2025-01-18 17:32:22","updated_at":"2025-01-18 17:32:22","name":"测试添加","pushed_at":"1970-01-01 00:00:00","stopped_at":"1970-01-01 00:00:00","app":"live","stream":"110","media_server_id":"","server_id":"","status":""},{"id":"6v1aeh","created_at":"2025-01-18 18:06:10","updated_at":"2025-01-18 18:06:10","name":"测试添加","pushed_at":"1970-01-01 00:00:00","stopped_at":"1970-01-01 00:00:00","app":"live1","stream":"110","media_server_id":"","server_id":"","status":""}],"total":3} +``` + +> 400 Response + +```json +{ + "msg": "string", + "reason": "string", + "trace_id": "string" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» items|[object]|true|none||none| +|»» id|string|true|none|唯一标识|none| +|»» created_at|string|true|none|创建时间|none| +|»» updated_at|string|true|none|更新时间|none| +|»» name|string|true|none|别名|none| +|»» pushed_at|string|true|none|推流时间|none| +|»» stopped_at|string|true|none|停流时间|none| +|»» app|string|true|none|应用|none| +|»» stream|string|true|none|流标识|none| +|»» media_server_id|string|true|none|流媒体服务标识|none| +|»» server_id|string|true|none||none| +|»» status|string|true|none|状态|PUSHING/STOPPED| +|» total|integer|true|none|列表总长度|none| + +状态码 **400** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» msg|string|true|none||none| +|» reason|string|true|none||none| +|» trace_id|string|true|none||none| + +## DELETE 删除推流通道 + +DELETE /stream_pushs/{id} + +重复删除同一个 id,不会提示错误,因为结果是确实删除了。 +第一次: 返回被删除的对象数据 +第二次: 返回 id 为空串 +更多: 返回 id 为空串 + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|string| 是 ||推流通道唯一标识 ID| + +> 返回示例 + +> 200 Response + +```json +{"id":"rsbpbgr","created_at":"2025-01-19 07:56:26","updated_at":"2025-01-19 07:56:26","name":"测试添加","pushed_at":"1970-01-01 00:00:00","stopped_at":"1970-01-01 00:00:00","app":"liv1","stream":"110","media_server_id":"","server_id":"","status":""} +``` + +> 400 Response + +```json +{ + "msg": "string", + "reason": "string", + "trace_id": "string" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» id|string|true|none||none| +|» created_at|string|true|none||none| +|» updated_at|string|true|none||none| +|» name|string|true|none||none| +|» pushed_at|string|true|none||none| +|» stopped_at|string|true|none||none| +|» app|string|true|none||none| +|» stream|string|true|none||none| +|» media_server_id|string|true|none||none| +|» server_id|string|true|none||none| +|» status|string|true|none||none| + +状态码 **400** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» msg|string|true|none||none| +|» reason|string|true|none||none| +|» trace_id|string|true|none||none| + +## PUT 编辑推流通道 + +PUT /stream_pushs/{id} + +> Body 请求参数 + +```json +{ + "name": "123123", + "app": "live", + "stream": "123", + "gbId": "1241231231233", + "longitude": null, + "latitude": null +} +``` + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|string| 是 ||none| +|body|body|object| 否 ||none| +|» name|body|string| 否 | 自定义名称|none| +|» app|body|string| 是 | 推流应用|none| +|» stream|body|string| 是 | 流唯一标识|none| +|» is_auth_enabled|body|boolean| 是 | 是否开启推流鉴权|none| + +> 返回示例 + +> 200 Response + +```json +{"id":"rsbpbgr","created_at":"2025-01-19 07:56:26","updated_at":"2025-01-19 07:56:26","name":"测试添加","pushed_at":"1970-01-01 00:00:00","stopped_at":"1970-01-01 00:00:00","app":"liv1","stream":"110","media_server_id":"","server_id":"","status":""} +``` + +> 400 Response + +```json +{ + "msg": "string", + "reason": "string", + "trace_id": "string" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» id|string|true|none||none| +|» created_at|string|true|none||none| +|» updated_at|string|true|none||none| +|» name|string|true|none||none| +|» pushed_at|string|true|none||none| +|» stopped_at|string|true|none||none| +|» app|string|true|none||none| +|» stream|string|true|none||none| +|» media_server_id|string|true|none||none| +|» server_id|string|true|none||none| +|» status|string|true|none||none| + +状态码 **400** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» msg|string|true|none||none| +|» reason|string|true|none||none| +|» trace_id|string|true|none||none| + +# 拉流代理 + +## POST 添加拉流代理 + +POST /stream_proxys + +> Body 请求参数 + +```json +{ + "app": "string", + "stream": "string", + "transport": 0, + "timeout_s": 0, + "enabled": true, + "source_url": "string" +} +``` + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|body|body|object| 否 ||none| +|» app|body|string| 是 | 流应用名称|none| +|» stream|body|string| 是 | 流 id|none| +|» transport|body|integer| 是 | 拉流方式(rtsp)|0:udp; 1: tcp| +|» timeout_s|body|integer| 是 | 超时时间秒|none| +|» enabled|body|boolean| 是 | 是否启用|none| +|» source_url|body|string| 是 | 拉流地址|none| + +> 返回示例 + +> 200 Response + +```json +{ + "id": "string", + "created_at": "string", + "updated_at": "string", + "app": "string", + "stream": "string", + "media_server_id": "string", + "source_url": "string", + "timeout_s": 0, + "transport": 0, + "enabled": true, + "enabled_audio": true, + "enabled_remove_none_reader": true, + "enabled_disabled_none_reader": true, + "stream_key": "string", + "pulling": true +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» id|string|true|none||none| +|» created_at|string|true|none||none| +|» updated_at|string|true|none||none| +|» app|string|true|none||none| +|» stream|string|true|none||none| +|» media_server_id|string|true|none||none| +|» source_url|string|true|none||none| +|» timeout_s|integer|true|none||none| +|» transport|integer|true|none||none| +|» enabled|boolean|true|none||none| +|» enabled_audio|boolean|true|none||none| +|» enabled_remove_none_reader|boolean|true|none||none| +|» enabled_disabled_none_reader|boolean|true|none||none| +|» stream_key|string|true|none||none| +|» pulling|boolean|true|none||none| + +## PUT 编辑拉流代理 + +PUT /stream_proxys + +> Body 请求参数 + +```json +{ + "app": "string", + "stream": "string", + "transport": 0, + "timeout_s": 0, + "enabled": true, + "source_url": "string" +} +``` + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|body|body|object| 否 ||none| +|» app|body|string| 是 | 流应用名称|none| +|» stream|body|string| 是 | 流 id|none| +|» transport|body|integer| 是 | 拉流方式(rtsp)|0:udp; 1: tcp| +|» timeout_s|body|integer| 是 | 超时时间秒|none| +|» enabled|body|boolean| 是 | 是否启用|none| +|» source_url|body|string| 是 | 拉流地址|none| + +> 返回示例 + +> 200 Response + +```json +{ + "id": "string", + "created_at": "string", + "updated_at": "string", + "app": "string", + "stream": "string", + "media_server_id": "string", + "source_url": "string", + "timeout_s": 0, + "transport": 0, + "enabled": true, + "enabled_audio": true, + "enabled_remove_none_reader": true, + "enabled_disabled_none_reader": true, + "stream_key": "string", + "pulling": true +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» id|string|true|none||none| +|» created_at|string|true|none||none| +|» updated_at|string|true|none||none| +|» app|string|true|none||none| +|» stream|string|true|none||none| +|» media_server_id|string|true|none||none| +|» source_url|string|true|none||none| +|» timeout_s|integer|true|none||none| +|» transport|integer|true|none||none| +|» enabled|boolean|true|none||none| +|» enabled_audio|boolean|true|none||none| +|» enabled_remove_none_reader|boolean|true|none||none| +|» enabled_disabled_none_reader|boolean|true|none||none| +|» stream_key|string|true|none||none| +|» pulling|boolean|true|none||none| + +## GET 查询拉流代理列表 + +GET /stream_proxys + +> 返回示例 + +> 200 Response + +```json +{ + "items": [ + { + "id": "string", + "created_at": "string", + "updated_at": "string", + "app": "string", + "stream": "string", + "media_server_id": "string", + "source_url": "string", + "timeout_s": 0, + "transport": 0, + "enabled": true, + "enabled_audio": true, + "enabled_remove_none_reader": true, + "enabled_disabled_none_reader": true, + "stream_key": "string", + "pulling": true + } + ], + "total": 0 +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» items|[object]|true|none||none| +|»» id|string|true|none||none| +|»» created_at|string|true|none||none| +|»» updated_at|string|true|none||none| +|»» app|string|true|none||none| +|»» stream|string|true|none||none| +|»» media_server_id|string|true|none||none| +|»» source_url|string|true|none||none| +|»» timeout_s|integer|true|none||none| +|»» transport|integer|true|none||none| +|»» enabled|boolean|true|none||none| +|»» enabled_audio|boolean|true|none||none| +|»» enabled_remove_none_reader|boolean|true|none||none| +|»» enabled_disabled_none_reader|boolean|true|none||none| +|»» stream_key|string|true|none||none| +|»» pulling|boolean|true|none||none| +|» total|integer|true|none||none| + +## DELETE 添加拉流代理 + +DELETE /stream_proxys/{id} + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|string| 是 ||none| + +> 返回示例 + +> 200 Response + +```json +{ + "id": "string", + "created_at": "string", + "updated_at": "string", + "app": "string", + "stream": "string", + "media_server_id": "string", + "source_url": "string", + "timeout_s": 0, + "transport": 0, + "enabled": true, + "enabled_audio": true, + "enabled_remove_none_reader": true, + "enabled_disabled_none_reader": true, + "stream_key": "string", + "pulling": true +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» id|string|true|none||none| +|» created_at|string|true|none||none| +|» updated_at|string|true|none||none| +|» app|string|true|none||none| +|» stream|string|true|none||none| +|» media_server_id|string|true|none||none| +|» source_url|string|true|none||none| +|» timeout_s|integer|true|none||none| +|» transport|integer|true|none||none| +|» enabled|boolean|true|none||none| +|» enabled_audio|boolean|true|none||none| +|» enabled_remove_none_reader|boolean|true|none||none| +|» enabled_disabled_none_reader|boolean|true|none||none| +|» stream_key|string|true|none||none| +|» pulling|boolean|true|none||none| + +# 国标设备 + +## POST 添加设备 + +POST /devices + +添加设备(所有协议,通过 type 区分) + +示例1 - 添加 GB28181 设备: +```json +{ "type": "GB28181", "device_id": "34020000001320000001", "name": "摄像头1" } +``` + +示例2 - 添加 ONVIF 设备: +```json +{ "type": "ONVIF", "ip": "192.168.1.100", "port": 80, "username": "admin", "password": "12345" } +``` + +> Body 请求参数 + +```json +{ + "device_id": "string", + "username": "string", + "ip": "string", + "port": 0, + "name": "string", + "password": "string", + "type": "GB28181" +} +``` + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|body|body|[AddDeviceInput](#schemaadddeviceinput)| 是 ||none| + +> 返回示例 + +> 200 Response + +```json +{ + "id": "string", + "type": "string", + "device_id": "string", + "name": "string", + "transport": "string", + "stream_mode": 0, + "ip": "string", + "port": 0, + "is_online": true, + "registered_at": 0, + "keepalive_at": 0, + "keepalives": 0, + "expires": 0, + "channels": 0, + "created_at": 0, + "updated_at": 0, + "password": "string", + "address": "string", + "username": "string", + "ext": { + "manufacturer": "string", + "model": "string", + "firmware": "string", + "name": "string", + "gb_version": "string", + "zones": [ + { + "name": "string", + "coordinates": [ + 0.1 + ], + "color": "string", + "labels": [ + "string" + ] + } + ], + "enabled_ai": true + } +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|[Device](#schemadevice)| + +## GET 设备列表 + +GET /devices + +设备列表(所有协议) + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|page|query|integer| 否 ||页码| +|size|query|integer| 否 ||每页数量| +|sort|query|string| 否 ||排序字段| +|key|query|string| 否 ||搜索关键字| + +> 返回示例 + +> 200 Response + +```json +{ + "items": [ + { + "id": "string", + "type": "string", + "device_id": "string", + "name": "string", + "transport": "string", + "stream_mode": 0, + "ip": "string", + "port": 0, + "is_online": true, + "registered_at": 0, + "keepalive_at": 0, + "keepalives": 0, + "expires": 0, + "channels": 0, + "created_at": 0, + "updated_at": 0, + "password": "string", + "address": "string", + "username": "string", + "ext": { + "manufacturer": "string", + "model": "string", + "firmware": "string", + "name": "string", + "gb_version": "string", + "zones": [ + { + "name": null, + "coordinates": null, + "color": null, + "labels": null + } + ], + "enabled_ai": true + } + } + ], + "total": 0 +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» items|[[Device](#schemadevice)]|false|none||none| +|»» id|string|false|none||设备 ID| +|»» type|string|false|none||设备类型(GB28181/ONVIF/RTMP/RTSP)| +|»» device_id|string|false|none||20 位国标编号| +|»» name|string|false|none||设备名称| +|»» transport|string|false|none||传输协议(TCP/UDP)| +|»» stream_mode|integer|false|none||数据传输模式(0:UDP; 1:TCP_PASSIVE; 2:TCP_ACTIVE)| +|»» ip|string|false|none||IP 地址| +|»» port|integer|false|none||端口| +|»» is_online|boolean|false|none||是否在线| +|»» registered_at|integer|false|none||注册时间(毫秒时间戳)| +|»» keepalive_at|integer|false|none||心跳时间(毫秒时间戳)| +|»» keepalives|integer|false|none||心跳间隔| +|»» expires|integer|false|none||注册有效期| +|»» channels|integer|false|none||通道数量| +|»» created_at|integer|false|none||创建时间(毫秒时间戳)| +|»» updated_at|integer|false|none||更新时间(毫秒时间戳)| +|»» password|string|false|none||注册密码| +|»» address|string|false|none||设备网络地址| +|»» username|string|false|none||用户名| +|»» ext|[DeviceExt](#schemadeviceext)|false|none||none| +|»»» manufacturer|string|false|none||生产厂商| +|»»» model|string|false|none||型号| +|»»» firmware|string|false|none||固件版本| +|»»» name|string|false|none||设备名| +|»»» gb_version|string|false|none||GB 版本| +|»»» zones|[[Zone](#schemazone)]|false|none||区域| +|»»»» name|string|false|none||区域名称| +|»»»» coordinates|[number]|false|none||坐标| +|»»»» color|string|false|none||颜色,支持 hex 颜色值,如| +|»»»» labels|[string]|false|none||标签| +|»»» enabled_ai|boolean|false|none||是否启用 AI| +|» total|integer|false|none||总数| + +## DELETE 删除设备 + +DELETE /devices/{id} + +删除设备(所有协议) + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|string| 是 ||设备 ID| + +> 返回示例 + +> 200 Response + +```json +{ + "id": "string", + "type": "string", + "device_id": "string", + "name": "string", + "transport": "string", + "stream_mode": 0, + "ip": "string", + "port": 0, + "is_online": true, + "registered_at": 0, + "keepalive_at": 0, + "keepalives": 0, + "expires": 0, + "channels": 0, + "created_at": 0, + "updated_at": 0, + "password": "string", + "address": "string", + "username": "string", + "ext": { + "manufacturer": "string", + "model": "string", + "firmware": "string", + "name": "string", + "gb_version": "string", + "zones": [ + { + "name": "string", + "coordinates": [ + 0.1 + ], + "color": "string", + "labels": [ + "string" + ] + } + ], + "enabled_ai": true + } +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|[Device](#schemadevice)| + +## PUT 修改设备 + +PUT /devices/{id} + +修改设备(所有协议) + +> Body 请求参数 + +```json +{ + "device_id": "string", + "name": "string", + "password": "string", + "stream_mode": 0, + "username": "string", + "ip": "string", + "port": 0 +} +``` + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|string| 是 ||设备 ID| +|body|body|[EditDeviceInput](#schemaeditdeviceinput)| 是 ||none| + +> 返回示例 + +> 200 Response + +```json +{ + "id": "string", + "type": "string", + "device_id": "string", + "name": "string", + "transport": "string", + "stream_mode": 0, + "ip": "string", + "port": 0, + "is_online": true, + "registered_at": 0, + "keepalive_at": 0, + "keepalives": 0, + "expires": 0, + "channels": 0, + "created_at": 0, + "updated_at": 0, + "password": "string", + "address": "string", + "username": "string", + "ext": { + "manufacturer": "string", + "model": "string", + "firmware": "string", + "name": "string", + "gb_version": "string", + "zones": [ + { + "name": "string", + "coordinates": [ + 0.1 + ], + "color": "string", + "labels": [ + "string" + ] + } + ], + "enabled_ai": true + } +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|[Device](#schemadevice)| + +## GET 设备详情 + +GET /devices/{id} + +设备详情(所有协议) + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|string| 是 ||设备 ID| + +> 返回示例 + +> 200 Response + +```json +{ + "id": "string", + "type": "string", + "device_id": "string", + "name": "string", + "transport": "string", + "stream_mode": 0, + "ip": "string", + "port": 0, + "is_online": true, + "registered_at": 0, + "keepalive_at": 0, + "keepalives": 0, + "expires": 0, + "channels": 0, + "created_at": 0, + "updated_at": 0, + "password": "string", + "address": "string", + "username": "string", + "ext": { + "manufacturer": "string", + "model": "string", + "firmware": "string", + "name": "string", + "gb_version": "string", + "zones": [ + { + "name": "string", + "coordinates": [ + 0.1 + ], + "color": "string", + "labels": [ + "string" + ] + } + ], + "enabled_ai": true + } +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|[Device](#schemadevice)| + +## GET 设备与通道列表 + +GET /devices/channels + +设备与通道列表(所有协议),返回设备及其子通道 + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|page|query|integer| 否 ||页码| +|size|query|integer| 否 ||每页数量| +|sort|query|string| 否 ||排序字段| +|key|query|string| 否 ||搜索关键字| + +> 返回示例 + +> 200 Response + +```json +{ + "items": [ + { + "id": "string", + "type": "string", + "device_id": "string", + "name": "string", + "transport": "string", + "stream_mode": 0, + "ip": "string", + "port": 0, + "is_online": true, + "registered_at": 0, + "keepalive_at": 0, + "keepalives": 0, + "expires": 0, + "channels": 0, + "created_at": 0, + "updated_at": 0, + "password": "string", + "address": "string", + "username": "string", + "ext": { + "manufacturer": "string", + "model": "string", + "firmware": "string", + "name": "string", + "gb_version": "string", + "zones": [ + null + ], + "enabled_ai": true + }, + "children": [ + { + "id": null, + "did": null, + "device_id": null, + "channel_id": null, + "name": null, + "ptztype": null, + "is_online": null, + "is_playing": null, + "type": null, + "app": null, + "stream": null, + "config": null, + "created_at": null, + "updated_at": null, + "ext": null + } + ] + } + ], + "total": 0 +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» items|[allOf]|false|none||none| + +*allOf* + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|»» *anonymous*|[Device](#schemadevice)|false|none||none| +|»»» id|string|false|none||设备 ID| +|»»» type|string|false|none||设备类型(GB28181/ONVIF/RTMP/RTSP)| +|»»» device_id|string|false|none||20 位国标编号| +|»»» name|string|false|none||设备名称| +|»»» transport|string|false|none||传输协议(TCP/UDP)| +|»»» stream_mode|integer|false|none||数据传输模式(0:UDP; 1:TCP_PASSIVE; 2:TCP_ACTIVE)| +|»»» ip|string|false|none||IP 地址| +|»»» port|integer|false|none||端口| +|»»» is_online|boolean|false|none||是否在线| +|»»» registered_at|integer|false|none||注册时间(毫秒时间戳)| +|»»» keepalive_at|integer|false|none||心跳时间(毫秒时间戳)| +|»»» keepalives|integer|false|none||心跳间隔| +|»»» expires|integer|false|none||注册有效期| +|»»» channels|integer|false|none||通道数量| +|»»» created_at|integer|false|none||创建时间(毫秒时间戳)| +|»»» updated_at|integer|false|none||更新时间(毫秒时间戳)| +|»»» password|string|false|none||注册密码| +|»»» address|string|false|none||设备网络地址| +|»»» username|string|false|none||用户名| +|»»» ext|[DeviceExt](#schemadeviceext)|false|none||none| +|»»»» manufacturer|string|false|none||生产厂商| +|»»»» model|string|false|none||型号| +|»»»» firmware|string|false|none||固件版本| +|»»»» name|string|false|none||设备名| +|»»»» gb_version|string|false|none||GB 版本| +|»»»» zones|[[Zone](#schemazone)]|false|none||区域| +|»»»»» name|string|false|none||区域名称| +|»»»»» coordinates|[number]|false|none||坐标| +|»»»»» color|string|false|none||颜色,支持 hex 颜色值,如| +|»»»»» labels|[string]|false|none||标签| +|»»»» enabled_ai|boolean|false|none||是否启用 AI| + +*and* + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|»» *anonymous*|object|false|none||none| +|»»» children|[[Channel](#schemachannel)]|false|none||子通道列表| +|»»»» id|string|false|none||通道 ID| +|»»»» did|string|false|none||父级设备 ID| +|»»»» device_id|string|false|none||国标编码| +|»»»» channel_id|string|false|none||国标编码| +|»»»» name|string|false|none||通道名称| +|»»»» ptztype|integer|false|none||云台类型| +|»»»» is_online|boolean|false|none||是否在线(RTMP/RTSP 表示推流/拉流状态)| +|»»»» is_playing|boolean|false|none||是否播放中| +|»»»» type|string|false|none||通道类型(GB28181/ONVIF/RTMP/RTSP)| +|»»»» app|string|false|none||应用名(RTMP/RTSP)| +|»»»» stream|string|false|none||流 ID(RTMP/RTSP)| +|»»»» config|[StreamConfig](#schemastreamconfig)|false|none||流配置(RTMP 推流和 RTSP 拉流代理)| +|»»»»» is_auth_disabled|boolean|false|none||是否禁用推流鉴权| +|»»»»» pushed_at|integer|false|none||最后推流时间(毫秒时间戳)| +|»»»»» stopped_at|integer|false|none||最后停止时间(毫秒时间戳)| +|»»»»» media_server_id|string|false|none||媒体服务器 ID| +|»»»»» push_addr|string|false|none||推流地址(动态生成,仅在查询时返回)| +|»»»»» source_url|string|false|none||原始 RTSP URL| +|»»»»» transport|integer|false|none||拉流方式(0:tcp, 1:udp)| +|»»»»» timeout_s|integer|false|none||超时时间(秒)| +|»»»»» enabled_audio|boolean|false|none||是否启用音频| +|»»»»» enabled_remove_none_reader|boolean|false|none||无人观看时删除| +|»»»»» enabled_disabled_none_reader|boolean|false|none||无人观看时禁用| +|»»»»» stream_key|string|false|none||ZLM 返回的 key| +|»»»»» enabled|boolean|false|none||是否启用| +|»»»» created_at|integer|false|none||创建时间(毫秒时间戳)| +|»»»» updated_at|integer|false|none||更新时间(毫秒时间戳)| +|»»»» ext|[DeviceExt](#schemadeviceext)|false|none||none| +|»»»»» manufacturer|string|false|none||生产厂商| +|»»»»» model|string|false|none||型号| +|»»»»» firmware|string|false|none||固件版本| +|»»»»» name|string|false|none||设备名| +|»»»»» gb_version|string|false|none||GB 版本| +|»»»»» zones|[[Zone](#schemazone)]|false|none||区域| +|»»»»» enabled_ai|boolean|false|none||是否启用 AI| + +*continued* + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» total|integer|false|none||总数| + +## POST 查询设备目录 + +POST /devices/{id}/catalog + +查询设备目录(GB28181 协议) + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|string| 是 ||设备 ID| + +> 返回示例 + +> 200 Response + +```json +{ + "msg": "ok" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» msg|string|false|none||none| + +# 国标设备/通道 + +## POST 播放 + +POST /channels/{id}/play + +播放(所有协议) + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|string| 是 ||通道 ID| + +> 返回示例 + +> 200 Response + +```json +{ + "app": "string", + "stream": "string", + "items": [ + { + "label": "string", + "ws_flv": "string", + "http_flv": "string", + "rtmp": "string", + "rtsp": "string", + "webrtc": "string", + "hls": "string" + } + ] +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|[PlayOutput](#schemaplayoutput)| + +## GET 通道列表 + +GET /channels + +通道列表(所有协议) + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|page|query|integer| 否 ||页码| +|size|query|integer| 否 ||每页数量| +|sort|query|string| 否 ||排序字段| +|did|query|string| 否 ||设备 ID| +|device_id|query|string| 否 ||国标编码| +|key|query|string| 否 ||名称/国标编码 模糊搜索,id 精确搜索| +|is_online|query|string| 否 ||是否在线(true/false)| +|type|query|string| 否 ||通道类型(GB28181/ONVIF/RTMP/RTSP)| +|app|query|string| 否 ||应用名(RTMP/RTSP)| +|stream|query|string| 否 ||流 ID(RTMP/RTSP)| + +#### 枚举值 + +|属性|值| +|---|---| +|type|GB28181| +|type|ONVIF| +|type|RTMP| +|type|RTSP| + +> 返回示例 + +> 200 Response + +```json +{ + "items": [ + { + "id": "string", + "did": "string", + "device_id": "string", + "channel_id": "string", + "name": "string", + "ptztype": 0, + "is_online": true, + "is_playing": true, + "type": "string", + "app": "string", + "stream": "string", + "config": { + "is_auth_disabled": true, + "pushed_at": 0, + "stopped_at": 0, + "media_server_id": "string", + "push_addr": "string", + "source_url": "string", + "transport": 0, + "timeout_s": 0, + "enabled_audio": true, + "enabled_remove_none_reader": true, + "enabled_disabled_none_reader": true, + "stream_key": "string", + "enabled": true + }, + "created_at": 0, + "updated_at": 0, + "ext": { + "manufacturer": "string", + "model": "string", + "firmware": "string", + "name": "string", + "gb_version": "string", + "zones": [ + { + "name": null, + "coordinates": null, + "color": null, + "labels": null + } + ], + "enabled_ai": true + } + } + ], + "total": 0 +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» items|[[Channel](#schemachannel)]|false|none||none| +|»» id|string|false|none||通道 ID| +|»» did|string|false|none||父级设备 ID| +|»» device_id|string|false|none||国标编码| +|»» channel_id|string|false|none||国标编码| +|»» name|string|false|none||通道名称| +|»» ptztype|integer|false|none||云台类型| +|»» is_online|boolean|false|none||是否在线(RTMP/RTSP 表示推流/拉流状态)| +|»» is_playing|boolean|false|none||是否播放中| +|»» type|string|false|none||通道类型(GB28181/ONVIF/RTMP/RTSP)| +|»» app|string|false|none||应用名(RTMP/RTSP)| +|»» stream|string|false|none||流 ID(RTMP/RTSP)| +|»» config|[StreamConfig](#schemastreamconfig)|false|none||流配置(RTMP 推流和 RTSP 拉流代理)| +|»»» is_auth_disabled|boolean|false|none||是否禁用推流鉴权| +|»»» pushed_at|integer|false|none||最后推流时间(毫秒时间戳)| +|»»» stopped_at|integer|false|none||最后停止时间(毫秒时间戳)| +|»»» media_server_id|string|false|none||媒体服务器 ID| +|»»» push_addr|string|false|none||推流地址(动态生成,仅在查询时返回)| +|»»» source_url|string|false|none||原始 RTSP URL| +|»»» transport|integer|false|none||拉流方式(0:tcp, 1:udp)| +|»»» timeout_s|integer|false|none||超时时间(秒)| +|»»» enabled_audio|boolean|false|none||是否启用音频| +|»»» enabled_remove_none_reader|boolean|false|none||无人观看时删除| +|»»» enabled_disabled_none_reader|boolean|false|none||无人观看时禁用| +|»»» stream_key|string|false|none||ZLM 返回的 key| +|»»» enabled|boolean|false|none||是否启用| +|»» created_at|integer|false|none||创建时间(毫秒时间戳)| +|»» updated_at|integer|false|none||更新时间(毫秒时间戳)| +|»» ext|[DeviceExt](#schemadeviceext)|false|none||none| +|»»» manufacturer|string|false|none||生产厂商| +|»»» model|string|false|none||型号| +|»»» firmware|string|false|none||固件版本| +|»»» name|string|false|none||设备名| +|»»» gb_version|string|false|none||GB 版本| +|»»» zones|[[Zone](#schemazone)]|false|none||区域| +|»»»» name|string|false|none||区域名称| +|»»»» coordinates|[number]|false|none||坐标| +|»»»» color|string|false|none||颜色,支持 hex 颜色值,如| +|»»»» labels|[string]|false|none||标签| +|»»» enabled_ai|boolean|false|none||是否启用 AI| +|» total|integer|false|none||总数| + +## PUT 修改通道 + +PUT /channels/{id} + +修改通道(所有协议) + +> Body 请求参数 + +```json +{ + "device_id": "string", + "name": "string", + "ptztype": 0, + "is_online": true, + "app": "string", + "stream": "string", + "config": { + "is_auth_disabled": true, + "pushed_at": 0, + "stopped_at": 0, + "media_server_id": "string", + "push_addr": "string", + "source_url": "string", + "transport": 0, + "timeout_s": 0, + "enabled_audio": true, + "enabled_remove_none_reader": true, + "enabled_disabled_none_reader": true, + "stream_key": "string", + "enabled": true + }, + "ext": { + "manufacturer": "string", + "model": "string", + "firmware": "string", + "name": "string", + "gb_version": "string", + "zones": [ + { + "name": "string", + "coordinates": [ + 0.1 + ], + "color": "string", + "labels": [ + "string" + ] + } + ], + "enabled_ai": true + } +} +``` + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|string| 是 ||通道 ID| +|body|body|[EditChannelInput](#schemaeditchannelinput)| 是 ||none| + +> 返回示例 + +> 200 Response + +```json +{ + "id": "string", + "did": "string", + "device_id": "string", + "channel_id": "string", + "name": "string", + "ptztype": 0, + "is_online": true, + "is_playing": true, + "type": "string", + "app": "string", + "stream": "string", + "config": { + "is_auth_disabled": true, + "pushed_at": 0, + "stopped_at": 0, + "media_server_id": "string", + "push_addr": "string", + "source_url": "string", + "transport": 0, + "timeout_s": 0, + "enabled_audio": true, + "enabled_remove_none_reader": true, + "enabled_disabled_none_reader": true, + "stream_key": "string", + "enabled": true + }, + "created_at": 0, + "updated_at": 0, + "ext": { + "manufacturer": "string", + "model": "string", + "firmware": "string", + "name": "string", + "gb_version": "string", + "zones": [ + { + "name": "string", + "coordinates": [ + 0.1 + ], + "color": "string", + "labels": [ + "string" + ] + } + ], + "enabled_ai": true + } +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|[Channel](#schemachannel)| + +## POST 图像抓拍 + +POST /channels/{id}/snapshot + +图像抓拍(所有协议) + +要获取最新的实时快照,需要传递 url 参数,url 参数建议从播放接口获取 rtsp 地址 + +> Body 请求参数 + +```json +{ + "within_seconds": 0, + "url": "string" +} +``` + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|string| 是 ||通道 ID| +|body|body|[RefreshSnapshotInput](#schemarefreshsnapshotinput)| 否 ||none| + +> 返回示例 + +> 200 Response + +```json +{ + "link": "string" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» link|string|false|none||快照图片链接| + +## GET 获取图像 + +GET /channels/{id}/snapshot + +获取图像(所有协议) + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|string| 是 ||通道 ID| + +> 返回示例 + +> 200 Response + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|JPEG 图片|Inline| + +### 返回数据结构 + +## POST 添加区域 + +POST /channels/{id}/zones + +添加区域(所有协议) + +> Body 请求参数 + +```json +{ + "name": "string", + "coordinates": [ + 0.1 + ], + "color": "string", + "labels": [ + "string" + ] +} +``` + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|string| 是 ||通道 ID| +|body|body|[AddZoneInput](#schemaaddzoneinput)| 是 ||none| + +> 返回示例 + +> 200 Response + +```json +{ + "items": { + "name": "string", + "coordinates": [ + 0.1 + ], + "color": "string", + "labels": [ + "string" + ] + } +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» items|[Zone](#schemazone)|false|none||none| +|»» name|string|false|none||区域名称| +|»» coordinates|[number]|false|none||坐标| +|»» color|string|false|none||颜色,支持 hex 颜色值,如| +|»» labels|[string]|false|none||标签| + +## GET 获取区域 + +GET /channels/{id}/zones + +获取区域(所有协议) + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|string| 是 ||通道 ID| + +> 返回示例 + +> 200 Response + +```json +[ + { + "name": "string", + "coordinates": [ + 0.1 + ], + "color": "string", + "labels": [ + "string" + ] + } +] +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|*anonymous*|[[Zone](#schemazone)]|false|none||none| +|» name|string|false|none||区域名称| +|» coordinates|[number]|false|none||坐标| +|» color|string|false|none||颜色,支持 hex 颜色值,如| +|» labels|[string]|false|none||标签| + +# 配置管理 + +## GET 查询配置信息 + +GET /configs/info + +> 返回示例 + +> 200 Response + +```json +{"sip":{"port":15062,"id":"3402000000200000001","host":"192.168.10.14","domain":"3402000000","password":"12345678"}} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» sip|[sip 配置](#schemasip 配置)|true|none||none| +|»» port|integer|true|none| 端口(tcp/udp)|none| +|»» id|string|true|none| 国标 id|none| +|»» host|string|true|none|主机 ip|none| +|»» domain|string|true|none|国标域|none| +|»» password|string|true|none|全局密码|none| + +## PUT 修改 sip 配置 + +PUT /configs/info/sip + +> Body 请求参数 + +```json +{ + "port": 0, + "id": "string", + "host": "string", + "domain": "string", + "password": "string" +} +``` + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|body|body|[sip 配置](#schemasip 配置)| 否 ||none| + +> 返回示例 + +> 200 Response + +```json +{} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +## GET 查询流媒体列表 + +GET /media_servers + +> 返回示例 + +> 200 Response + +```json +{"items":[{"id":"local","ip":"127.0.0.1","created_at":"2025-03-09 10:30:16","updated_at":"2025-03-16 22:54:20","hook_ip":"192.168.1.10","sdpip":"192.168.1.10","stream_ip":"","ports":{"http":8080,"https":443,"rtmp":1935,"flv":8080,"flvs":443,"ws_flv":8080,"ws_flvs":443,"rtmps":0,"rtpporxy":10001,"rtsp":554,"rtsps":0},"auto_config":false,"secret":"","hook_alive_interval":10,"rtpenable":false,"status":false,"rtpport_range":"20000-20500","send_rtpport_range":"","record_assist_port":0,"last_keepalive_at":"2025-03-09 10:30:16","record_day":0,"record_path":"","type":"zlm","transcode_suffix":""}],"total":1} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» items|[object]|true|none||none| +|»» id|string|false|none||none| +|»» ip|string|false|none||none| +|»» created_at|string|false|none||none| +|»» updated_at|string|false|none||none| +|»» hook_ip|string|false|none||none| +|»» sdpip|string|false|none||none| +|»» stream_ip|string|false|none||none| +|»» ports|object|false|none||none| +|»»» http|integer|true|none||none| +|»»» https|integer|true|none||none| +|»»» rtmp|integer|true|none||none| +|»»» flv|integer|true|none||none| +|»»» flvs|integer|true|none||none| +|»»» ws_flv|integer|true|none||none| +|»»» ws_flvs|integer|true|none||none| +|»»» rtmps|integer|true|none||none| +|»»» rtpporxy|integer|true|none||none| +|»»» rtsp|integer|true|none||none| +|»»» rtsps|integer|true|none||none| +|»» auto_config|boolean|false|none||none| +|»» secret|string|false|none||none| +|»» hook_alive_interval|integer|false|none||none| +|»» rtpenable|boolean|false|none||none| +|»» status|boolean|false|none||none| +|»» rtpport_range|string|false|none||none| +|»» send_rtpport_range|string|false|none||none| +|»» record_assist_port|integer|false|none||none| +|»» last_keepalive_at|string|false|none||none| +|»» record_day|integer|false|none||none| +|»» record_path|string|false|none||none| +|»» type|string|false|none||none| +|»» transcode_suffix|string|false|none||none| +|» total|integer|true|none||none| + +## PUT 修改流媒体配置 + +PUT /media_servers/{id} + +> Body 请求参数 + +```json +{ + "ip": "string", + "sdp_ip": "string", + "secret": "string", + "hook_ip": "string", + "ports": { + "http": 0, + "rtmp": 0, + "flv": 0, + "rtsp": 0 + } +} +``` + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|string| 是 ||none| +|body|body|object| 否 ||none| +|» ip|body|string| 是 | gowvp 联系 zlm 的内网地址|none| +|» sdp_ip|body|string| 是 | 国标收流默认地址|none| +|» secret|body|string| 是 | zlm 的 api 秘钥|none| +|» hook_ip|body|string| 是 | gowvp 的地址,用于接收回调|none| +|» ports|body|object| 是 | zlm 的相关端口|none| +|»» http|body|integer| 是 ||none| +|»» rtmp|body|integer| 是 ||none| +|»» flv|body|integer| 是 ||none| +|»» rtsp|body|integer| 是 ||none| + +> 返回示例 + +> 200 Response + +```json +{} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +# ONVIF + +## GET 设备发现 + +GET /onvif/devices-discover + +响应是 sse, event=discover,data= {"addr": "192.168.1.2"}。 +遇到 event=end,即发现结束。 + +> 返回示例 + +> 200 Response + +```json +{} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +## POST 添加设备 + +POST /onvif + +> Body 请求参数 + +```json +{ + "username": "string", + "password": "string", + "name": "string", + "ip": "string", + "port": 0 +} +``` + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|body|body|object| 否 ||none| +|» username|body|string| 是 ||none| +|» password|body|string| 是 ||none| +|» name|body|string| 是 ||none| +|» ip|body|string| 是 ||none| +|» port|body|integer| 是 ||none| + +> 返回示例 + +> 200 Response + +```json +{} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +## GET ONVIF 设备发现 + +GET /onvif/discover + +ONVIF 设备发现(ONVIF 特有),返回 SSE 事件流 + +> 返回示例 + +> 200 Response + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|SSE 事件流|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» event|string|false|none||事件类型(discover/end)| +|» data|object|false|none||发现的设备信息| + +# GB28181 + +## POST GB28181 快照回调 + +POST /gb28181/snapshot + +GB28181 协议特有的快照回调接口,接收设备上报的快照图片 + +> Body 请求参数 + +```yaml +string + +``` + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|body|body|string(binary)| 是 ||none| + +> 返回示例 + +> 200 Response + +```json +{ + "msg": "ok" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» msg|string|false|none||none| + +# 通道管理 + +## POST 添加通道 + +POST /channels + +添加 RTMP/RTSP 通道 + +设备关联逻辑: +1. 如果提供 device_id,则关联到已存在的设备 +2. 如果提供 device_name(无 device_id),则创建新的虚拟设备 +3. 两者都不提供则返回错误 + +示例1 - 添加 RTMP 推流通道(关联已有设备): +```json +{ + "type": "RTMP", + "name": "推流通道1", + "device_id": "mp123456", + "app": "live", + "stream": "stream1", + "config": { + "is_auth_disabled": false + } +} +``` + +示例2 - 添加 RTSP 拉流通道(创建新设备): +```json +{ + "type": "RTSP", + "name": "拉流通道1", + "device_name": "新设备", + "app": "live", + "stream": "stream2", + "config": { + "source_url": "rtsp://192.168.1.100:554/stream", + "transport": 0, + "enabled": true + } +} +``` + +> Body 请求参数 + +```json +{ + "type": "RTMP", + "name": "string", + "device_id": "string", + "device_name": "string", + "app": "string", + "stream": "string", + "config": { + "is_auth_disabled": true, + "pushed_at": 0, + "stopped_at": 0, + "media_server_id": "string", + "push_addr": "string", + "source_url": "string", + "transport": 0, + "timeout_s": 0, + "enabled_audio": true, + "enabled_remove_none_reader": true, + "enabled_disabled_none_reader": true, + "stream_key": "string", + "enabled": true + } +} +``` + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|body|body|[AddChannelInput](#schemaaddchannelinput)| 是 ||none| + +> 返回示例 + +> 200 Response + +```json +{ + "id": "string", + "did": "string", + "device_id": "string", + "channel_id": "string", + "name": "string", + "ptztype": 0, + "is_online": true, + "is_playing": true, + "type": "string", + "app": "string", + "stream": "string", + "config": { + "is_auth_disabled": true, + "pushed_at": 0, + "stopped_at": 0, + "media_server_id": "string", + "push_addr": "string", + "source_url": "string", + "transport": 0, + "timeout_s": 0, + "enabled_audio": true, + "enabled_remove_none_reader": true, + "enabled_disabled_none_reader": true, + "stream_key": "string", + "enabled": true + }, + "created_at": 0, + "updated_at": 0, + "ext": { + "manufacturer": "string", + "model": "string", + "firmware": "string", + "name": "string", + "gb_version": "string", + "zones": [ + { + "name": "string", + "coordinates": [ + 0.1 + ], + "color": "string", + "labels": [ + "string" + ] + } + ], + "enabled_ai": true + } +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|[Channel](#schemachannel)| + +## DELETE 删除通道 + +DELETE /channels/{id} + +删除 RTMP/RTSP 通道(仅支持删除 RTMP/RTSP 类型通道) + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|string| 是 ||通道 ID| + +> 返回示例 + +> 200 Response + +```json +{ + "id": "string", + "did": "string", + "device_id": "string", + "channel_id": "string", + "name": "string", + "ptztype": 0, + "is_online": true, + "is_playing": true, + "type": "string", + "app": "string", + "stream": "string", + "config": { + "is_auth_disabled": true, + "pushed_at": 0, + "stopped_at": 0, + "media_server_id": "string", + "push_addr": "string", + "source_url": "string", + "transport": 0, + "timeout_s": 0, + "enabled_audio": true, + "enabled_remove_none_reader": true, + "enabled_disabled_none_reader": true, + "stream_key": "string", + "enabled": true + }, + "created_at": 0, + "updated_at": 0, + "ext": { + "manufacturer": "string", + "model": "string", + "firmware": "string", + "name": "string", + "gb_version": "string", + "zones": [ + { + "name": "string", + "coordinates": [ + 0.1 + ], + "color": "string", + "labels": [ + "string" + ] + } + ], + "enabled_ai": true + } +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|[Channel](#schemachannel)| + +## POST 启用 AI 检测 + +POST /channels/{id}/ai/enable + +启用指定通道的 AI 检测功能 + +前置条件: +1. 全局 AI 服务已启用(配置文件中 AI.Disabled=false) +2. AI 服务已连接正常 + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|string| 是 ||通道 ID| + +> 返回示例 + +> 200 Response + +```json +{ + "enabled": true, + "message": "string", + "source_width": 0, + "source_height": 0, + "source_fps": 0 +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» enabled|boolean|false|none||AI 检测是否已启用| +|» message|string|false|none||返回消息| +|» source_width|integer|false|none||视频源宽度| +|» source_height|integer|false|none||视频源高度| +|» source_fps|number|false|none||视频源帧率| + +## POST 禁用 AI 检测 + +POST /channels/{id}/ai/disable + +禁用指定通道的 AI 检测功能,会同时停止正在运行的检测任务 + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|string| 是 ||通道 ID| + +> 返回示例 + +> 200 Response + +```json +{ + "enabled": false, + "message": "AI 检测已停止" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» enabled|boolean|false|none||AI 检测是否已启用| +|» message|string|false|none||返回消息| + +# events + + + +## GET 分页查询事件列表 + +GET /events + +分页查询 AI 检测事件,支持按通道 ID、设备 ID、标签和时间范围筛选。 +结果按事件开始时间倒序排列。 + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|page|query|integer| 否 ||页码| +|size|query|integer| 否 ||每页数量| +|sort|query|string| 否 ||排序字段| +|start_ms|query|integer(int64)| 否 ||开始时间(毫秒时间戳)| +|end_ms|query|integer(int64)| 否 ||结束时间(毫秒时间戳)| +|did|query|string| 否 ||设备 ID| +|cid|query|string| 否 ||通道 ID| +|label|query|string| 否 ||检测标签(如 person, car)| + +> 返回示例 + +> 200 Response + +```json +{ + "items": [ + { + "id": 0, + "did": "string", + "cid": "string", + "started_at": 0, + "ended_at": 0, + "label": "string", + "score": 0.1, + "zones": "string", + "image_path": "string", + "model": "string", + "created_at": 0, + "updated_at": 0 + } + ], + "total": 0 +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» items|[[Event](#schemaevent)]|false|none||none| +|»» id|integer(int64)|false|none||事件 ID| +|»» did|string|false|none||设备 ID| +|»» cid|string|false|none||通道 ID| +|»» started_at|integer(int64)|false|none||事件开始时间(毫秒时间戳)| +|»» ended_at|integer(int64)|false|none||事件结束时间(毫秒时间戳)| +|»» label|string|false|none||检测标签(如 person, car)| +|»» score|number(float)|false|none||置信度(0.0-1.0)| +|»» zones|string|false|none||检测区域 JSON(边界框信息)| +|»» image_path|string|false|none||图片相对路径,可通过 /events/image/{image_path} 访问| +|»» model|string|false|none||分析模型名称| +|»» created_at|integer(int64)|false|none||创建时间(毫秒时间戳)| +|»» updated_at|integer(int64)|false|none||更新时间(毫秒时间戳)| +|» total|integer(int64)|false|none||总数| + + + +## GET 获取单个事件详情 + +GET /events/{id} + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|integer(int64)| 是 ||事件 ID| + +> 返回示例 + +> 200 Response + +```json +{ + "id": 0, + "did": "string", + "cid": "string", + "started_at": 0, + "ended_at": 0, + "label": "string", + "score": 0.1, + "zones": "string", + "image_path": "string", + "model": "string", + "created_at": 0, + "updated_at": 0 +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|[Event](#schemaevent)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|事件不存在|None| + + + +## PUT 更新事件信息 + +PUT /events/{id} + +> Body 请求参数 + +```json +{ + "ended_at": 0 +} +``` + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|integer(int64)| 是 ||事件 ID| +|body|body|[EditEventInput](#schemaediteventinput)| 是 ||none| + +> 返回示例 + +> 200 Response + +```json +{ + "id": 0, + "did": "string", + "cid": "string", + "started_at": 0, + "ended_at": 0, + "label": "string", + "score": 0.1, + "zones": "string", + "image_path": "string", + "model": "string", + "created_at": 0, + "updated_at": 0 +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|[Event](#schemaevent)| + + + +## DELETE 删除事件 + +DELETE /events/{id} + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|id|path|integer(int64)| 是 ||事件 ID| + +> 返回示例 + +> 200 Response + +```json +{ + "id": 0, + "did": "string", + "cid": "string", + "started_at": 0, + "ended_at": 0, + "label": "string", + "score": 0.1, + "zones": "string", + "image_path": "string", + "model": "string", + "created_at": 0, + "updated_at": 0 +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功|[Event](#schemaevent)| + + + +## GET 获取事件快照图片 + +GET /events/image/{path} + +根据事件的 image_path 字段获取快照图片。 +路径格式为: {cid}/{filename}.jpg + +### 请求参数 + +|名称|位置|类型|必选|中文名|说明| +|---|---|---|---|---|---| +|path|path|string| 是 ||图片相对路径(如 ch4w2cz/20260106143003_123456.jpg)| + +> 返回示例 + +> 200 Response + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|成功返回图片|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|图片不存在|None| + +### 返回数据结构 + +# 数据模型 + +

sip 配置

+ + + + + + +```json +{ + "port": 0, + "id": "string", + "host": "string", + "domain": "string", + "password": "string" +} + +``` + +### 属性 + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|port|integer|true|none| 端口(tcp/udp)|none| +|id|string|true|none| 国标 id|none| +|host|string|true|none|主机 ip|none| +|domain|string|true|none|国标域|none| +|password|string|true|none|全局密码|none| + +

Device

+ + + + + + +```json +{ + "id": "string", + "type": "string", + "device_id": "string", + "name": "string", + "transport": "string", + "stream_mode": 0, + "ip": "string", + "port": 0, + "is_online": true, + "registered_at": 0, + "keepalive_at": 0, + "keepalives": 0, + "expires": 0, + "channels": 0, + "created_at": 0, + "updated_at": 0, + "password": "string", + "address": "string", + "username": "string", + "ext": { + "manufacturer": "string", + "model": "string", + "firmware": "string", + "name": "string", + "gb_version": "string", + "zones": [ + { + "name": "string", + "coordinates": [ + 0.1 + ], + "color": "string", + "labels": [ + "string" + ] + } + ], + "enabled_ai": true + } +} + +``` + +### 属性 + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|id|string|false|none||设备 ID| +|type|string|false|none||设备类型(GB28181/ONVIF/RTMP/RTSP)| +|device_id|string|false|none||20 位国标编号| +|name|string|false|none||设备名称| +|transport|string|false|none||传输协议(TCP/UDP)| +|stream_mode|integer|false|none||数据传输模式(0:UDP; 1:TCP_PASSIVE; 2:TCP_ACTIVE)| +|ip|string|false|none||IP 地址| +|port|integer|false|none||端口| +|is_online|boolean|false|none||是否在线| +|registered_at|integer|false|none||注册时间(毫秒时间戳)| +|keepalive_at|integer|false|none||心跳时间(毫秒时间戳)| +|keepalives|integer|false|none||心跳间隔| +|expires|integer|false|none||注册有效期| +|channels|integer|false|none||通道数量| +|created_at|integer|false|none||创建时间(毫秒时间戳)| +|updated_at|integer|false|none||更新时间(毫秒时间戳)| +|password|string|false|none||注册密码| +|address|string|false|none||设备网络地址| +|username|string|false|none||用户名| +|ext|[DeviceExt](#schemadeviceext)|false|none||none| + +

Event

+ + + + + + +```json +{ + "id": 0, + "did": "string", + "cid": "string", + "started_at": 0, + "ended_at": 0, + "label": "string", + "score": 0.1, + "zones": "string", + "image_path": "string", + "model": "string", + "created_at": 0, + "updated_at": 0 +} + +``` + +### 属性 + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|id|integer(int64)|false|none||事件 ID| +|did|string|false|none||设备 ID| +|cid|string|false|none||通道 ID| +|started_at|integer(int64)|false|none||事件开始时间(毫秒时间戳)| +|ended_at|integer(int64)|false|none||事件结束时间(毫秒时间戳)| +|label|string|false|none||检测标签(如 person, car)| +|score|number(float)|false|none||置信度(0.0-1.0)| +|zones|string|false|none||检测区域 JSON(边界框信息)| +|image_path|string|false|none||图片相对路径,可通过 /events/image/{image_path} 访问| +|model|string|false|none||分析模型名称| +|created_at|integer(int64)|false|none||创建时间(毫秒时间戳)| +|updated_at|integer(int64)|false|none||更新时间(毫秒时间戳)| + +

DeviceWithChannels

+ + + + + + +```json +{ + "id": "string", + "type": "string", + "device_id": "string", + "name": "string", + "transport": "string", + "stream_mode": 0, + "ip": "string", + "port": 0, + "is_online": true, + "registered_at": 0, + "keepalive_at": 0, + "keepalives": 0, + "expires": 0, + "channels": 0, + "created_at": 0, + "updated_at": 0, + "password": "string", + "address": "string", + "username": "string", + "ext": { + "manufacturer": "string", + "model": "string", + "firmware": "string", + "name": "string", + "gb_version": "string", + "zones": [ + { + "name": "string", + "coordinates": [ + null + ], + "color": "string", + "labels": [ + null + ] + } + ], + "enabled_ai": true + }, + "children": [ + { + "id": "string", + "did": "string", + "device_id": "string", + "channel_id": "string", + "name": "string", + "ptztype": 0, + "is_online": true, + "is_playing": true, + "type": "string", + "app": "string", + "stream": "string", + "config": { + "is_auth_disabled": true, + "pushed_at": 0, + "stopped_at": 0, + "media_server_id": "string", + "push_addr": "string", + "source_url": "string", + "transport": 0, + "timeout_s": 0, + "enabled_audio": true, + "enabled_remove_none_reader": true, + "enabled_disabled_none_reader": true, + "stream_key": "string", + "enabled": true + }, + "created_at": 0, + "updated_at": 0, + "ext": { + "manufacturer": "string", + "model": "string", + "firmware": "string", + "name": "string", + "gb_version": "string", + "zones": [ + null + ], + "enabled_ai": true + } + } + ] +} + +``` + +### 属性 + +allOf + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|*anonymous*|[Device](#schemadevice)|false|none||none| + +and + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|*anonymous*|object|false|none||none| +|» children|[[Channel](#schemachannel)]|false|none||子通道列表| + +

EditEventInput

+ + + + + + +```json +{ + "ended_at": 0 +} + +``` + +### 属性 + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|ended_at|integer(int64)|false|none||事件结束时间(毫秒时间戳)| + +

Channel

+ + + + + + +```json +{ + "id": "string", + "did": "string", + "device_id": "string", + "channel_id": "string", + "name": "string", + "ptztype": 0, + "is_online": true, + "is_playing": true, + "type": "string", + "app": "string", + "stream": "string", + "config": { + "is_auth_disabled": true, + "pushed_at": 0, + "stopped_at": 0, + "media_server_id": "string", + "push_addr": "string", + "source_url": "string", + "transport": 0, + "timeout_s": 0, + "enabled_audio": true, + "enabled_remove_none_reader": true, + "enabled_disabled_none_reader": true, + "stream_key": "string", + "enabled": true + }, + "created_at": 0, + "updated_at": 0, + "ext": { + "manufacturer": "string", + "model": "string", + "firmware": "string", + "name": "string", + "gb_version": "string", + "zones": [ + { + "name": "string", + "coordinates": [ + 0.1 + ], + "color": "string", + "labels": [ + "string" + ] + } + ], + "enabled_ai": true + } +} + +``` + +### 属性 + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|id|string|false|none||通道 ID| +|did|string|false|none||父级设备 ID| +|device_id|string|false|none||国标编码| +|channel_id|string|false|none||国标编码| +|name|string|false|none||通道名称| +|ptztype|integer|false|none||云台类型| +|is_online|boolean|false|none||是否在线(RTMP/RTSP 表示推流/拉流状态)| +|is_playing|boolean|false|none||是否播放中| +|type|string|false|none||通道类型(GB28181/ONVIF/RTMP/RTSP)| +|app|string|false|none||应用名(RTMP/RTSP)| +|stream|string|false|none||流 ID(RTMP/RTSP)| +|config|[StreamConfig](#schemastreamconfig)|false|none||流配置(RTMP 推流和 RTSP 拉流代理)| +|created_at|integer|false|none||创建时间(毫秒时间戳)| +|updated_at|integer|false|none||更新时间(毫秒时间戳)| +|ext|[DeviceExt](#schemadeviceext)|false|none||none| + +

DeviceExt

+ + + + + + +```json +{ + "manufacturer": "string", + "model": "string", + "firmware": "string", + "name": "string", + "gb_version": "string", + "zones": [ + { + "name": "string", + "coordinates": [ + 0.1 + ], + "color": "string", + "labels": [ + "string" + ] + } + ], + "enabled_ai": true +} + +``` + +### 属性 + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|manufacturer|string|false|none||生产厂商| +|model|string|false|none||型号| +|firmware|string|false|none||固件版本| +|name|string|false|none||设备名| +|gb_version|string|false|none||GB 版本| +|zones|[[Zone](#schemazone)]|false|none||区域| +|enabled_ai|boolean|false|none||是否启用 AI| + +

StreamConfig

+ + + + + + +```json +{ + "is_auth_disabled": true, + "pushed_at": 0, + "stopped_at": 0, + "media_server_id": "string", + "push_addr": "string", + "source_url": "string", + "transport": 0, + "timeout_s": 0, + "enabled_audio": true, + "enabled_remove_none_reader": true, + "enabled_disabled_none_reader": true, + "stream_key": "string", + "enabled": true +} + +``` + +流配置(RTMP 推流和 RTSP 拉流代理) + +### 属性 + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|is_auth_disabled|boolean|false|none||是否禁用推流鉴权| +|pushed_at|integer|false|none||最后推流时间(毫秒时间戳)| +|stopped_at|integer|false|none||最后停止时间(毫秒时间戳)| +|media_server_id|string|false|none||媒体服务器 ID| +|push_addr|string|false|none||推流地址(动态生成,仅在查询时返回)| +|source_url|string|false|none||原始 RTSP URL| +|transport|integer|false|none||拉流方式(0:tcp, 1:udp)| +|timeout_s|integer|false|none||超时时间(秒)| +|enabled_audio|boolean|false|none||是否启用音频| +|enabled_remove_none_reader|boolean|false|none||无人观看时删除| +|enabled_disabled_none_reader|boolean|false|none||无人观看时禁用| +|stream_key|string|false|none||ZLM 返回的 key| +|enabled|boolean|false|none||是否启用| + +

Zone

+ + + + + + +```json +{ + "name": "string", + "coordinates": [ + 0.1 + ], + "color": "string", + "labels": [ + "string" + ] +} + +``` + +### 属性 + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|name|string|false|none||区域名称| +|coordinates|[number]|false|none||坐标| +|color|string|false|none||颜色,支持 hex 颜色值,如| +|labels|[string]|false|none||标签| + +

AddDeviceInput

+ + + + + + +```json +{ + "device_id": "string", + "username": "string", + "ip": "string", + "port": 0, + "name": "string", + "password": "string", + "type": "GB28181" +} + +``` + +### 属性 + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|device_id|string|false|none||20 位国标编号| +|username|string|false|none||用户名(ONVIF 必填)| +|ip|string|false|none||IP 地址(ONVIF 必填)| +|port|integer|false|none||端口(ONVIF 必填)| +|name|string|false|none||设备名称| +|password|string|false|none||注册密码| +|type|string|false|none||设备类型(GB28181/ONVIF)| + +#### 枚举值 + +|属性|值| +|---|---| +|type|GB28181| +|type|ONVIF| + +

EditDeviceInput

+ + + + + + +```json +{ + "device_id": "string", + "name": "string", + "password": "string", + "stream_mode": 0, + "username": "string", + "ip": "string", + "port": 0 +} + +``` + +### 属性 + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|device_id|string|false|none||20 位国标编号| +|name|string|false|none||设备名称| +|password|string|false|none||注册密码| +|stream_mode|integer|false|none||数据传输模式| +|username|string|false|none||用户名| +|ip|string|false|none||IP 地址| +|port|integer|false|none||端口| + +

EditChannelInput

+ + + + + + +```json +{ + "device_id": "string", + "name": "string", + "ptztype": 0, + "is_online": true, + "app": "string", + "stream": "string", + "config": { + "is_auth_disabled": true, + "pushed_at": 0, + "stopped_at": 0, + "media_server_id": "string", + "push_addr": "string", + "source_url": "string", + "transport": 0, + "timeout_s": 0, + "enabled_audio": true, + "enabled_remove_none_reader": true, + "enabled_disabled_none_reader": true, + "stream_key": "string", + "enabled": true + }, + "ext": { + "manufacturer": "string", + "model": "string", + "firmware": "string", + "name": "string", + "gb_version": "string", + "zones": [ + { + "name": "string", + "coordinates": [ + 0.1 + ], + "color": "string", + "labels": [ + "string" + ] + } + ], + "enabled_ai": true + } +} + +``` + +### 属性 + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|device_id|string|false|none||国标编码| +|name|string|false|none||通道名称| +|ptztype|integer|false|none||云台类型| +|is_online|boolean|false|none||是否在线| +|app|string|false|none||应用名(RTMP/RTSP)| +|stream|string|false|none||流 ID(RTMP/RTSP)| +|config|[StreamConfig](#schemastreamconfig)|false|none||流配置(RTMP 推流和 RTSP 拉流代理)| +|ext|[DeviceExt](#schemadeviceext)|false|none||none| + +

AddZoneInput

+ + + + + + +```json +{ + "name": "string", + "coordinates": [ + 0.1 + ], + "color": "string", + "labels": [ + "string" + ] +} + +``` + +### 属性 + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|name|string|false|none||区域名称| +|coordinates|[number]|false|none||坐标| +|color|string|false|none||颜色| +|labels|[string]|false|none||标签| + +

AddChannelInput

+ + + + + + +```json +{ + "type": "RTMP", + "name": "string", + "device_id": "string", + "device_name": "string", + "app": "string", + "stream": "string", + "config": { + "is_auth_disabled": true, + "pushed_at": 0, + "stopped_at": 0, + "media_server_id": "string", + "push_addr": "string", + "source_url": "string", + "transport": 0, + "timeout_s": 0, + "enabled_audio": true, + "enabled_remove_none_reader": true, + "enabled_disabled_none_reader": true, + "stream_key": "string", + "enabled": true + } +} + +``` + +### 属性 + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|type|string|true|none||通道类型(仅支持 RTMP/RTSP)| +|name|string|true|none||通道名称| +|device_id|string|false|none||可选,关联的父设备 ID(与 device_name 二选一)| +|device_name|string|false|none||可选,device_id 不存在时用于创建新设备(与 device_id 二选一)| +|app|string|true|none||应用名| +|stream|string|true|none||流 ID| +|config|[StreamConfig](#schemastreamconfig)|false|none||流配置(RTMP 推流和 RTSP 拉流代理)| + +#### 枚举值 + +|属性|值| +|---|---| +|type|RTMP| +|type|RTSP| + +

RefreshSnapshotInput

+ + + + + + +```json +{ + "within_seconds": 0, + "url": "string" +} + +``` + +### 属性 + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|within_seconds|integer|false|none||指定获取多少秒内创建的快照| +|url|string|false|none||取快照的链接地址| + +

PlayOutput

+ + + + + + +```json +{ + "app": "string", + "stream": "string", + "items": [ + { + "label": "string", + "ws_flv": "string", + "http_flv": "string", + "rtmp": "string", + "rtsp": "string", + "webrtc": "string", + "hls": "string" + } + ] +} + +``` + +### 属性 + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|app|string|false|none||应用名| +|stream|string|false|none||流名| +|items|[[StreamLiveAddr](#schemastreamliveaddr)]|false|none||播放地址列表| + +

StreamLiveAddr

+ + + + + + +```json +{ + "label": "string", + "ws_flv": "string", + "http_flv": "string", + "rtmp": "string", + "rtsp": "string", + "webrtc": "string", + "hls": "string" +} + +``` + +### 属性 + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|label|string|false|none||标签| +|ws_flv|string|false|none||WebSocket FLV 地址| +|http_flv|string|false|none||HTTP FLV 地址| +|rtmp|string|false|none||RTMP 地址| +|rtsp|string|false|none||RTSP 地址| +|webrtc|string|false|none||WebRTC 地址| +|hls|string|false|none||HLS 地址| + diff --git a/doc/设计文档/网关Owl模块整改方案_v1.0.md b/doc/设计文档/网关Owl模块整改方案_v1.0.md new file mode 100644 index 0000000..8019c7b --- /dev/null +++ b/doc/设计文档/网关Owl模块整改方案_v1.0.md @@ -0,0 +1,423 @@ +# 网关 Owl 模块整改方案 v1.0 + +> **版本**: 1.0 +> **日期**: 2026-06-03 +> **基准**: `doc/设计文档/网关owl模块检查报告20260603.md` +> **架构原则**: 遵循网关设计原则 §3.2-3.4(显式、异步、统一分页、弹性 Extra、不修改已有接口签名) + +--- + +## 1. 整改总览 + +| 阶段 | 优先级 | 内容 | 涉及文件 | 预计 | +|:---:|:---:|------|------|:---:| +| O1 | P0 | 设备通道展开 + OwlDevice 模型补全 | OwlAdapter.cs + OwlModels.cs | 2h | +| O2 | P0 | AI 事件接入 IHasAlarms | OwlAdapter.cs + OwlModels.cs | 2h | +| O3 | P1 | 回放取流修正 + PTZ 预设位 | OwlAdapter.cs | 1h | +| O4 | P2 | AI 检测启停(IAcceptsControl) | OwlAdapter.cs | 1h | +| O5 | P2 | 推流/拉流管理(可选独立路由) | Program.cs + OwlAdapter | 1.5h | +| O6 | 验证 | 全量编译 + 联调 | — | 1h | +| **合计** | — | — | **5 文件** | **~8.5h** | + +--- + +## 2. 阶段 O1: 设备通道展开 + 模型补全(预计 2h) + +### 2.1 现状与问题 + +```csharp +// 当前: GET /devices → 只返回NVR父设备 +public async Task> GetDevicesAsync(...) +{ + var json = await client.GetStringAsync($"/devices?page={page}&size={size}"); + // MapDevice: IsParent=true, Category="硬盘录像机" — 无通道子设备 +} +``` + +**后果**: Vol.Pro 设备列表只有 NVR,前端"预览"按钮找不到摄像头通道。 + +### 2.2 整改设计 + +**改用** `GET /devices/channels` — Owl 的联合接口直接返回设备+通道的扁平列表: + +```json +{ + "items": [ + { "id": "mp123", "type": "DEVICE", "name": "NVR-01", "is_online": "1", "channel_count": 4, ... }, + { "id": "mp123/34020000001320000001", "type": "CHANNEL", "did": "mp123", "name": "仓库入口", "is_online": true, "ptztype": 1, ... }, + { "id": "mp123/34020000001320000002", "type": "CHANNEL", "did": "mp123", "name": "仓库后门", "is_online": true, "ptztype": 0, ... } + ], + "total": 3 +} +``` + +**映射逻辑**(单次请求完成父子映射): + +```csharp +// OwlDeviceChannel 联合模型 +public class OwlDeviceChannel +{ + public string? Id { get; set; } + public string? Type { get; set; } // "DEVICE" | "CHANNEL" + public string? Did { get; set; } // 通道所属设备ID + public string? Name { get; set; } + public string? IsOnline { get; set; } // DEVICE: "1"/"0", CHANNEL: true/false + public string? Manufacturer { get; set; } + public string? Model { get; set; } + public string? Firmware { get; set; } + public string? Longitude { get; set; } + public string? Latitude { get; set; } + public int? ChannelCount { get; set; } + public int? Ptztype { get; set; } // CHANNEL: 0=无云台, 1=方向, 2=预置位 + public string? App { get; set; } // CHANNEL: 流应用名 + public string? StreamId { get; set; } // CHANNEL: 流ID + // ... 其他字段 +} +``` + +**GetDevicesAsync 重写**: + +```csharp +public async Task> GetDevicesAsync(int page, int size, string? keyword = null) +{ + await _limiter.WaitAsync(); + var client = await _auth.GetAuthenticatedClientAsync(); + var url = $"/devices/channels?page={page}&size=1000"; // 大pageSize一次性获取 + if (!string.IsNullOrEmpty(keyword)) url += $"&key={Uri.EscapeDataString(keyword)}"; + var json = await client.GetStringAsync(url); + var result = JsonSerializer.Deserialize>(json)!; + + var devices = new List(); + + // 第一遍: 映射 DEVICE 为父设备 + var deviceItems = result.Items.Where(x => x.Type == "DEVICE").ToList(); + var channelItems = result.Items.Where(x => x.Type == "CHANNEL").ToList(); + + foreach (var d in deviceItems) + { + // 收集该设备的通道 + var childChannels = channelItems.Where(c => c.Did == d.Id).ToList(); + + devices.Add(new StandardDevice + { + SourceId = d.Id ?? "", + Name = d.Name ?? d.Id ?? "", + Category = "硬盘录像机", + Group = "视频设备", + IsOnline = d.IsOnline == "1", + IsParent = true, + IpAddress = d.Address, + Extra = new Dictionary + { + ["manufacturer"] = d.Manufacturer, + ["model"] = d.Model, + ["firmware"] = d.Firmware, + ["longitude"] = d.Longitude, + ["latitude"] = d.Latitude, + ["channelCount"] = d.ChannelCount ?? childChannels.Count + } + }); + + // 映射通道为子设备 + foreach (var ch in childChannels) + { + devices.Add(new StandardDevice + { + SourceId = ch.Id ?? "", + Name = ch.Name ?? $"通道{ch.Id}", + Category = "摄像机", + Group = "视频设备", + IsOnline = ch.IsOnline?.ToLower() == "true" || ch.IsOnline == "1", + IsParent = false, + ParentSourceId = d.Id, + Extra = new Dictionary + { + ["hasPtz"] = (ch.Ptztype ?? 0) > 0 ? "1" : "0", + ["app"] = ch.App, + ["streamId"] = ch.StreamId + } + }); + } + } + + return new PagedResult { Items = devices, Total = devices.Count }; +} +``` + +### 2.3 影响分析 + +| 影响点 | 说明 | +|------|------| +| 前端预览按钮 | 现在能找到 `DeviceGroup=视频设备, IsParent=否` 的通道子设备,预览按钮可用 | +| 设备树同步 | A3 同步时有父子关系,`ParentSourceId` 解析为父设备 DeviceId | +| 视频墙 | 摄像机通道列表包含 `hasPtz` 标识,云台面板按需显示 | +| MC4/IoT | 零影响 — 不同适配器独立运行 | + +--- + +## 3. 阶段 O2: AI 事件接入 IHasAlarms(预计 2h) + +### 3.1 现状态 + +`OwlAdapter` **没有**实现 `IHasAlarms`,AI 事件走不到 Vol.Pro。 + +### 3.2 整改设计 + +**OwlAdapter 增加 IHasAlarms 实现**: + +```csharp +public class OwlAdapter : IHasFlatDevices, IHasStreams, IHasRecordings, IAcceptsMetadataPush, IHasAlarms +{ + // Capabilities 增加 HasAlarms = true + + /// GET /events → StandardAlarm[] + public async Task> GetAlarmsAsync( + int page, int size, DateTime from, DateTime to, string? level = null, string? state = null) + { + await _limiter.WaitAsync(); + var client = await _auth.GetAuthenticatedClientAsync(); + var fromMs = new DateTimeOffset(from).ToUnixTimeMilliseconds(); + var toMs = new DateTimeOffset(to).ToUnixTimeMilliseconds(); + var url = $"/events?page={page}&size={size}&start_ms={fromMs}&end_ms={toMs}"; + var json = await client.GetStringAsync(url); + var result = JsonSerializer.Deserialize>(json)!; + + return new PagedResult + { + Items = result.Items.Select(MapEventToAlarm).ToList(), + Total = result.Total + }; + } + + private StandardAlarm MapEventToAlarm(OwlAiEvent e) => new() + { + AlarmId = $"owl-ai-{e.Id}", + AdapterCode = AdapterCode, + Level = e.Label switch { + "person" => "重要", + "car" => "重要", + _ => "普通" + }, + Title = $"AI检测: {e.Label} (置信度 {e.Score:P0})", + Content = $"通道{e.Cid}: {e.Zones ?? ""}", + OccurTime = DateTimeOffset.FromUnixTimeMilliseconds(e.StartedAt ?? 0).DateTime, + Status = e.EndedAt > 0 ? "已结束" : "未确认", + Extra = new Dictionary + { + ["imagePath"] = e.ImagePath, + ["score"] = e.Score, + ["label"] = e.Label, + ["model"] = e.Model + } + }; + + public async Task ConfirmAlarmAsync(string alarmId) { /* AI事件不支持确认 */ } + public async Task EndAlarmAsync(string alarmId) { /* AI事件不支持结束 */ } +} +``` + +### 3.3 事件快照图片 + +网关注册一条 B-路由直接代理图片访问: + +```csharp +// 在 OwlAdapter 中增加 +public async Task GetEventImageAsync(string imagePath) +{ + var client = await _auth.GetAuthenticatedClientAsync(); + return await client.GetByteArrayAsync($"/events/image/{imagePath}"); +} + +// Program.cs 加路由 +app.MapGet("/api/gateway/owl/image/{*path}", async (string path, AdapterRegistry registry) => +{ + var owl = registry.FindByCode("Owl:main"); + if (owl == null) return Results.NotFound(); + var bytes = await owl.GetEventImageAsync(path); + return Results.File(bytes, "image/jpeg"); +}); +``` + +### 3.4 后端 DTO 补充 + +```csharp +/// Owl AI 事件 +public class OwlAiEvent +{ + public long? Id { get; set; } + public string? Did { get; set; } // 设备ID + public string? Cid { get; set; } // 通道ID + public long? StartedAt { get; set; } // 毫秒时间戳 + public long? EndedAt { get; set; } + public string? Label { get; set; } // person / car / ... + public float? Score { get; set; } // 0.0-1.0 + public string? Zones { get; set; } // 检测区域JSON + public string? ImagePath { get; set; } + public string? Model { get; set; } +} +``` + +--- + +## 4. 阶段 O3: 回放修正 + PTZ 扩展(预计 1h) + +### 4.1 GetPlaybackUrlAsync 修正 + +当前手工拼 URL,改为调用 Owl API: + +GoWVP 文档中播放接口 POST /channels/{id}/play 返回的 `PlayOutput.Items[]` 包含 `Hls` 字段。录像回放无需额外接口——同一个 HLS 地址加上 `start_ms`/`end_ms` 参数即可。 + +**方案**: 保持当前实现(手工拼 URL 是 Owl 的约定用法),增加 URL 不存在时的 fallback: + +```csharp +public async Task GetPlaybackUrlAsync(string channelId, DateTime start, DateTime end) +{ + await _limiter.WaitAsync(); + var client = await _auth.GetAuthenticatedClientAsync(); + var token = await _auth.GetTokenAsync(); + var startMs = new DateTimeOffset(start).ToUnixTimeMilliseconds(); + var endMs = new DateTimeOffset(end).ToUnixTimeMilliseconds(); + var baseAddr = client.BaseAddress?.ToString().TrimEnd('/') ?? ""; + return new StreamUrls + { + Hls = $"{baseAddr}/recordings/channels/{channelId}/index.m3u8?start_ms={startMs}&end_ms={endMs}&token={token}" + }; +} +``` + +变化:`client.BaseAddress` → 实际 Owl 地址(之前隐式依赖 `HttpClient.BaseAddress` 已包含)。 + +### 4.2 PTZ 预设位/巡航 + +`PtzControlAsync` 增加 action 参数透传: + +```csharp +public async Task PtzPresetAsync(string channelId, int presetIndex) +{ + await _limiter.WaitAsync(); + var client = await _auth.GetAuthenticatedClientAsync(); + await client.PostAsJsonAsync($"/channels/{channelId}/ptz/control", + new { action = "preset", preset = presetIndex }); +} +``` + +无需修改 `IHasStreams` 接口——PTZ 扩展通过 `PtzControlAsync(direction: "preset_1")` 或新增公开方法由 B-路由直接调用。 + +--- + +## 5. 阶段 O4: AI 检测启停(预计 1h) + +### 5.1 通过 IAcceptsControl 暴露 + +`OwlAdapter` 实现 `IAcceptsControl`(已在 KMS 适配器中新增的接口): + +```csharp +public class OwlAdapter : ..., IAcceptsControl +{ + public async Task SendControlAsync(string sourceDeviceId, string command, Dictionary parameters) + { + await _limiter.WaitAsync(); + var client = await _auth.GetAuthenticatedClientAsync(); + try + { + switch (command) + { + case "ai-enable": + await client.PostAsync($"/channels/{sourceDeviceId}/ai/enable", null); + break; + case "ai-disable": + await client.PostAsync($"/channels/{sourceDeviceId}/ai/disable", null); + break; + case "zone-add": + await client.PostAsJsonAsync($"/channels/{sourceDeviceId}/zones", parameters!); + break; + default: + return new ControlResult { Success = false, Message = $"不支持的指令: {command}" }; + } + return new ControlResult { Success = true }; + } + catch (Exception ex) { return new ControlResult { Success = false, Message = ex.Message }; } + } +} +``` + +前端调用:`POST /api/gateway/control/Owl:main { deviceId: "ch123", command: "ai-enable" }` + +--- + +## 6. 阶段 O5: 推流/拉流管理(可选项,预计 1.5h) + +### 6.1 设计决策 + +推流/拉流管理属于**管理员操作**而非实时数据查询。建议通过 B-组新路由暴露,不新增 Core 接口: + +```csharp +// Program.cs — 推流/拉流 CRUD 路由组 +app.MapGet("/api/gateway/owl/stream-pushs", async (int page, int size, AdapterRegistry registry) => { ... }); +app.MapPost("/api/gateway/owl/stream-pushs", async (StreamPushRequest req, ...) => { ... }); +// ... 类推 +``` + +### 6.2 推流请求模型 + +```csharp +public class StreamPushRequest +{ + public string Name { get; set; } = ""; + public string App { get; set; } = "live"; + public string Stream { get; set; } = ""; + public bool? IsAuthEnabled { get; set; } +} + +public class StreamProxyRequest +{ + public string Name { get; set; } = ""; + public string Type { get; set; } = "RTSP"; + public string App { get; set; } = "live"; + public string Stream { get; set; } = ""; + public string? SourceUrl { get; set; } + public int? Transport { get; set; } +} +``` + +### 6.3 作用 + +管理端通过网关统一管理 Owl 视频源添加/删除/状态查询,无需单独登录 Owl 控制台。前端可加"添加摄像头"按钮调用这些路由。 + +--- + +## 7. 文件变更清单 + +| 文件 | 新增 | 修改 | 说明 | +|------|:---:|:---:|------| +| `OwlAdapter.cs` | — | ✅ | GetDevicesAsync 重写 + IHasAlarms 实现 + IAcceptsControl 实现 + PTZ 预设位 | +| `OwlModels.cs` (新建) | ✅ | — | OwlDeviceChannel + OwlAiEvent + DTO 完整化(从 OwlAdapter.cs 分离) | +| `OwlAuthHelper.cs` | — | ✅ | HealthCheck 端点改 /stats(需确认) | +| `Program.cs` | ✅ | ✅ | AI 事件图片代理路由 + 推流/拉流路由(O5) | +| `IAcceptsControl.cs` | — | — | 已存在(KMS 阶段新增) | + +--- + +## 8. 与现有架构的兼容性 + +| 架构元素 | 影响 | +|------|------| +| IHasFlatDevices 签名 | 不变 — GetDevicesAsync 签名不变,仅内部实现改为调 /devices/channels | +| IHasAlarms | OwlAdapter 新增实现,零冲突 — KMS 也实现了 IHasAlarms | +| IAcceptsControl | OwlAdapter 新增实现 — KMS 已有实现,B10 路由自动发现 | +| AdapterCapabilities | 扩展 HasAlarms=true, FeatureFlags["aiEnable"]=true | +| Vol.Pro A3 同步 | ParentSourceId 已有解析逻辑,新通道子设备自然被正确处理 | +| 前端 base_device.vue | 无改动 — 操作列按钮按 DeviceGroup="视频设备" 自动匹配新展开的通道 | + +--- + +## 9. 验证点 + +| 场景 | 预期 | +|------|------| +| GET /api/gateway/devices?adapter=Owl:main | 返回 NVR 父设备 + 通道子设备,子设备有 hasPtz Extra | +| Vol.Pro 设备列表 | 显示 Owl 摄像机通道,AdapterCode=Owl:main | +| 前端预览按钮 | 通道子设备显示"预览"按钮,点击播放实时流 | +| GET /api/gateway/alarms/Owl:main | 返回 AI 检测事件(人员/车辆等) | +| 规则引擎 | 可将 Owl AI 事件作为告警源触发规则 | +| POST /api/gateway/control/Owl:main ai-enable | 远程开启 Owl AI 检测 |