对接指南
一、交互流程总览
二、设备端接入(GB28181 IPC / NVR)
国标监控是协议接入,设备端通常无需开发,只在 web 管理界面填写 SIP 参数即可。
1. 平台准备(运维操作)
通过平台管理后台或运维直连数据库,为目标租户创建一条 SIP 平台配置:
| 字段 | 含义 |
|---|---|
server_id | 平台 SIP 服务器 ID(国标 20 位编码) |
domain | SIP 域(国标 10 位编码) |
password | 设备 Digest 鉴权默认密码 |
2. 设备配置(以海康 IPC 为例)
| 字段 | 填写值 |
|---|---|
| SIP 服务器 ID | 与平台 server_id 一致 |
| SIP 服务器域 | 与平台 domain 一致 |
| SIP 服务器地址 | 平台公网 IP / 域名 |
| SIP 服务器端口 | 5060 |
| SIP 用户名 | 设备国标编号(20 位) |
| SIP 用户认证 ID | 同上 |
| 密码 | 与平台 password 一致 |
| 注册有效期 | 3600(秒) |
| 心跳周期 | 60(秒) |
| 心跳超时次数 | 3 |
大华 / 宇视 / 华为 / 天地伟业等设备的字段名略有差异,但参数语义一致。配置完成保存重启,设备会自动 REGISTER。
3. 验证接入成功
接入成功后:
- 平台日志会出现
device registered信息(含 deviceId / tenantId / ip / port / transport / expires) - 设备表
online_status变为ONLINE,register_time与keepalive_time持续更新
接入失败常见原因:
- 设备 SIP 参数与平台配置不匹配
- 设备 IP 无法连通平台 5060 端口(防火墙 / NAT)
- 密码错误(查后端 401 日志)
三、应用端接入(OpenAPI)
国标监控模块对外提供完整 REST 接口,统一前缀 /blade-iot/vms/。
OpenAPI 一览
| 资源 | 路径 | 主要端点 |
|---|---|---|
| 平台 | /platform | submit / detail / page / sip-info(只读回填 SIP 监听信息) |
| 媒体节点 | /media-node | save / remove / detail / page(租户级 ZLM 节点 + 自动负载) |
| 设备 | /device | save(手动注册)/ detail / page / sync-catalog / remove / query-info / query-status / query-config / query-result / sync-time / reboot / record-start / record-stop / subscribe / unsubscribe |
| 通道 | /channel | detail / page / list-by-device / reset-civil-code / reset-parent-id |
| 流会话 | /stream | start(+streamType) / stop / detail / page |
| 回放 | /playback | records/query / records/result / start / control(PLAY/PAUSE/SCALE/SEEK) |
| 云台 | /ptz | control / stop / preset(call/set/remove) |
| 抓图 | /snapshot | snapshot / detail / page / url(返回 OSS 外链 JSON,前端优先)/ file(302 重定向 OSS) |
| 录像 | /record | download / download/page / download/url(外链 JSON,前端优先)/ download/file(302)/ plan/save / plan/remove / plan/page / archive/page / archive/url(外链 JSON,前端优先)/ archive/file(302) |
| 分组 | /group | save / remove / tree / assign / unassign / channels |
| 预置位 | /preset | query / list |
| 级联 | /cascade | save / remove / detail / page / register / unregister |
| 流媒体 Webhook | /webhook | on_publish / on_stream_changed / on_stream_none_reader / on_server_keepalive(节点心跳)(流媒体回调,启动期强制 hook-secret) |
1. 启动实时点播
POST /blade-iot/vms/stream/start
Content-Type: application/json
{
"channelId": "34020000001320000003",
"streamType": "main"
}响应:
{
"code": 200,
"data": {
"sessionId": "5e7b2c8a-...",
"ssrc": "0345670001",
"sessionStatus": "WAITING"
}
}前端轮询 GET /stream/detail?sessionId=...,sessionStatus=RUNNING 后取 wsFlvUrl 用 flv.js 播放。
2. PTZ 控制
POST /blade-iot/vms/ptz/control?channelId=34020...&action=RIGHT&speed=100支持 7 种动作:UP / DOWN / LEFT / RIGHT / ZOOM_IN / ZOOM_OUT / STOP,速度 0-255(0=停止,默认 100)。
停止:POST /blade-iot/vms/ptz/stop?channelId=...(等效 action=STOP)。 预置位:POST /blade-iot/vms/ptz/preset/call / set(@RequestBody preset 实体) / remove。
为什么不支持斜向 8 方向
对角线方向(UP_LEFT 等)在 GB28181-2016 附录 A.3 的 8 字节控制码里需要同时置位水平 + 垂直 bit,实际海康 / 大华 / 宇视主流固件对组合方向的响应不一致,业内普遍采用前端拆成两路单方向指令的折中方案。本实现协议层只保留 4 个正交方向,前端组合需要时分别下发。
3. 异步查询设备(轮询模式)
POST /blade-iot/vms/device/query-info?deviceId=34020...
→ 返回 { "sn": 12345 }
GET /blade-iot/vms/device/query-result?sn=12345
→ pending: { "code": 200, "data": null }
→ ready: { "code": 200, "data": { "manufacturer": "...", "model": "...", ... } }前端典型实现:1s 轮询,最多 30 次(30s 超时)。响应在 vms.query.cache-ttl-ms(默认 60s)内可重复读取,避免轮询窗口期内缓存被踢出。
4. 抓图 + 下载
POST /blade-iot/vms/snapshot/snapshot?channelId=...
→ 返回抓图记录 { id, storageUrl, fileSize }
GET /blade-iot/vms/snapshot/url?id=xxx ← 推荐:返回 { "url": "<OSS 外链>" } JSON
→ 前端拿外链直接作为 <img> src 显示 / window.open 下载
GET /blade-iot/vms/snapshot/file?id=xxx
→ HTTP 302 重定向到 OSS 外链(平台不中转流量)前端优先用 `*/url` 而非 `*/file`
*/file 端点 302 跳转到 OSS。若前端用 XHR / blob 直拉,浏览器会自动跟随 302 并把平台 Authorization 头一并发往第三方 OSS 域(凭证泄漏)。抓图(/snapshot/url)、录像下载(/record/download/url)、录像归档(/record/archive/url)均提供返回 JSON 外链的 */url 端点;前端拿外链后直接作为图片 src 显示或 window.open 跳转下载,不对 OSS 域发起带平台凭证的请求。
5. 录像计划
POST /blade-iot/vms/record/plan/save
{
"channelId": "34020...",
"cronExpression": "0 0 0 ? * SAT",
"durationSeconds": 28800,
"retainDays": 30,
"enabled": 1
}平台后台任务 VmsRecordPlanExecutorTask 每 60s 扫描 next_fire_time ≤ now 的计划。命中后阶段 1 同步起回放 → 等流就绪 → ZLM startRecord;阶段 2 异步由 executor.schedule 在 duration+5s 后回调 → stopRecord → MP4 上传 OSS → 删本地临时文件 → 归档行转 DONE。
6. 开启订阅(增量推送 + GPS)
POST /blade-iot/vms/device/subscribe?deviceId=...&type=CATALOG
POST /blade-iot/vms/device/subscribe?deviceId=...&type=ALARM
POST /blade-iot/vms/device/subscribe?deviceId=...&type=POSITION&intervalSec=60订阅过期前 1h 自动续约,优先走 in-dialog SUBSCRIBE refresh(RFC 6665 §4.1.2.1),失败回退为新建订阅。设备 REGISTER 时自动重发该设备所有 ACTIVE 订阅,避免设备视角订阅丢失。
四、关键配置项
vms:
enabled: true # 模块总开关:false 则不绑 SIP 端口 / 不起定时器 / 不加载 ZLM
gb28181:
sip:
ip: 0.0.0.0 # SIP 监听绑定地址(全局单端口,多租户按 server-id 区分)
sip-host: ${VMS_SIP_HOST} # 对外信令 IP(写入出站 Via/Contact,设备据此回送)
port: 5060
server-id: ${VMS_SERVER_ID} # 平台 SIP 服务器 ID(20 位)
domain: ${VMS_SIP_DOMAIN} # SIP 域(10 位)
password: ${VMS_SIP_PASSWORD} # 平台默认 Digest 密码(设备可配独立密码覆盖)
register-expire: 3600
keepalive-interval: 60
transport: BOTH # UDP / TCP / BOTH
charset: GB2312 # 国标默认,部分上级要 UTF-8
default-tenant-id: "000000"
thread-pool-size: 50
mobile-position-event: presence # 默认 presence,海康老固件可改 "Catalog;id=1" 或 "MobilePosition"
# ⚠️ 以下 zlm 配置自 v2.7 起仅作「默认节点种子」:启动时植入默认租户 000000 的媒体节点表。
# 运行期 ZLM 节点以 iot_vms_media_node 表为准,各租户在【节点配置】页面自助增删改;
# 多节点按并发自动负载、按 node-id 绑定会话,无需在此配置多个节点(详见「配置详解」)。
zlm:
node-id: node-1 # 须与 ZLM general.mediaServerId 一致
host: ${ZLM_HOST} # 平台访问 ZLM REST API 的 IP
public-host: ${ZLM_PUBLIC_HOST} # ⚠️ 浏览器拉流地址,启动期强制非空(dev profile 例外)
fail-on-missing-public-host: true
http-port: 80
internal-http-port: 0 # docker NAT 部署填容器内端口,0 回退 http-port
sdp-ip: ${ZLM_SDP_IP} # 对设备暴露的 RTP 媒体 IP,空回退 host
rtmp-port: 1935
secret: ${ZLM_SECRET} # ZLM HTTP API secret
hook-secret: ${ZLM_HOOK_SECRET} # webhook 校验密钥,启动期 fail-fast 强制非空
allowed-hook-ips: 10.0.0.5,10.0.0.6 # 可选,CSV webhook 源 IP 白名单
waiting-ttl-seconds: 90 # WAITING 短超时(INVITE 已发但未收到 RTP)
session-ttl-seconds: 7200 # 流活跃 TTL,GC 与 CLOSE_PENDING 转 EXPIRED 都依据
session-retain-days: 3 # 终态流会话历史保留天数,超期由清理任务删除
tcp-mode: true # NAT 场景建议 true(可在节点表按节点覆盖)
snapshot:
oss-path-prefix: vms/snapshot
expire-days: 7
auto-stream-timeout-ms: 5000
settle-delay-ms: 1000
record:
zlm-temp-dir: /data/blade-vms/record-tmp # ZLM 与平台共享卷
oss-path-prefix: vms/record
archive-retain-days: 30
download-retain-days: 7
download-concurrency: 4
download-speed: 4 # 录像下载倍速(SDP a=downloadspeed),1=实时速率
plan-executor-pool: 2
max-range-seconds: 21600 # 6h 上限
stream:
retry:
batch-size: 200 # CLOSE_PENDING 单次扫描批量
max-rounds: 50 # 单次调度最多 50 轮
max-fail-per-session: 5 # 单会话失败 5 次转 EXPIRED
query:
cache-ttl-ms: 60000 # 异步查询结果缓存 TTL
oss:
enabled: true
name: minio # minio / aliyun / qcloud / huawei / qiniu
tenant-mode: false
endpoint: ${OSS_ENDPOINT}
access-key: ${OSS_ACCESS_KEY}
secret-key: ${OSS_SECRET_KEY}
bucket-name: vms-prod五、流媒体部署要点
| 项 | 要点 |
|---|---|
| 部署形态 | 单节点或多节点;节点以 iot_vms_media_node 表为准,按租户隔离、按并发自动负载(详见「配置详解」) |
| 节点编码绑定 | 每个 ZLM 的 general.mediaServerId 必须等于其在媒体节点表中的「节点编码」node-id,否则心跳与会话回查命中不到节点 |
| 与平台关系 | 流媒体独立进程,平台通过 REST API + Hook 双向通信 |
| RTP 端口范围 | 由流媒体自身配置(config.ini 的 rtp_proxy 端口段)管理,平台 openRtpServer 用 port=0 让流媒体自动分配,不下发端口段 |
| Hook 配置 | 流媒体配置文件配置四个回调 on_publish / on_stream_changed / on_stream_none_reader / on_server_keepalive,均指向 http://平台地址/blade-iot/vms/webhook/<hook 名>?secret=<hook-secret> |
| Hook 安全 | 必须设置 hook-secret,平台启动期 fail-fast 校验非空;可选 allowed-hook-ips CSV 进一步限制源 IP |
| 共享卷 | 录像计划临时 MP4 需流媒体写 + 平台读,挂载同一路径 |
| NAT / 防火墙 | RTP 端口段必须开放;public-host 是浏览器拉流域名 |
六、监控指标
平台暴露 6 个 Prometheus 指标,供运维监控:
| 指标 | 类型 | 健康基线 | 异常排查 |
|---|---|---|---|
vms_stream_active_total | Gauge(按 invite/playback 分) | 与活跃流数一致 | 不一致检查 GC / Retry 任务 |
vms_stream_gc_pending_total | Gauge | 长期为 0 | 持续 > 0 = 流媒体关流失败 |
vms_subscription_active_total | Gauge(memory/db 双视角) | 一致 | 不一致 = 进程重启后未完整恢复 |
vms_zlm_open_fail_total | Counter | 增量为 0 | 增量大 = 流媒体端口耗尽或不可达 |
vms_sip_msg_total | Counter(direction / method) | 持续增长 | 长时间不增 = SIP 端口被阻断 |
vms_catalog_partial_timeout_total | Counter | 增量为 0 | 增量大 = Catalog 分批 NOTIFY 长期收不齐,触发兜底超时聚合 |
七、事件总线(进程内 Spring 事件)
告警与设备状态走进程内 Spring 事件,不依赖 Kafka 等外部消息队列。告警先经 VmsAlarmUplinkAdapter 写入告警引擎(EngineAlarmEntity / IEngineAlarmService),事务提交后(AFTER_COMMIT)由 ApplicationEventPublisher 发布 VmsAlarmPersistedEvent;设备上下线发布 DeviceOnlineEvent / DeviceOfflineEvent。下游(告警引擎联动、WebSocket 大屏推送等)用 @EventListener / @TransactionalEventListener 在同进程消费。
| 事件 | 触发 | 典型消费者 |
|---|---|---|
VmsAlarmPersistedEvent | 告警写入告警引擎,事务提交后 AFTER_COMMIT 发布 | 告警引擎联动、WebSocket 大屏推送 |
DeviceOnlineEvent | 设备注册 / 心跳判定上线 | 在线状态联动、大屏推送 |
DeviceOfflineEvent | 心跳超时 / 注销判定下线 | 离线状态联动、大屏推送 |
告警事件 pushData 示例:
{
"deviceId": "34020000001320000003",
"channelId": "34020000001320000003",
"alarmTime": "2026-05-26T10:00:00",
"alarmMethod": "5",
"alarmType": "6",
"eventType": "1",
"priority": 4,
"description": "区域入侵",
"longitude": 116.40739,
"latitude": 39.9042
}八、调试与排查
设备无法注册
| 检查项 | 排查方法 |
|---|---|
| 设备配置匹配 | 设备 web 界面 server-id / domain / port / password 与平台配置对齐 |
| 401 循环 | 检查 password 是否一致 |
| qop=auth 不兼容(级联场景) | 上级 401 携带 qop="auth",平台已支持自动适配;若仍失败抓包看 Authorization 头 |
| 防火墙 | 设备 IP → 平台 5060 UDP/TCP 必须连通 |
点播失败
| 检查项 | 排查方法 |
|---|---|
| INVITE 缺 Subject 头 | 严格按国标的设备会拒绝,检查平台 INVITE 报文是否含 Subject |
| 打开 RTP 端口失败 | 端口耗尽(vms_zlm_open_fail_total 增长)/ 流媒体不可达 |
| 设备 200 OK 后无 RTP | NAT 场景流媒体 public-host 配置错误,设备收到错误地址 |
| Hook 不到达 | 流媒体配置 hook URL 错 / 防火墙拦截 |
| 主子码流走错 | 通道不支持子码流时启动直播应传 main |
Catalog 同步缺通道
| 检查项 | 排查方法 |
|---|---|
| 分批 NOTIFY 丢包 | UDP 弱网常见,等 30s 兜底自动完成;持续丢包改 TCP 传输 |
| 设备 SumNum 不准 | 极个别厂商 bug,兜底按已收 items 完成 |
| 订阅模式 Event 字段未识别 | 平台已支持 ADD / DEL / UPDATE / ON / OFF / VLOST / DEFECT,检查报文是否真有 <Event> 节点 |
MobilePosition 收不到 NOTIFY
| 检查项 | 排查方法 |
|---|---|
| 设备无 GPS 硬件 | 固定摄像头 ≠ 移动单兵 / 车载,无 GPS 永远无响应 |
| Event 头不兼容 | 海康部分老固件用 Catalog;id=xx,改 vms.gb28181.sip.mobile-position-event |
| Interval 太长 | 默认 60s 过长,首次订阅后 60s 才有第一条;改小观察 |
| 经纬度被拒 | WGS84 合理性校验拒绝越界坐标,看后端日志 |
CLOSE_PENDING 持续累积
| 检查项 | 排查方法 |
|---|---|
| 流媒体健康 | 流媒体进程是否在跑 / 关流接口是否返回成功 |
| 转 EXPIRED 是否生效 | 累积超 TTL 或单会话失败 5 次会转 EXPIRED,检查 vms_stream_gc_pending_total |
上传 OSS 失败
| 检查项 | 排查方法 |
|---|---|
| OSS 配置 | endpoint / access-key / secret-key / bucket-name 是否正确 |
| 网络 | 平台是否能访问 OSS endpoint |
| 临时目录 | 流媒体与平台共享卷是否可写可读 |
九、约束与限制
| 项 | 限制 | 说明 |
|---|---|---|
| 视频时长 | 单次录像 ≤ 6h | vms.record.max-range-seconds=21600 |
| 抓图频率 | 推荐 ≤ 1 次/秒/通道 | 高频会导致流媒体性能下降 |
| 字符集 | 默认 GB2312 | 部分上级要求 UTF-8,可改 vms.gb28181.sip.charset |
| 多租户 | 每租户 1 个 SIP 平台 + N 个 ZLM 媒体节点 | SIP 端口全局共享,按 server-id 区分租户;媒体节点按租户隔离 |
| 设备接入 | 自动接入(AUTO) / 手动注册(MANUAL 白名单) | 平台级 register-mode 决定;MANUAL 仅放行已登记设备,详见「操作手册」 |
