优化文档, 优化notify-catalog大并发处理

pull/1642/head
648540858 2024-10-11 15:24:46 +08:00
parent 028274a088
commit 3078162c58
45 changed files with 842 additions and 392 deletions

View File

@ -3,11 +3,15 @@
> 开箱即用的28181协议视频平台
# 概述
- WVP-PRO基于GB/T 28181-2016标准实现的流媒体平台依托优秀的开源流媒体服务[ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit),提供完善丰富的功能。
- WVP-PRO基于GB/T
28181-2016标准实现的流媒体平台依托优秀的开源流媒体服务[ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit)
,提供完善丰富的功能。
- GB/T 28181-2016 中文标准名称是《公共安全视频监控联网系统信息传输、交换、控制技术要求》是监控领域的国家标准。大量应用于政府视频平台。
- 通过28181协议你可以将IPC摄像头接入平台可以观看也可以使用28181/rtsp/rtmp/flv等协议将视频流分发到其他平台。
# 特性
- 实现标准的28181信令兼容常见的品牌设备比如海康、大华、宇视等品牌的IPC、NVR以及平台。
- 支持将国标设备级联到其他国标平台,也支持将不支持国标的设备的图像或者直播推送到其他国标平台
- 前端完善,自带完整前端页面,无需二次开发可直接部署使用。
@ -15,92 +19,94 @@
- 支持多流媒体节点负载均衡。
# 付费社群
[![社群](_media/shequ.png "shequ")](https://t.zsxq.com/0d8VAD3Dm)
> 收费是为了提供更好的服务,也是对作者更大的激励。加入星球的用户三天后可以私信我留下微信号,我会拉大家入群。加入三天内不满意可以直接退款,大家不需要有顾虑,来白嫖三天也不是不可以。
# 我们实现了哪些国标功能
**作为上级平台**
- [X] 注册
- [X] 注销
- [X] 实时视音频点播
- [X] 设备控制
- [X] 云台控制
- [X] 远程启动
- [X] 录像控制
- [X] 报警布防/撤防
- [X] 报警复位
- [X] 强制关键帧
- [X] 拉框放大
- [X] 拉框缩小
- [X] 看守位控制
- [X] 设备配置
- [X] 云台控制
- [X] 远程启动
- [X] 录像控制
- [X] 报警布防/撤防
- [X] 报警复位
- [X] 强制关键帧
- [X] 拉框放大
- [X] 拉框缩小
- [X] 看守位控制
- [X] 设备配置
- [X] 报警事件通知和分发
- [X] 设备目录订阅
- [X] 网络设备信息查询
- [X] 设备目录查询
- [X] 设备状态查询
- [X] 设备配置查询
- [X] 设备预置位查询
- [X] 设备目录查询
- [X] 设备状态查询
- [X] 设备配置查询
- [X] 设备预置位查询
- [X] 状态信息报送
- [X] 设备视音频文件检索
- [X] 历史视音频的回放
- [X] 播放
- [X] 暂停
- [X] 进/退
- [X] 停止
- [X] 播放
- [X] 暂停
- [X] 进/退
- [X] 停止
- [X] 视音频文件下载
- [X] 校时
- [X] 订阅和通知
- [X] 事件订阅
- [X] 移动设备位置订阅
- [X] 报警订阅
- [X] 目录订阅
- [X] 事件订阅
- [X] 移动设备位置订阅
- [X] 报警订阅
- [X] 目录订阅
- [X] 语音广播
- [X] 语音喊话
**作为下级平台**
- [X] 注册
- [X] 注销
- [X] 实时视音频点播
- [X] 设备控制
- [X] 云台控制
- [ ] 远程启动
- [X] 录像控制
- [X] 报警布防/撤防
- [X] 报警复位
- [X] 强制关键帧
- [X] 拉框放大
- [X] 拉框缩小
- [X] 看守位控制
- [ ] 设备配置
- [X] 云台控制
- [ ] 远程启动
- [X] 录像控制
- [X] 报警布防/撤防
- [X] 报警复位
- [X] 强制关键帧
- [X] 拉框放大
- [X] 拉框缩小
- [X] 看守位控制
- [ ] 设备配置
- [ ] 报警事件通知和分发
- [X] 设备目录订阅
- [X] 网络设备信息查询
- [X] 设备目录查询
- [X] 设备状态查询
- [ ] 设备配置查询
- [X] 设备预置位查询
- [X] 设备目录查询
- [X] 设备状态查询
- [ ] 设备配置查询
- [X] 设备预置位查询
- [X] 状态信息报送
- [X] 设备视音频文件检索
- [X] 历史视音频的回放
- [X] 播放
- [x] 暂停
- [x] 进/退
- [x] 停止
- [X] 播放
- [x] 暂停
- [x] 进/退
- [x] 停止
- [X] 视音频文件下载
- [ ] ~~校时~~
- [X] 订阅和通知
- [X] 事件订阅
- [X] 移动设备位置订阅
- [ ] 报警订阅
- [X] 目录订阅
- [X] 事件订阅
- [X] 移动设备位置订阅
- [ ] 报警订阅
- [X] 目录订阅
- [X] 语音广播
- [X] 语音喊话
# 社区
代码目前托管在GitHub和GiteeGitee目前作为加速仓库使用不接受issue。
GitHub [https://github.com/648540858/wvp-GB28181-pro](https://github.com/648540858/wvp-GB28181-pro)
Gitee [https://gitee.com/pan648540858/wvp-GB28181-pro](https://gitee.com/pan648540858/wvp-GB28181-pro)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -1,2 +1,3 @@
<!-- 自动点播 -->
# 自动点播

View File

@ -1,34 +1,56 @@
<!-- 国标级联的使用 -->
# 国标级联的使用
国标28181不同平台之间支持两种连接方式平级和上下级WVP目前支持向上级级联。
## 1 接入平台
### 1.1 wvp-pro
#### 1.1.1 wvp-pro管理页面点击添加
![cascade1](_media/cascade1.png)
![cascade1](_media/cascade1.png)
#### 1.1.2 填入wvp-pro上级平台信息
![cascade1](_media/img_4.png)
![cascade1](_media/img_5.png)
![cascade1](_media/img_4.png)
![cascade1](_media/img_5.png)
#### 1.1.3 编辑wvp-pro上级设备信息开启订阅
![cascade1](_media/img_6.png)
![cascade1](_media/img_6.png)
### 1.2 大华平台
### 1.3 海康平台
### 1.4 liveGBS
#### 1.4.1. wvp-pro管理页面点击添加
![添加](_media/cascade1.png)
#### 1.4.2. 填入liveGBS平台信息
![填入liveGBS平台信息1](_media/cascade2.png)
![填入liveGBS平台信息2](_media/cascade3.png)
#### 1.4.3. 编辑liveGBS设备信息开启目录订阅
![cascade1](_media/cascade4.png)
#### 1.4.4. 编辑liveGBS设备信息开启GPS订阅
![cascade1](_media/img_7.png)
#### 1.4.1. wvp-pro管理页面点击添加
![添加](_media/cascade1.png)
#### 1.4.2. 填入liveGBS平台信息
![填入liveGBS平台信息1](_media/cascade2.png)
![填入liveGBS平台信息2](_media/cascade3.png)
#### 1.4.3. 编辑liveGBS设备信息开启目录订阅
![cascade1](_media/cascade4.png)
#### 1.4.4. 编辑liveGBS设备信息开启GPS订阅
![cascade1](_media/img_7.png)
## 2 添加目录与通道
1. 级联平台添加目录信息
![cascade1](_media/img_1.png)
2. 为目录添加通道
![cascade1](_media/img_2.png)
3. 设置默认流目录
如果需要后续自动生成的流信息都在某一个节点下,可以在对应节点右键设置为默认
如果需要后续自动生成的流信息都在某一个节点下,可以在对应节点右键设置为默认
![cascade1](_media/img_3.png)

View File

@ -1,18 +1,71 @@
<!-- 国标级联的使用 -->
# 国标级联的使用
国标28181不同平台之间支持两种连接方式平级和上下级WVP目前支持向上级级联。
## 添加上级平台
在国标级联页面点击“添加”按钮以推送到上级WVP为例子参看[接入设备](./_content/ability/device.md)
![cascade17](_media/img_17.png)
点击保存可以在上级的国标通道列表看到新增加的设备;
1. 名称
上级平台看到的下级平台名称;
2. 本地IP
本地连接上级使用的具体哪个网卡;
3. SIP认证用户名
可以设置为与"设备国标编号"一致;
4. 注册周期
间隔多久发起一次注册,单位秒;
5. 心跳周期
间隔多久发送一次心跳,一般上级平台三次收不到心跳就会认为下级离线了, 所以建议{心跳周期}x3 < 注册周期;
6. SDP发流IP
调用媒体节点发送视频流给上级时,使用的本地IP;
7. 信令传输
信令传输模式,支持udp和TCP,没有特殊需求,默认UDP即可;
8. 目录分组
上级发送"CATALOG"消息查询通道信息,每一条消息中携带几条通道信息,默认为1,增大该值,可以加快通道发送速度;
9. 字符集
发送给上级"MESSAGE"消息中的消息体使用的编码格式,国标28181-2016默认为GB2312;
10. 行政区划
如果勾选"其他选项/推送平台信息"选项,会给上级推送平台信息,这里就是平台的行政区划信息
11. 平台厂商
如果勾选"其他选项/推送平台信息"选项,会给上级推送平台信息,这里就是平台的平台厂商信息
12. 平台型号
如果勾选"其他选项/推送平台信息"选项,会给上级推送平台信息,这里就是平台的平台型号信息
13. 平台安装地址
如果勾选"其他选项/推送平台信息"选项,会给上级推送平台信息,这里就是平台的平台安装地址信息
14. 其他选项
- RTCP保活
在上级的流传输模式为UDP时,因为UDP的无状态特性,会无法知道上级是否在正常收流,启用RTCP保活时,就可以主动发送RTCP消息确认上级是否在正常收流,
异常情况下,可以下级主动停止发流;
- 消息通道
支持通过报警消息给上级级WVP推送消息,消息内容由redis消息发送给wvp,wvp编辑成报警消息发送给上级;
- 主动推送通道
WVP模拟一条目录订阅信息,然后在共享通道变化时,发送CATAOLOG事件给上级,通知具体的通道变化,
目前支持的状态有: 状态改变事件 ON:上线,OFF:离线,VLOST:视频丢失,DEFECT:故障,ADD:增加,DEL:删除,UPDATE:更新;
- 推送平台信息
勾选此项,上级收到的通道信息中会多出一个平台信息的通道.内容在平台的编辑中修改;
- 推送分组信息
勾选此项,如果你共享的通道分配了具体的业务分组以及虚拟组织,那么上级收到的通道中会包括业务分组以及虚拟组织节点信息;
- 推送行政区划
勾选此项,如果你共享的通道分配了具体的行政区划,那么上级收到的通道中会包括行政区划信息;
国标级联列表出现了级联的这个平台;同时状态显示为在线,如果状态为离线那么可能是你的服务信息配置有误或者网络不通。
订阅信息列有三个图标,表示上级开启订阅,从左到右依次是:报警订阅,目录订阅,移动位置订阅。
## 推送通道
点击你要推送的平台的“选择通道”按钮。
## 通道共享
点击你要推送的平台的“通道共享”按钮。
![cascade18](_media/img_18.png)
- **页面结构**
- 左侧为目录结构
选择未分配,则右侧显示待分配的通道,可以点击“添加按钮”,在弹窗中选择要放置的位置,保存后即可添加通道成功
选择其他的目录可以看到已经分配在这个目录下的通道,可以对其进行删除后重新在未分配中去分配。
- 右侧为数据展示以及操作
国标通道栏内为来自其他国标设备/平台的通道;直播流通道为来自推流/拉流代理的通道。
1. 添加状态选择"未共享"可以将具体的通道共享给上级;
2. 添加状态选择"已共享"可以看到已经共享的通道,并且支持为这个通道在这个平台设备专门的名称和编号;
3. 点击"按设备添加"可以将某个国标设备下的所有通道共享给上级;
4. 点击"按设备移除"可以将某个国标设备下的所有通道取消共享给上级;
5. 点击"全部添加"可以将所有通道共享给上级;
6. 点击"全部移除"可以将所有通道共享给上级;
## 推送通道
WVP会将所有通道信息按照目录订阅消息通知形式,发送ADD事件给上级.

View File

@ -1,12 +1,22 @@
# 通道管理
通道管理为了对已经分配国标编号的通道进行统一的行政区划和业务分组管理,国标中对于组织结构有两种表示方式,一种是按照行政区划,一种是业务分组+虚拟组织的方式.
行政区划结构固定,比如: 北京/市辖区/昌平区, 通道可以挂载道何一级行政区划下. 业务分组比较灵活, 可以按照自己的随意取名, 但是通道只能放在业务分组下的虚拟组织里,不能放在业务分组下.
行政区划结构固定,比如: 北京/市辖区/昌平区, 通道可以挂载道何一级行政区划下. 业务分组比较灵活, 可以按照自己的随意取名,
但是通道只能放在业务分组下的虚拟组织里,不能放在业务分组下.
## 行政区划
左侧树结构为行政区划结构, 通过数据鼠标右键可以操作,包括: 刷新节点,新建节点,编辑节点,删除节点,添加设备(可以将某个国标设备下的通道全部添加道某一个节点下),移除设备(可以将某个国标设备下的通道全部从这个节点移除)
右侧伪通道列表, 对于非国标接入的设备只有配置了国标编号后才可以在这里进行操作, 添加状态选择"未添加"可以进行添加操作,选择"已添加"进行移除操作
左侧树结构为行政区划结构, 通过数据鼠标右键可以操作,包括: 刷新节点,新建节点,编辑节点,删除节点,添加设备(
可以将某个国标设备下的通道全部添加道某一个节点下),移除设备(可以将某个国标设备下的通道全部从这个节点移除)
右侧伪通道列表, 对于非国标接入的设备只有配置了国标编号后才可以在这里进行操作, 添加状态选择"未添加"可以进行添加操作,选择"
已添加"进行移除操作
![刷新](_media/img_21.png)
## 业务分组
左侧树结构为业务分组结构, 通过数据鼠标右键可以操作,包括: 刷新节点,新建节点,编辑节点,删除节点,添加设备(可以将某个国标设备下的通道全部添加道某一个节点下),移除设备(可以将某个国标设备下的通道全部从这个节点移除)
左侧树结构为业务分组结构, 通过数据鼠标右键可以操作,包括: 刷新节点,新建节点,编辑节点,删除节点,添加设备(
可以将某个国标设备下的通道全部添加道某一个节点下),移除设备(可以将某个国标设备下的通道全部从这个节点移除)
业务分组下不能挂载设备,所以没有选择该节点的单选框.
右侧伪通道列表, 对于非国标接入的设备只有配置了国标编号后才可以在这里进行操作, 添加状态选择"未添加"可以进行添加操作,选择"已添加"进行移除操作
右侧伪通道列表, 对于非国标接入的设备只有配置了国标编号后才可以在这里进行操作, 添加状态选择"未添加"可以进行添加操作,选择"
已添加"进行移除操作
![刷新](_media/img_22.png)

View File

@ -1,8 +1,13 @@
<!-- 云端录像 -->
# 云端录像
![云端录像](_media/img_23.png)
云端录像是对录制在zlm服务下的录像文件的管理录像的文件路径默认在ZLM/www/record下。
如果你需要24小时的录像目前有一个这种方案可以参考[7*24不间断录像](./_content/ability/continuous_recording.md)。
1. 云段录像支持录像文件的查看,播放(可能因为编码的原因导致无法播放);
2. 支持录像的下载;
3. 支持录像的合并下载;
功能没有太多特殊的地方就不一一介绍了,大家自行体验吧。
- 国标设备是否录像: 可以再WVP的配置中user-settings.record-sip设置为true那么每次点播以及录像回放都会录像;
- 推流设备是否录像: 可以再WVP的配置中user-settings.record-push-live设置为true;
- 拉流代理的是否录像: 在添加和编辑拉流代理时可以指定, 每次点播都会进行录像
- 录像文件存储路径配置: 可以修改media.record-path来修改录像路径,但是如果有旧的录像文件,请不要迁移,因为数据库记录了每一个录像的绝对路径,一旦修改会造成找到文件,无法定时移除以及播放
- 录像保存时间: 可以修改media.record-day来修改录像保存时间,单位是天;

View File

@ -1,9 +1,13 @@
# 语音对讲
## 流程和原理
语音对讲在国标28181-2016中分为broadcast广播和talk对讲两种模式broadcast模式是从服务端把音频传送到设备端是单向的
需要结合点播视频来实现双向对讲talk模式支持双向不过wvp只处理了和broadcast一样的把音频传递设备这样两种模式可以使用一样的逻辑处理即可。
不同的设备对于两种模式的支持不同且通常差异很大不同的设备对同一个设备的支持也有一些不同所以语音对讲中的兼容和适配也是问题最多的。talk模式因为在国标28181-2022中已经移除所以这里不再讨论它了。
### 1. broadcast模式流程
```plantuml
@startuml
"WVP-PRO" -> "设备": 语音广播通知
@ -16,24 +20,33 @@
"ZLMediaKit" -> "设备": 向设备发送语音流
@enduml
```
与点播的流程不同的是这里的invite消息是由设备发送给wvp的wvp按照invite协商的方式给设备推送语音流所有对讲的使用那种方式UDP/TCP被动/TCP主动传输语音流由设备决定
## 使用条件与限制
因为invite消息是由设备发送给wvp的这决定了发送语音流的方式这也就决定了有的设备不能用于公网对讲比如大部分的海康设备只支持udp方式收流(目前新版的海康设备已经在着手解决这个问题)那么wvp发流时只能按照sdp中指定的ip端口发流所以如果wvp在公网设备在内网中那么wvp无法连接设备提供的IP发流也就失败了。
因为invite消息是由设备发送给wvp的这决定了发送语音流的方式这也就决定了有的设备不能用于公网对讲比如大部分的海康设备只支持udp方式收流(
目前新版的海康设备已经在着手解决这个问题)那么wvp发流时只能按照sdp中指定的ip端口发流所以如果wvp在公网设备在内网中那么wvp无法连接设备提供的IP发流也就失败了。
与海康不同的大华以及很多执法记录仪厂商是支持tcp主动方式取流的这样是可以实现公网对讲的。
## 使用ffmpeg快速测试
由于浏览器对于音频的采集需要网页支持https才可以所以如果想要实现网页音频对讲那么你必须给wvp和zlm配置证书以使用https。
测试阶段如果只是想测试功能可以用ffmpeg来模拟语音流推送到wvp后可以实现把音频文件推送到摄像头。
测试命令格式如下:
```shell
ffmpeg -re -i {音频文件} -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp 'rtsp://{zlm的IP}:{zlm的RTSP端口}/broadcast/{设备国标编号}_{通道国标编号}?sign={md5(pushKey)}'
```
例如
```shell
ffmpeg -re -i test.mp3 -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp 'rtsp://192.168.1.3:22554/broadcast/34020000001320000001_34020000001320000001?sign=41db35390ddad33f83944f44b8b75ded'
```
测试流程如下:
```plantuml
@startuml
"FFMPEG" -> "ZLMediaKit": 推流到zlm
@ -44,33 +57,48 @@ ffmpeg -re -i test.mp3 -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp 'rtsp://192.168.1
"ZLMediaKit" -> "设备": 向设备推流
@enduml
```
如果听到设备播放你推送的音频,那么意味着调用成功,此过程推流即可需要调用任何接口
## 生产环境网页发起语音对讲
生产环境下使用语音对讲如果是自己的客户端设备那么直接上面的ffmpeg测试方式按照固定格式推流到zlm即可。
对于WEB程序主要是局域网和公网的区别两个原因
1. 很多设备不支持公网对讲
2. 公网和局域网获取证书实现https支持的方式不同
### 公网使用
公网你可以直接使用证书厂商或者云服务器厂商提供的证书,这是很方便的。
### 局域网使用
局域网你需要为wvp和zlm生成自签名证书这里我推荐一种生成自签名证书相对方便的方式,
此方式为linux下的一种方式。
下载证书生成工具:
[https://github.com/FiloSottile/mkcert/releases/tag/v1.4.4](https://github.com/FiloSottile/mkcert/releases/tag/v1.4.4)
安装此工具, 进入解压的工具目录,执行
```shell
./mkcert-v1.4.4-linux-amd64 -install
```
生成pem证书
```shell
./mkcert-v1.4.4-linux-amd64 局域网IP 局域网IP2 局域网IP3
```
你会得到两文件*-key.pem和*.pem, 此文件配置到wvp后既可实现证书的加载
生成zlm使用的证书
```shell
cat *.pem *-key.pem> ./zlm.pem
```
得到的文件就是可以给zlm使用的证书
zlm下使用证书有两种方式
1. 替换zlm下的default.pem, 即删除此文件并把zlm.pem重命名为default.pem
2. 在启动zlm的使用添加 `-s zlm.pem`

View File

@ -1,14 +1,16 @@
<!-- 7*24不间断录像 -->
# 7*24不间断录像
目前如果要实现不间断录像如果只是关闭无人观看停止推流是不够的,设备可能经历断网,重启,都会导致录像的中断,目前给大家提供一种可用的临时方案。
目前如果要实现不间断录像如果只是关闭无人观看停止推流是不够的,设备可能经历断网,重启,都会导致录像的中断,目前给大家提供一种可用的临时方案。
**原理:** wvp支持使用流地址自动点播即你拿到一个流地址直接去播放即使设备处于未点播状态wvp会自动帮你点播ZLM
的拉流代理成功后会无限重试,只要流一恢复就可以拉起来,基于这两个原理。
**方案如下:**
1. wvp的配置中user-settings->auto-apply-play设置为团true,开启自动点播;
2. 点击你要录像的通道点击播放页面左下角的“更多地址”点击rtsp此时复制了rtsp地址到剪贴板
3. 在拉流代理中添加一路流,地址填写你复制的地址,启用成功即可。
**前提:**
**前提:**
1. wvp使用多端口收流不然你无法得到一个固定的流地址也就无法实现自动点播。

View File

@ -1,54 +1,68 @@
<!-- 接入设备 -->
# 接入设备
## 国标28181设备
设备接入主要是需要在设备上配置28181上级也就是WVP-PRO的信息只有信息一致的情况才可以注册成功。设备注册成功后打开WVP->国标设备,可以看到新增加的设备;[设备使用](./_content/ability/device_use.md)
主要有以下字段需要配置:
设备接入主要是需要在设备上配置28181上级也就是WVP-PRO的信息只有信息一致的情况才可以注册成功。设备注册成功后打开WVP->
国标设备,可以看到新增加的设备;[设备使用](./_content/ability/device_use.md)
主要有以下字段需要配置:
- sip->port
28181服务监听的端口
28181服务监听的端口
- sip->domain
domain宜采用ID统一编码的前十位编码。
domain宜采用ID统一编码的前十位编码。
- sip->id
28181服务ID
28181服务ID
- sip->password
28181服务密码
28181服务密码
- 配置信息在如下位置
- 配置信息在如下位置
![_media/img_16.png](_media/img_16.png)
***
### 1. 大华摄像头
![_media/img_10.png](_media/img_10.png)
### 2. 大华NVR
![_media/img_11.png](_media/img_11.png)
### 3. 艾科威视摄像头
![_media/img_15.png](_media/img_15.png)
### 4. 水星摄像头
![_media/img_12.png](_media/img_12.png)
### 5. 海康摄像头
![_media/img_9.png](_media/img_9.png)
## 直播推流设备
这里以obs推流为例,很多无人机也是一样的,设置下推流地址就可以接入了
1. 从wvp获取推流地址, 选择节点管理菜单,查看要推流的节点;
![_media/img_19.png](_media/img_19.png)
2. 拼接推流地址
得到的rtsp地址就是: rtsp://{流IP}:{RTSP PORT}/{app}/{stream}
得到的rtmp地址就是: rtsp://{流IP}:{RTMP PORT}/{app}/{stream}
其中流IP是设备可以连接到zlm的IP,端口是对应协议的端口号, app和stream自己定义就可以.
得到的rtsp地址就是: rtsp://{流IP}:{RTSP PORT}/{app}/{stream}
得到的rtmp地址就是: rtsp://{流IP}:{RTMP PORT}/{app}/{stream}
其中流IP是设备可以连接到zlm的IP,端口是对应协议的端口号, app和stream自己定义就可以.
3. 增加推流鉴权信息
wvp默认开启推流鉴权,拼接好的地址是不能直接推送的,会被返回鉴权失败,参考[推流规则](_content/ability/push?id=推流规则)
wvp默认开启推流鉴权,拼接好的地址是不能直接推送的,会被返回鉴权失败,参考[推流规则](_content/ability/push?id=推流规则)
4. 推流成功后可以再推流列表中看到推流设备,可以播放
此方式只支持设备实时流的播放,无其他功能, 推流信息在推流结束后会自动移除,在列表里就看不到了,如果需要推流信息需要为设备配置国标编号,这样才可以作为wvp的一个永久通道存在.
此方式只支持设备实时流的播放,无其他功能, 推流信息在推流结束后会自动移除,在列表里就看不到了,如果需要推流信息需要为设备配置国标编号,这样才可以作为wvp的一个永久通道存在.
## 接入非国标IPC设备或者其他流地址形式的设备
这类设备的接入主要通过拉流代理的方式接入,原理就是zlm主动像播放器一样拉取这个流缓存在自己服务器供其他人播放.可以解决源设备并发访问能力差的问题.
在拉流代理/添加代理后可以直接播放, 拉流代理也是同样只支持播放当前配置的流.
[设备使用](_content/ability/device_use.md)

View File

@ -1,11 +1,20 @@
<!-- 设备使用 -->
# 国标设备
### 更新设备通道
点击列表末尾的“刷新”按钮,可以看到一个圆形进度条,等进度结束提示成功后即可更新完成,如果通道数量有变化你可以看点击左上角的![刷新](_media/img_14.png)即可看到通道数量的变化如果通道数量仍未0,那么可能时对方尚未推送通道给你。
### 查看设备通道
点击列表末尾的“通道”按钮,
### 更新设备通道
点击列表末尾的“刷新”按钮,可以看到一个圆形进度条,等进度结束提示成功后即可更新完成,如果通道数量有变化你可以看点击左上角的![刷新](_media/img_14.png)
即可看到通道数量的变化如果通道数量仍未0,那么可能时对方尚未推送通道给你。
### 查看设备通道
点击列表末尾的“通道”按钮,
### 编辑设备
点击列表末尾的“编辑”按钮,即可在打开的弹窗中对设备功能进行修改
- 设备名称
如何未能从设备里读取到设备名称或者需要自己重命名,那么可以修改此选项。
- 密码
@ -27,13 +36,27 @@
wvp支持通过报警消息给下级WVP互相推送消息,消息内容由redis消息发送给wvp,wvp编辑成报警消息发送给下级
- 收到ACK后发流
语音对讲策略: 不同的设备对于语音对讲的收流时机要求不一,勾选后会在收到设备发送的ack后再开始发流,不勾选则在回复200OK后开始发流,目前已知大华设备不勾选,海康需要勾选.
### 删除设备
可以删除WVP中的设备信息如果设备28181配置未更改那么设备在下一次注册后仍然会注册上来。
### 点播视频
进入通道列表后,点击列表末尾的“播放”按钮,稍等即可弹出播放页面
### 设备录像
进入通道列表后,点击列表末尾的“设备录像”按钮,也可以在播放页面点击录像查询进入录像查看页面,选择要查看的日期即可对录像进行播放和下载。
### 云台控制
可以对支持云台功能的设备进行上下左右的转动以及拉近拉远的操作。
### 获取视频的播放器地址
视频点播成功后在实时视频页面点击“更多地址”可以看到所有的播放地址地址是否可以播放与你是否完整编译启用zlm功能有关更与网络有关。
### 删除设备
可以删除WVP中的设备信息如果设备28181配置未更改那么设备在下一次注册后仍然会注册上来。
### 点播视频
进入通道列表后,点击列表末尾的“播放”按钮,稍等即可弹出播放页面
### 设备录像
进入通道列表后,点击列表末尾的“设备录像”按钮,也可以在播放页面点击录像查询进入录像查看页面,选择要查看的日期即可对录像进行播放和下载。
### 云台控制
可以对支持云台功能的设备进行上下左右的转动以及拉近拉远的操作。
### 获取视频的播放器地址
视频点播成功后在实时视频页面点击“更多地址”可以看到所有的播放地址地址是否可以播放与你是否完整编译启用zlm功能有关更与网络有关。
### 语音对讲
[语音对讲](_content/ability/continuous_broadcast.md)

View File

@ -1,20 +1,26 @@
<!-- 电子地图 -->
# 电子地图
WVP提供了简单的电子地图用于设备的定位以及移动设备的轨迹信息电子地图基于开源的地图引擎openlayers开发。
### 查看设备定位
1. 可以在设备列表点击“定位”按钮,自动跳转到电子地图页面;
2. 在电子地图页面在设备上右键点击“定位”获取设备/平台下的所有通道位置。
3. 单击通道信息可以定位到具体的通道
# 电子地图
WVP提供了简单的电子地图用于设备的定位以及移动设备的轨迹信息电子地图基于开源的地图引擎openlayers开发。
### 查看设备定位
1. 可以在设备列表点击“定位”按钮,自动跳转到电子地图页面;
2. 在电子地图页面在设备上右键点击“定位”获取设备/平台下的所有通道位置。
3. 单击通道信息可以定位到具体的通道
### 查询设备轨迹
查询轨迹需要提前配置save-position-history选项开启轨迹信息的保存目前WVP此处未支持分库分表对于大数据量的轨迹信息无法胜任有需求请自行二次开发或者定制开发。
在电子地图页面在设备上右键点击“查询轨迹”获取设备轨迹信息。
PS 目前的底图仅用用作演示和学习,商用情况请自行购买授权使用。
### 更换底图以及底图配置
目前WVP支持使用了更换底图配置文件在web_src/static/js/config.js请修改后重新编译前端文件。
```javascript
window.mapParam = {
// 开启/关闭地图功能

View File

@ -1,9 +1,20 @@
<!-- 节点管理 -->
# 节点管理
![节点管理](_media/img_23.png)
WVP支持单个WVP多个ZLM的方案来扩展WVP的视频并发能力并发点播是因为带宽和性能的原因单个ZLM节点能支持的路数有限所以WVP增加了ZLM集群来扩展并发并且保证ZLM的高可用。
## 默认节点
WVP中为了保证功能的完整性ZLM节点至少要有一个默认节点这个节点不是在管理页面添加的而是在WVP的配置文件中配置的这个节点不可在页面删除。每次启动会自动从配置文件中读取配置写入数据库备用。
## 新增节点
启动你要添加的zlm节点然后点击“添加节点”按钮输入zlm的ip http端口SECRET。点击测试测试完成则开始对节点进行详细的设置如果你的zlm是使用docker启动的可能存在zlm使用的端口与宿主机端口不一致的情况需要在这里一一配置。
启动你要添加的zlm节点然后点击“添加节点”按钮输入zlm的ip
http端口SECRET。点击测试测试完成则开始对节点进行详细的设置如果你的zlm是使用docker启动的可能存在zlm使用的端口与宿主机端口不一致的情况需要在这里一一配置。
## wvp使用多个节点的原理
wvp会把连接的节点统一记录在redis中并记录zlm的负载情况当新的请求到来时会取出负载最低的那个zlm进行使用。以此保证节点负载均衡。

View File

@ -1,2 +1,3 @@
<!-- 在线文档 -->
# 在线文档

View File

@ -1,12 +1,17 @@
<!-- 拉流代理 -->
# 拉流代理
不是所有的摄像机都支持国标或者推流的但是这些设备可以得到一个视频播放地址通常为rtsp协议
以大华为例:
```text
rtsp://{user}:{passwd}@{ipc_ip}:{rtsp_port}/cam/realmonitor?channel=1&subtype=0
```
可以得到这样一个流地址可以直接用vlc进行播放此时我们可以通过拉流代理功能将这个设备推送给其他国标平台了。
流程如下:
```plantuml
@startuml
"摄像机" <- "ZLMediaKit": 1. ZLM
@ -15,10 +20,13 @@ rtsp://{user}:{passwd}@{ipc_ip}:{rtsp_port}/cam/realmonitor?channel=1&subtype=0
"WVP-PRO" -> "ZLMediaKit": 4. 通知推流到上级国标平台
@enduml
```
## 添加代理
拉流代理支持两种方式:
1. ZLM中直接代理流支持RTSP/RTMP不支持转码
2. 借助ffmpeg完成拉转可以通过修改ffmpeg拉转参数完成转码。
点击页面的“添加代理”,添加信息后保存即可,如果你需要共享推流信息到其他国标平台,那么你需要编辑/国标通道配置,配置国标编码.
点击页面的“添加代理”,添加信息后保存即可,如果你需要共享推流信息到其他国标平台,那么你需要编辑/国标通道配置,配置国标编码.
`PS ffmpeg默认模板不需修改需要修改`参数自行去ZLM配置文件中添加一个即可。

View File

@ -1,8 +1,11 @@
<!-- 推流列表 -->
# 推流列表
## 功能说明
WVP支持三种图像输入方式直播[拉流代理](_content/ability/proxy.md)[国标](_content/ability/device.md),直播设备接入流程如下
```plantuml
@startuml
"直播设备" -> "ZLMediaKit": 1. 发起推流
@ -11,30 +14,41 @@ WVP支持三种图像输入方式直播[拉流代理](_content/ability/pro
"WVP-PRO" -> "ZLMediaKit": 4. 通知推流到上级国标平台
@enduml
```
1. 默认情况下WVP收到推流信息后列表中出现这条推流信息如果你需要共享推流信息到其他国标平台,那么你需要编辑/国标通道配置,配置国标编码.
2. WVP也支持推流前导入大量通道直接推送给上级点击“下载模板”按钮根据示例修改模板后点击“通道导入”按钮导入通道数据.
## 推拉流鉴权规则
为了保护服务器的WVP默认开启推流鉴权目前不支持关闭此功能
### 推流规则
推流时需要携带推流鉴权的签名signsign=md5(pushKey),pushKey来自用户表每个用户会有一个不同的pushKey.
例如app=teststream=livepushKey=1000ip=192.168.1.4, port=10554 那么推流地址为:
```
rtsp://192.168.1.4:10554/test/live?sign=a9b7ba70783b617e9998dc4dd82eb3c5
```
支持推流时自定义播放鉴权Id参数名为callId此时sign=md5(callId_pushKey)
例如app=teststream=livepushKey=1000callId=12345678, ip=192.168.1.4, port=10554 那么推流地址为:
```
rtsp://192.168.1.4:10554/test/live?callId=12345678&sign=c8e6e01dde2d60c66dcea8d2498ffef1
```
### 播放规则
默认情况播放不需要鉴权但是如果推流时携带了callId那么播放时必须携带callId
例如app=teststream=live无callId, ip=192.168.1.4, port=10554 那么播放地址为:
```
rtsp://192.168.1.4:10554/test/live
```
例如app=teststream=livecallId=12345678, ip=192.168.1.4, port=10554 那么播放地址为:
```
rtsp://192.168.1.4:10554/test/live?callId=12345678
```

View File

@ -1,2 +1,3 @@
<!-- 用户管理 -->
# 用户管理

View File

@ -1,5 +1,7 @@
<!-- 关于本文档 -->
# 关于本文档
本文档开源在gitee上[https://gitee.com/pan648540858/wvp-pro-doc.git](https://gitee.com/pan648540858/wvp-pro-doc.git),如果文档出现任何错误或者不易理解的语句请大家提ISSUE帮助我及时更正。欢迎大家提交PR一起维护这份文档让更多的人可以使用到这个开源的视频平台。
本文档开源在gitee上[https://gitee.com/pan648540858/wvp-pro-doc.git](https://gitee.com/pan648540858/wvp-pro-doc.git)
,如果文档出现任何错误或者不易理解的语句请大家提ISSUE帮助我及时更正。欢迎大家提交PR一起维护这份文档让更多的人可以使用到这个开源的视频平台。

View File

@ -1,6 +1,7 @@
# 原理图
## 使用ffmpeg测试语音对讲原理
```plantuml
@startuml
"FFMPEG" -> "ZLMediaKit": 推流到zlm
@ -13,6 +14,7 @@
```
## 使用网页测试语音对讲原理
```plantuml
@startuml
"前端页面" -> "WVP-PRO": 请求推流地址

View File

@ -1,2 +1,5 @@
# 免责声明
WVP-PRO自有代码使用宽松的MIT协议在保留版权信息的情况下可以自由应用于各自商用、非商业的项目。 但是本项目也零碎的使用了一些其他的开源代码,在商用的情况下请自行替代或剔除; 由于使用本项目而产生的商业纠纷或侵权行为一概与本项目及开发者无关,请自行承担法律风险。 在使用本项目代码时,也应该在授权协议中同时表明本项目依赖的第三方库的协议
WVP-PRO自有代码使用宽松的MIT协议在保留版权信息的情况下可以自由应用于各自商用、非商业的项目。
但是本项目也零碎的使用了一些其他的开源代码,在商用的情况下请自行替代或剔除; 由于使用本项目而产生的商业纠纷或侵权行为一概与本项目及开发者无关,请自行承担法律风险。
在使用本项目代码时,也应该在授权协议中同时表明本项目依赖的第三方库的协议

View File

@ -1,46 +1,63 @@
<!-- 编译 -->
# 编译
WVP-PRO不只是实现了国标28181的协议本身也是一个完整的视频平台。所以对于新手来说你可能需要一些耐心来完成。遇到问题不要焦躁你可以
1. 百度
2. 加入星球体提问;[知识星球](https://t.zsxq.com/0d8VAD3Dm)
3. 向作者发送邮件648540858@qq.com,寻求技术支持(有偿);
WVP-PRO使用Spring boot开发maven管理依赖。对于熟悉spring开发的朋友是很容易进行编译部署以及运行的。
下面将提供一种通用方法方便大家运行项目。
## 1 服务介绍
| 服务 | 作用 | 是否必须 |
|----------------|------------------------------------------|-------------------------|
| WVP-PRO | 实现国标28181的信令以及视频平台相关的功能 | 是 |
| ZLMediaKit | 为WVP-PRO提供国标28181的媒体部分的实现以及各种视频流格式的分发支持 | 是 |
| 服务 | 作用 | 是否必须 |
|------------|------------------------------------------|------|
| WVP-PRO | 实现国标28181的信令以及视频平台相关的功能 | 是 |
| ZLMediaKit | 为WVP-PRO提供国标28181的媒体部分的实现以及各种视频流格式的分发支持 | 是 |
## 2 安装依赖
| 依赖 | 版本 | 用途 | 开发环境需要 | 生产环境需要 |
|--------|------------|-------------|--------|--------|
| jdk | >=1.8 | 运行与编译java代码 | 是 | 是 |
| maven | >=3.3 | 管理java代码依赖 | 否 | 否 |
| git || 下载/更新/提交代码 | 否 | 否 |
| nodejs || 编译于运行前端文件 | 否 | 否 |
| npm || 管理前端文件依赖 | 否 | 否 |
| 依赖 | 版本 | 用途 | 开发环境需要 | 生产环境需要 |
|--------|-------|-------------|--------|--------|
| jdk | >=1.8 | 运行与编译java代码 | 是 | 是 |
| maven | >=3.3 | 管理java代码依赖 | 否 | 否 |
| git | | 下载/更新/提交代码 | 否 | 否 |
| nodejs | | 编译于运行前端文件 | 否 | 否 |
| npm | | 管理前端文件依赖 | 否 | 否 |
如果你是一个新手建议你使用linux或者macOS平台。windows不推荐。
ubuntu环境以ubuntu 18为例
``` bash
apt-get install -y openjdk-11-jre git maven nodejs npm
```
centos环境,以centos 8为例
```bash
yum install -y java-1.8.0-openjdk.x86_64 git maven nodejs npm
```
window环境以windows10为例
```bash
这里不细说了,百度或者谷歌一搜一大把,基本都是下一步下一步,然后配置环境变量。
```
## 3 安装mysql以及redis
这里依然是参考网上教程,自行安装吧。
## 4 编译ZLMediaKit
参考ZLMediaKit[WIKI](https://github.com/ZLMediaKit/ZLMediaKit/wiki),如果需要使用语音对讲功能,请参考[zlm启用webrtc编译指南](https://github.com/ZLMediaKit/ZLMediaKit/wiki/zlm%E5%90%AF%E7%94%A8webrtc%E7%BC%96%E8%AF%91%E6%8C%87%E5%8D%97)开启zlm的webrtc功能。截取一下关键步骤
参考ZLMediaKit[WIKI](https://github.com/ZLMediaKit/ZLMediaKit/wiki)
,如果需要使用语音对讲功能,请参考[zlm启用webrtc编译指南](https://github.com/ZLMediaKit/ZLMediaKit/wiki/zlm%E5%90%AF%E7%94%A8webrtc%E7%BC%96%E8%AF%91%E6%8C%87%E5%8D%97)
开启zlm的webrtc功能。截取一下关键步骤
```bash
# 国内用户推荐从同步镜像网站gitee下载
git clone --depth 1 https://gitee.com/xia-chu/ZLMediaKit
@ -48,40 +65,52 @@ cd ZLMediaKit
# 千万不要忘记执行这句命令
git submodule update --init
```
## 5 编译WVP-PRO
### 5.1 可以通过git克隆也可以在项目下载点击下载
![点击下载](_media/img_1.png)
![点击下载](_media/img_2.png)
从gitee克隆
```bash
git clone https://gitee.com/pan648540858/wvp-GB28181-pro.git
```
从github克隆
```bash
git clone https://github.com/648540858/wvp-GB28181-pro.git
```
### 5.2 编译前端页面
```shell script
cd wvp-GB28181-pro/web_src/
npm --registry=https://registry.npmmirror.com install
npm run build
```
编译如果报错, 一般都是网络问题, 导致的依赖包下载失败
编译完成后在src/main/resources下出现static目录
**编译完成一般是这个样子,中间没有报红的错误信息**
![编译成功](_media/img.png)
### 5.3 生成可执行jar
```bash
cd wvp-GB28181-pro
mvn package
```
### 5.4 生成war
```bash
cd wvp-GB28181-pro
mvn package -P war
```
编译如果报错, 一般都是网络问题, 导致的依赖包下载失败
编译完成后在target目录下出现wvp-pro-***.jar/wvp-pro-***.war。
接下来[配置服务](./_content/introduction/config.md)

View File

@ -1,5 +1,7 @@
<!-- 配置 -->
# 配置
对于首次测试或者新手同学我建议在局域网测试并且关闭服务器与客户机的防火墙测试。建议部署在linux进行测试。
```plantuml
@ -8,24 +10,38 @@
"WVP-PRO" <-- "ZLMediaKit": Web Hook
@enduml
```
WVP-PRO通过调用ZLMediaKit的RESTful接口实现对ZLMediaKit行为的控制; ZLMediaKit通过Web Hook 接口把消息通知WVP-PRO。通过这种方式实现了两者的互通。
对于最简单的配置你不需要修改ZLMediaKit的任何默认配置。你只需要在WVP-PRO中配置的ZLMediaKit信息即可
## 1 WVP配置文件位置
基于spring boot的开发方式配置文件的加载是很灵活的。默认在src/main/resources/application.yml部分配置项是可选你不需要全部配置在配置文件中
完全的配置说明可以参看all-application.yml。
### 1.1 默认加载配置文件方式
使用maven打包后的target里已经存在了配置文件默认加载配置文件为application.yml查看内容发现其中spring.profiles.active配置的内容将入配置的值为dev那么具体加载的配置文件就是application-dev.yml如果配置的值找不到对应的配置文件修改值为dev。
```shell
cd wvp-GB28181-pro/target
java -jar wvp-pro-*.jar
```
## 2 配置WVP-PRO
wvp支持多种数据库包括MysqlPostgresql金仓等配置任选一种即可。
### 2.1 数据库配置
#### 2.1.1 初始化数据库
#### 2.1.1 初始化数据库
首先使用创建数据库然后使用sql/初始化.sql初始化数据库如果是从旧版升级上来的使用升级sql更新。
#### 2.1.2 Mysql数据库配置
#### 2.1.2 Mysql数据库配置
数据库名称以wvp为例
```yaml
spring:
dynamic:
@ -38,24 +54,30 @@ spring:
username: root
password: root123
```
#### 2.1.3 Postgresql数据库配置
#### 2.1.3 Postgresql数据库配置
数据库名称以wvp为例
```yaml
spring:
dynamic:
primary: master
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true&allowPublicKeyRetrieval=true
username: root
password: 12345678
primary: master
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true&allowPublicKeyRetrieval=true
username: root
password: 12345678
pagehelper:
helper-dialect: postgresql
```
#### 2.1.4 金仓数据库配置
#### 2.1.4 金仓数据库配置
数据库名称以wvp为例
```yaml
spring:
dynamic:
@ -70,31 +92,39 @@ spring:
pagehelper:
helper-dialect: postgresql
```
### 2.2 Redis数据库配置
配置wvp中的redis连接信息建议wvp自己单独使用一个db。
### 2.3 配置服务启动端口(可直接使用默认配置)
```yaml
# [可选] WVP监听的HTTP端口, 网页和接口调用都是这个端口
server:
port: 18080
port: 18080
```
### 2.4 配置28181相关信息可直接使用默认配置
```yaml
# 作为28181服务器的配置
sip:
# [可选] 28181服务监听的端口
port: 5060
# 根据国标6.1.2中规定domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码由省级、市级、区级、基层编号组成参照GB/T 2260-2007
# 后两位为行业编码定义参照附录D.3
# 3701020049标识山东济南历下区 信息行业接入
# [可选]
domain: 3402000000
# [可选]
id: 34020000002000000001
# [可选] 默认设备认证密码,后续扩展使用设备单独密码, 移除密码将不进行校验
password: 12345678
# [可选] 28181服务监听的端口
port: 5060
# 根据国标6.1.2中规定domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码由省级、市级、区级、基层编号组成参照GB/T 2260-2007
# 后两位为行业编码定义参照附录D.3
# 3701020049标识山东济南历下区 信息行业接入
# [可选]
domain: 3402000000
# [可选]
id: 34020000002000000001
# [可选] 默认设备认证密码,后续扩展使用设备单独密码, 移除密码将不进行校验
password: 12345678
```
### 2.5 配置ZLMediaKit连接信息
```yaml
#zlm 默认服务器配置
media:
@ -118,7 +148,9 @@ media:
# [可选] 国标级联在此范围内选择端口发送媒体流,
send-port-range: 40000,40300 # 端口范围
```
### 2.4 策略配置
```yaml
# [根据业务需求配置]
user-settings:
@ -133,6 +165,7 @@ user-settings:
# 国标点播 按需拉流, true有人观看拉流无人观看释放 false拉起后不自动释放
stream-on-demand: true
```
更多完整的配置信息参考all-application.yml文件需要那个配置项复制到正在使用的配置文件中对应的文件即可。
如果配置信息无误你可以启动zlm再启动wvp来测试了启动成功的话你可以在wvp的日志下看到zlm已连接的提示。

View File

@ -1,50 +1,62 @@
<!-- 部署 -->
# 部署
**请仔细阅读以下内容**
1. WVP-PRO与ZLM支持分开部署;
2. 需要开放的端口
| 服务 | 端口 | 类型 | 必选 |
|-----|:-------------------------|-------------|-------|
| wvp | server.port | tcp | 是 |
| wvp | sip.port | udp and tcp | 是 |
| zlm | http.port | tcp | 是 |
| zlm | http.sslport | tcp | 否 |
| zlm | rtmp.port | tcp | 否 |
| zlm | rtmp.sslport | tcp | 否 |
| zlm | rtsp.port | udp and tcp | 否 |
| zlm | rtsp.sslport | udp and tcp | 否 |
| zlm | rtp_proxy.port | udp and tcp | 单端口开放 |
| zlm | rtp.port-range(在wvp中配置) | udp and tcp | 多端口开放 |
| 服务 | 端口 | 类型 | 必选 |
|-----|:-------------------------|-------------|-------|
| wvp | server.port | tcp | 是 |
| wvp | sip.port | udp and tcp | 是 |
| zlm | http.port | tcp | 是 |
| zlm | http.sslport | tcp | 否 |
| zlm | rtmp.port | tcp | 否 |
| zlm | rtmp.sslport | tcp | 否 |
| zlm | rtsp.port | udp and tcp | 否 |
| zlm | rtsp.sslport | udp and tcp | 否 |
| zlm | rtp_proxy.port | udp and tcp | 单端口开放 |
| zlm | rtp.port-range(在wvp中配置) | udp and tcp | 多端口开放 |
3. 测试环境部署建议所有服务部署在一台主机,关闭防火墙,减少因网络出现问题的可能;
4. 生产环境按需开放端口但是建议修改默认端口尤其是5060端口易受到攻击;
5. zlm使用docker部署的情况请使用host模式或者端口映射一致比如映射5060,应将外部端口也映射为5060端口;
6. zlm与wvp会保持高频率的通信所以不要去将wvp与zlm分属在两个网络比如wvp在内网zlm却在公网的情况。
7. 启动服务以linux为例
**启动WVP-PRO**
**启动WVP-PRO**
```shell
nohup java -jar wvp-pro-*.jar &
```
**war包**
下载Tomcat后将war包放入webapps中启动Tomcat以解压war包停止Tomcat后删除ROOT目录以及war包将解压后的war包目录重命名为ROOT将配置文件中的Server.port配置为与Tomcat端口一致
然后启动Tomcat。
**启动ZLM**
```shell
nohup ./MediaServer -d -m 3 &
```
### 前后端分离部署
前后端部署目前在最新的版本已经支持请使用3月15日之后的版本部署
前端编译后的文件在`src/main/resources/static`中,将此目录下的文件部署。
WVP默认开启全部接口支持跨域。部署前端文件到WEB容器并将访问的地址设置为WVP的地址即可。
**配置前端服务器**
1. 在`src/main/resources/static/static/js/config.js`下配置服务器的地址也就是wvp服务的地址
```javascript
window.baseUrl = "http://xxx.com:18080"
```
`这里的地址是需要客户电脑能访问到的,因为请求是客户端电脑发起,与代理不同`
[接入设备](./_content/ability/device.md)
### 默认账号和密码
部署完毕后,可以通过访问 ip加端口的方式访问 WVP WVP的默认登录账号和密码均为 admin。

View File

@ -1,12 +1,18 @@
<!-- 反馈bug -->
# 反馈bug
代码是在不断的完善的不断修改会修复旧的问题也有可能引入新的问题所以遇到BUG是很正常的一件事。所以遇到问题不要烦燥咱们就事论事就好了。
## 如何反馈
1. 在知识星球提问。
2. 更新代码,很可能你遇到问题别人已经更早的遇到了,或者是作者自己发现了,已经解决了,所以你可以更新代码再次进行测试;
3. 可以在github提ISSUE我几乎每天都会去看issue你的问题我会尽快给予答复;
> 有偿支持可以给我发邮件, 648540858@qq.com
## 社群
[![社群](../../_media/shequ.png "shequ")](https://t.zsxq.com/0d8VAD3Dm)
> 收费是为了提供更好的服务,也是对作者更大的激励。加入星球的用户三天后可以私信我留下微信号,我会拉大家入群。加入三天内不满意可以直接退款,大家不需要有顾虑,来白嫖三天也不是不可以。

View File

@ -1,7 +1,11 @@
<!-- 参与开发 -->
# 参与到开发中来
非常欢迎有兴趣的小伙伴一起来维护这个项目
## 与开发有关的信息
- 开发语言后端java + 前端vue
- jdk版本 1.8
- 作者自用开发ide jetbrains intellij idea
@ -9,7 +13,9 @@
- 后端使用Spring boot框架开发
- 项目大量使用了异步操作;
- 跟代码学流程需要参考28181文档只看代码你会很懵的
- 必须学会[抓包](_content/skill/tcpdump.md),这是必须的
- 必须学会[抓包](_content/skill/tcpdump.md),这是必须的
## 提交代码
大家可以通过fork项目的方式提交自己的代码然后提交PR我来合并到主线。提交代码的过程中我们需要遵循“**阿里编码规约**”,现有代码也有很多代码没有做到,但是我们在朝这个方向努力。
大家可以通过fork项目的方式提交自己的代码然后提交PR我来合并到主线。提交代码的过程中我们需要遵循“**阿里编码规约**
”,现有代码也有很多代码没有做到,但是我们在朝这个方向努力。

View File

@ -1,6 +1,9 @@
<!-- 点播错误 -->
# 点播错误
排查点播错误你首先要清楚[点播的基本流程](_content/theory/play.md),一般的流程如下:
```plantuml
@startuml
"WEB用户" -> "WVP-PRO": 1. 发起点播请求
@ -15,11 +18,16 @@
"设备" --> "WVP-PRO": 10 200OK
@enduml
```
针对几种常见的错误,我们来分析一下,也方便大家对号入座解决常见的问题
## 点播收到错误码
这个错误一般表现为点击"播放"按钮后很快得到一个错误。
1. **400错误码**
出现400错误玛时一般是这样的流程是这样的
出现400错误玛时一般是这样的流程是这样的
```plantuml
@startuml
"WEB用户" -> "WVP-PRO": 1. 发起点播请求
@ -27,30 +35,39 @@
"设备" --> "WVP-PRO": 3. 400错误
@enduml
```
此时通常是设备认为WVP发送了错误的消息给它它认为消息不全或者错误所以直接返回400错误此时我们需要[抓包](_content/skill/tcpdump.md)来分析是否缺失了内容也可以直接联系对方询问为什么返回了400。
WVP不能保证兼容所有的设备有些实现不规范的设备可能在对接时就会出现上述问题你可以联系作者帮忙对接。
2. **500错误码**
500或者大于500小于600的错误码一般多是设备内部出了问题解决方式有两个第一种直接联系设备/平台客服寻求解决第二种如果你有确定可以对接这个设备的平台那么可以把对接这个平台的抓包和对接wvp的抓包同时发送给我我来尝试解决。
此时通常是设备认为WVP发送了错误的消息给它它认为消息不全或者错误所以直接返回400错误此时我们需要[抓包](_content/skill/tcpdump.md)
来分析是否缺失了内容也可以直接联系对方询问为什么返回了400。
WVP不能保证兼容所有的设备有些实现不规范的设备可能在对接时就会出现上述问题你可以联系作者帮忙对接。
2. **500错误码**
500或者大于500小于600的错误码一般多是设备内部出了问题解决方式有两个第一种直接联系设备/平台客服寻求解决第二种如果你有确定可以对接这个设备的平台那么可以把对接这个平台的抓包和对接wvp的抓包同时发送给我我来尝试解决。
## 点播超时
点播超时的情况大致分为两种:点播超时和收流超时
1. **点播超时**
点播超时错误一般为信令的超时,比如长时间为收到对方的回复,可能出现在流程中 “3. 200OK(携带SDP消息体)”这个位置,即我们发送点播消息,但是设备没有回复,可能的原因:
点播超时错误一般为信令的超时,比如长时间为收到对方的回复,可能出现在流程中 “3. 200OK(携带SDP消息体)
”这个位置,即我们发送点播消息,但是设备没有回复,可能的原因:
> 1. 设备内部错误,未能回复消息
> 2. 网络原因消息未到到达设备
> 2. 网络原因消息未到到达设备
大部分时候是原因2所以遇到这个错误我们首先要排查我们我的网路如果你是公网部署那么也可能时心跳周期太长导致的路由NAT失效WVP的消息无法通道原来的IP端口号发送给设备。
2. **收流超时**
收流超时可能发生在流程中的5和6,可能的原因有:
> 1. 设备发送了流但是发送到了错误的ip和端口上而这个信息是在invite消息的sdp中指定的就是流程2Invite(携带SDP消息体)中而这个错误很可能来自你的配置错误比如你设置了127.0.0.1导致设备网127.0.0.1上发流或者是你WVP在公网但是你给设备了一个内网ip导致设备无法把流发送过来
收流超时可能发生在流程中的5和6,可能的原因有:
> 1. 设备发送了流但是发送到了错误的ip和端口上而这个信息是在invite消息的sdp中指定的就是流程2Invite(携带SDP消息体)
而这个错误很可能来自你的配置错误比如你设置了127.0.0.1导致设备网127.0.0.1上发流或者是你WVP在公网但是你给设备了一个内网ip导致设备无法把流发送过来
> 2. 设备内部错误未发送流;
> 2. 设备发送了流,但是流无法识别,可能存在于流不规范和网络很差的情况下;
> 3. 设备发送了流zlm也收到了但是zlm无法通过hook通知到wvp此时原因是你可以检查zlm的配置文件中的hook配置看看是否无法从zlm连接到wvp
> 4. 设备发送了流但是开启SSRC校验设备的流不够规范采用错误的ssrc导致zlm选择丢弃
针对这些可能的错误原因我建议的排查顺序:
针对这些可能的错误原因我建议的排查顺序:
- 关闭ssrc校验
- 查看zlm配置的hook是否可以连接到zlm
- 查看zlm日志是否有流注册

View File

@ -1,8 +1,10 @@
<!-- 设备注册不上来的解决办法 -->
# 设备注册不上来的解决办法
一般的原因有两个
1. 信息填写错误,比如密码错误;
2. 网络不通导致注册消息无法发送到WVP
# 设备注册不上来的解决办法
一般的原因有两个
1. 信息填写错误,比如密码错误;
2. 网络不通导致注册消息无法发送到WVP
遇到问题首先仔细校验填写信息,例如海康可能需要勾选鉴权才可以输入密码。网络问题请自行测试。

View File

@ -1,17 +1,19 @@
<!-- 启动时报错 -->
# 启动时报错
启动时的报错大部分时候是因为你的配置有问题比如mysql没连接上redis没连接上18080/15060端口占用了这些都会导致启动是报错修改配置配置之后都可以解决
下面我整理的一些常见的错误,大家可以先对号入座的简单排查下。
> **常见错误**
> **常见错误**
![_media/img.png](_media/img.png)
**错误原因:** redis配置错误可能原因 redis未启动/ip错误/端口错误/网络不通
**错误原因:** redis配置错误可能原因 redis未启动/ip错误/端口错误/网络不通
---
![_media/img_1.png](_media/img_1.png)
**错误原因:** redis配置错误可能原因 密码错误
---
![_media/img_2.png](_media/img_2.png)
**错误原因:** mysql配置错误可能原因 mysql未启动/ip错误/端口错误/网络不通
**错误原因:** mysql配置错误可能原因 mysql未启动/ip错误/端口错误/网络不通
---
![_media/img_3.png](_media/img_3.png)
**错误原因:** mysql配置错误可能原因 用户名/密码错误

View File

@ -1,15 +1,25 @@
<!-- 抓包 -->
# 抓包
如果说对于网络编程有什么工具是必会的我觉得抓包肯定是其中之一了。作为GB/T 28181调试过程中最重要的手段我觉得如果你真对他有兴趣或者系统遇到问题可以最快的得到解决那么抓包你就一定要学会了。
如果说对于网络编程有什么工具是必会的我觉得抓包肯定是其中之一了。作为GB/T
28181调试过程中最重要的手段我觉得如果你真对他有兴趣或者系统遇到问题可以最快的得到解决那么抓包你就一定要学会了。
## 抓包工具的选择
### 1. Wireshark
在具备图形界面的系统上比如windowslinux发行版ubuntuopensuse等我一般直接使用Wireshark直接进行抓包也方便进行内容的查看。
### 2. Tcpdump
在使用命令行的系统比如linux服务器我一般使用Tcpdump进行抓包无需额外安装系统一般自带抓包的到的文件可以使用Wireshark打开在图形界面下方便查看内容。
## 工具安装
Wireshark的安装很简单根据提示一步步点击就好了在linux需要解决权限的问题如果和我一样使用图形界面的linux发行版的话可以参看如下步骤; windows的小伙伴直接略过即可
Wireshark的安装很简单根据提示一步步点击就好了在linux需要解决权限的问题如果和我一样使用图形界面的linux发行版的话可以参看如下步骤;
windows的小伙伴直接略过即可
```shell
# 1. 添加wireshark用户组
sudo groupadd wireshark
@ -20,43 +30,65 @@ sudo chmod 4755 /usr/bin/dumpcap
# 4. 将需要使用的用户名加入wireshark用户组
sudo gpasswd -a $USER wireshark
```
tcpdump一般linux都是自带无需安装可以这样验证;显示版本信息即是已安装
```shell
tcpdump --version
```
## 开始抓包
### 使用Wireshark
在28181中我一般只关注sip包和rtp包所以我一般是直接过滤sip和rtp可以输入框输入 `sip or rtp`这样即可如果设备来源比较多还可以加上ip和端口号的过滤`(sip or rtp )and ip.addr==192.168.1.3 and udp.port==5060`
在28181中我一般只关注sip包和rtp包所以我一般是直接过滤sip和rtp可以输入框输入 `sip or rtp`这样即可如果设备来源比较多还可以加上ip和端口号的过滤
`(sip or rtp )and ip.addr==192.168.1.3 and udp.port==5060`
详细的过滤规则可以自行百度,我可以提供一些常用的给大家参考
![img.png](_media/img.png)
**只过滤SIP**
```shell
sip
```
**只获取rtp数据**
```shell
rtp
```
**默认方式:**
```shell
sip or rtp
```
**过滤IP**
```shell
sip and ip.addr==192.168.1.3
```
**过滤端口:**
```shell
sip and udp.port==5060
```
输入命令开启抓包后此时可以进行操作比如点播录像回访等操作完成回到Wireshark点击红色的停止即可需要保存文件可以点击`文件->导出特定分组`导出过滤后的数据,也可以直接`文件->另存为`保存未过滤的数据。
输入命令开启抓包后此时可以进行操作比如点播录像回访等操作完成回到Wireshark点击红色的停止即可需要保存文件可以点击
`文件->导出特定分组`导出过滤后的数据,也可以直接`文件->另存为`保存未过滤的数据。
### 使用tcpdump
对于服务器抓包,为了得到足够完整的数据,我一般会要求直接抓取网卡数据而不过滤,如下:
抓取网卡首先需要获取网卡名在linux我一般使用`ip addr`获取网卡信息,如下所示:
![img_1.png](_media/img_1.png)
```shell
sudo tcpdump -i wlp3s0 -w demo.pcap
```
![img_2.png](_media/img_2.png)
命令行会停留在这个位置,此时可以进行操作,比如点播,录像回放等,操作完成回到命令行使用`Ctrl+C`结束命令行在当前目录下得到demo.pcap将这个文件下载到图形界面操作系统里即可使用Wireshark查看了
命令行会停留在这个位置,此时可以进行操作,比如点播,录像回放等,操作完成回到命令行使用`Ctrl+C`
结束命令行在当前目录下得到demo.pcap将这个文件下载到图形界面操作系统里即可使用Wireshark查看了
更多的操作可以参考: [https://www.cnblogs.com/jiujuan/p/9017495.html](https://www.cnblogs.com/jiujuan/p/9017495.html)

View File

@ -1,6 +1,7 @@
<!-- 点播流程 -->
# 点播流程
> 以下为WVP-PRO级联语音喊话流程。
```plantuml
@ -31,8 +32,8 @@
@enduml
```
## 注册流程描述如下:
## 注册流程描述如下:
1. 用户从网页或调用接口发起点播请求;
2. WVP-PRO向摄像机发送Invite消息,消息头域中携带 Subject字段,表明点播的视频源ID、发送方媒体流序列号、ZLMediaKit接收流使用的IP、端口号、
接收端媒体流序列号等参数,SDP消息体中 s字段为“Play”代表实时点播y字段描述SSRC值,f字段描述媒体参数。

View File

@ -1,14 +0,0 @@
<!-- 通道的树形结构 -->
# 通道的树形结构
国标28181规定了两种组织设备树的方式
1. **行政区划**
行政区划模式下主要是以行政区划作为目录节点例如:河北省->邯郸市->广平县
![_media/img_8.png](_media/img_8.png)
2. **业务分组**
业务分组主要自定义的目录树的一种组织形式,但是对定义的目录的国标编号有一定的要求。
第一级别需要是业务分组类型即国标编码中的11、12、13是215,例如65010200002150000001
业务分组下是虚拟组织即国标编码中的11、12、13是216,例如65010200002160000002。
虚拟组织下不可是业务分组,虚拟组织下可以继续添加虚拟组织。
![_media/img_9.png](_media/img_9.png)

View File

@ -1,25 +1,29 @@
<!-- 统一编码规则 -->
# 统一编码规则
## D.1 编码规则 A
>&emsp;&emsp;编码规则 A 由中心编码(8位)、行业编码(2位)、类型编码(3位)和序号(7位)四个码段共20位十
>进制数字字符构成,即系统编码 =中心编码 + 行业编码 + 类型编码 + 序号。
>&emsp;&emsp;编码规则 A 的详细说明见表 D.1。其中,中心编码指用户或设备所归属的监控中心的编码,按照监控中心所在地的行政区划代码确定,
> 当不是基层单位时空余位为0。行政区划代码采用 GB/T2260— 2007规定的行政区划代码表示。行业编码是指用户或设备所归属的行业,行业编码对照表见 D.3。
> 类型编码指定了设备或用户的具体类型,其中的前端设备包含公安系统和非公安系统的前端设备,终端用 户包含公安系统和非公安系统的终端用户。
> &emsp;&emsp;编码规则 A 由中心编码(8位)、行业编码(2位)、类型编码(3位)和序号(7位)四个码段共20位十
> 进制数字字符构成,即系统编码 =中心编码 + 行业编码 + 类型编码 + 序号。
> &emsp;&emsp;编码规则 A 的详细说明见表 D.1。其中,中心编码指用户或设备所归属的监控中心的编码,按照监控中心所在地的行政区划代码确定,
> 当不是基层单位时空余位为0。行政区划代码采用 GB/T2260— 2007规定的行政区划代码表示。行业编码是指用户或设备所归属的行业,行业编码对照表见
> D.3。
> 类型编码指定了设备或用户的具体类型,其中的前端设备包含公安系统和非公安系统的前端设备,终端用
> 户包含公安系统和非公安系统的终端用户。
![img_7.png](_media/img_7.png)
![img_1.png](_media/img_1.png)
![img_2.png](_media/img_2.png)
![img_2.png](_media/img_2.png)
## D.2 编码规则 B
>&emsp;&emsp;编码规则 B由中心编码(8位)、行业编码(2位)、序号(4位)和类型编码(2位)四个码段构成,即系
>统编码 =中心编码 + 行业编码 +序号+类型编码。编码规则 B的详细说明见表 D.2。
> &emsp;&emsp;编码规则 B由中心编码(8位)、行业编码(2位)、序号(4位)和类型编码(2位)四个码段构成,即系
> 统编码 =中心编码 + 行业编码 +序号+类型编码。编码规则 B的详细说明见表 D.2。
![img_3.png](_media/img_3.png)
![img_4.png](_media/img_4.png)
## D.3 行业编码对照表
>&emsp;&emsp;行业编码对照表见表 D.3。
> &emsp;&emsp;行业编码对照表见表 D.3。
![img_5.png](_media/img_5.png)
![img_6.png](_media/img_6.png)

View File

@ -1,6 +1,7 @@
<!-- 点播流程 -->
# 点播流程
> 以下为WVP-PRO点播流程。点播成功前的任何一个环节出现问题都可能出现点播超时这也是排查点播超时的依据。
```plantuml
@ -18,11 +19,11 @@
@enduml
```
## 注册流程描述如下:
## 注册流程描述如下:
1. 用户从网页或调用接口发起点播请求;
1. 用户从网页或调用接口发起点播请求;
2. WVP-PRO向摄像机发送Invite消息,消息头域中携带 Subject字段,表明点播的视频源ID、发送方媒体流序列号、ZLMediaKit接收流使用的IP、端口号、
接收端媒体流序列号等参数,SDP消息体中 s字段为“Play”代表实时点播y字段描述SSRC值,f字段描述媒体参数。
接收端媒体流序列号等参数,SDP消息体中 s字段为“Play”代表实时点播y字段描述SSRC值,f字段描述媒体参数。
3. 摄像机向WVP-PRO回复200OK消息体中描述了媒体流发送者发送媒体流的IP、端口、媒体格式、SSRC字段等内容。
4. WVP-PRO向设备回复Ack 会话建立成功。
5. 设备向ZLMediaKit发送实时流。

View File

@ -1,6 +1,7 @@
<!-- 注册流程 -->
# 注册流程
WVP-PRO目前仅支持国标中描述的基本注册流程也是最常用的
> 基本注册即采用IETFRFC3261规定的基于数字摘要的挑战应答式安全技术进行注册.
@ -13,9 +14,8 @@ WVP-PRO目前仅支持国标中描述的基本注册流程也是最常用的
@enduml
```
> 注册流程描述如下:
> 1. 摄像机向WVP-PRO服务器发送 Register请求;
> 注册流程描述如下:
> 1. 摄像机向WVP-PRO服务器发送 Register请求;
> 2. WVP-PRO向摄像机发送响应401,并在响应的消息头 WWW_Authenticate字段中给出适合摄像机的认证体制和参数;
> 3. 摄像机重新向WVP-PRO发送 Register请求,在请求的 Authorization字段给出信任书, 包含认证信息;
> 4. WVP-PRO对请求进行验证,如果检查出 摄像机身份合法,向摄像机发送成功响应 200OK,如果身份不合法则发送拒绝服务应答。

View File

@ -5,8 +5,8 @@
> 开箱即用的28181协议视频平台。
- 基于GB/T28181-2016标准信令实现兼容GB/T28181-2011。
- 自带完整前端页面,开箱即用。
- 基于GB/T28181-2016标准信令实现兼容GB/T28181-2011。
- 自带完整前端页面,开箱即用。
- 完全开源且使用MIT许可协议。可以在保留版权信息的基础上商用。
[GitHub](https://github.com/648540858/wvp-GB28181-pro)

View File

@ -1,34 +1,32 @@
<!-- 侧边栏 -->
* **编译与部署**
* [编译](_content/introduction/compile.md)
* [配置](_content/introduction/config.md)
* [部署](_content/introduction/deployment.md)
* [编译](_content/introduction/compile.md)
* [配置](_content/introduction/config.md)
* [部署](_content/introduction/deployment.md)
* **功能与使用**
* [接入设备](_content/ability/device.md)
* [国标设备](_content/ability/device_use.md)
* [推流列表](_content/ability/push.md)
* [拉流代理](_content/ability/proxy.md)
* [通道管理](_content/ability/channel.md)
* [国标级联](_content/ability/cascade2.md)
* [节点管理](_content/ability/node_manger.md)
* [云端录像](_content/ability/cloud_record.md)
* [不间断录像](_content/ability/continuous_recording.md)
* [语音对讲](_content/ability/continuous_broadcast.md)
* [接入设备](_content/ability/device.md)
* [国标设备](_content/ability/device_use.md)
* [推流列表](_content/ability/push.md)
* [拉流代理](_content/ability/proxy.md)
* [云端录像](_content/ability/cloud_record.md)
* [节点管理](_content/ability/node_manger.md)
* [通道管理](_content/ability/channel.md)
* [国标级联](_content/ability/cascade2.md)
* **流程与原理**
* [统一编码规则](_content/theory/code.md)
* [树形结构](_content/theory/channel_tree.md)
* [注册流程](_content/theory/register.md)
* [点播流程](_content/theory/play.md)
* [级联语音喊话流程](_content/theory/broadcast_cascade.md)
* [统一编码规则](_content/theory/code.md)
* [注册流程](_content/theory/register.md)
* [点播流程](_content/theory/play.md)
* [级联语音喊话流程](_content/theory/broadcast_cascade.md)
* [语音对讲](_content/ability/continuous_broadcast.md)
* **必备技巧**
* [抓包](_content/skill/tcpdump.md)
* [抓包](_content/skill/tcpdump.md)
* **常见问答**
- [如何反馈BUG](_content/qa/bug.md)
- [如何参与开发](_content/qa/development.md)
- [启动报错的解决办法](_content/qa/start_error.md)
- [设备注册不上来的解决办法](_content/qa/regiser_error.md)
- [点播超时/报错的解决办法](_content/qa/play_error.md)
- [如何反馈BUG](_content/qa/bug.md)
- [如何参与开发](_content/qa/development.md)
- [启动报错的解决办法](_content/qa/start_error.md)
- [设备注册不上来的解决办法](_content/qa/regiser_error.md)
- [点播超时/报错的解决办法](_content/qa/play_error.md)
* [**免责声明**](_content/disclaimers.md)
* [**关于本文档**](_content/about_doc.md)

View File

@ -1,59 +1,59 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WVP-PRO文档</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Description">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<link rel="icon" href="_media/favicon.ico" type="image/x-icon" />
<link rel="stylesheet" href="./lib/css/vue.css">
<!-- <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify/lib/themes/dark.css">-->
<style>
.cover{
background: linear-gradient(to left bottom, hsl(82, 100%, 85%) 0%,hsl(199, 100%, 85%) 100%) !important;
}
</style>
<meta charset="UTF-8">
<title>WVP-PRO文档</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="description" content="Description">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<link rel="icon" href="_media/favicon.ico" type="image/x-icon"/>
<link rel="stylesheet" href="./lib/css/vue.css">
<!-- <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify/lib/themes/dark.css">-->
<style>
.cover {
background: linear-gradient(to left bottom, hsl(82, 100%, 85%) 0%, hsl(199, 100%, 85%) 100%) !important;
}
</style>
</head>
<body>
<div id="app">加载中</div>
<script>
<div id="app">加载中</div>
<script>
window.$docsify = {
name: 'WVP-RPO使用文档',
repo: 'https://github.com/648540858/wvp-GB28181-pro',
loadSidebar: true, // 开启侧边栏
loadNavbar: true, // 开启导航栏
coverpage: true, // 开启封面
name: 'WVP-RPO使用文档',
repo: 'https://github.com/648540858/wvp-GB28181-pro',
loadSidebar: true, // 开启侧边栏
loadNavbar: true, // 开启导航栏
coverpage: true, // 开启封面
subMaxLevel: 3,
plantuml: {
skin: 'default',
},
search: {
maxAge: 86400000, // 过期时间,单位毫秒,默认一天
paths: 'auto', // or 'auto'
placeholder: '搜索',
noData: '找不到结果',
// 搜索标题的最大层级, 1 - 6
depth: 4,
hideOtherSidebarContent: false, // 是否隐藏其他侧边栏内容
},
copyCode: {
buttonText : '复制',
errorText : '错误',
successText: '已复制'
},
// disqus: 'shortname'
subMaxLevel: 3,
plantuml: {
skin: 'default',
},
search: {
maxAge: 86400000, // 过期时间,单位毫秒,默认一天
paths: 'auto', // or 'auto'
placeholder: '搜索',
noData: '找不到结果',
// 搜索标题的最大层级, 1 - 6
depth: 4,
hideOtherSidebarContent: false, // 是否隐藏其他侧边栏内容
},
copyCode: {
buttonText: '复制',
errorText: '错误',
successText: '已复制'
},
// disqus: 'shortname'
}
</script>
<!-- Docsify v4 -->
<script src="./lib/js/docsify@4.js"></script>
<script src="./lib/js/docsify-plantuml.min.js"></script>
<script src="./lib/js/search.min.js"></script>
<script src="./lib/js/zoom-image.min.js"></script>
<script src="./lib/js/docsify-copy-code.min.js"></script>
</script>
<!-- Docsify v4 -->
<script src="./lib/js/docsify@4.js"></script>
<script src="./lib/js/docsify-plantuml.min.js"></script>
<script src="./lib/js/search.min.js"></script>
<script src="./lib/js/zoom-image.min.js"></script>
<script src="./lib/js/docsify-copy-code.min.js"></script>
</script>
</script>
<!-- <script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/disqus.min.js"></script>-->
</body>
</html>

View File

@ -133,6 +133,14 @@
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 数据库监控页面 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.23</version>
</dependency>
<!-- mysql数据库 -->
<dependency>
<groupId>com.mysql</groupId>

View File

@ -0,0 +1,38 @@
package com.genersoft.iot.vmp.gb28181.bean;
public class NotifyCatalogChannel {
private Type type;
private DeviceChannel channel;
public enum Type {
ADD, DELETE, UPDATE, STATUS_CHANGED
}
public static NotifyCatalogChannel getInstance(Type type, DeviceChannel channel) {
NotifyCatalogChannel notifyCatalogChannel = new NotifyCatalogChannel();
notifyCatalogChannel.setType(type);
notifyCatalogChannel.setChannel(channel);
return notifyCatalogChannel;
}
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
public DeviceChannel getChannel() {
return channel;
}
public void setChannel(DeviceChannel channel) {
this.channel = channel;
}
}

View File

@ -18,16 +18,21 @@ import java.util.List;
@Repository
public interface DeviceChannelMapper {
@Insert("INSERT INTO wvp_device_channel (device_id, device_db_id, name, manufacturer, model, owner, civil_code, block, " +
@Insert("<script> " +
"insert into wvp_device_channel " +
"(device_id, device_db_id, name, manufacturer, model, owner, civil_code, block, " +
"address, parental, parent_id, safety_way, register_way, cert_num, certifiable, err_code, end_time, secrecy, " +
"ip_address, port, password, status, longitude, latitude, ptz_type, position_type, room_type, use_type, " +
"supply_light_type, direction_type, resolution, business_group_id, download_speed, svc_space_support_mod, " +
"svc_time_support_mode, create_time, update_time, sub_countstream_id, has_audio, gps_time, stream_identification, channel_type) " +
"VALUES (#{deviceId}, #{deviceDbId}, #{name}, #{manufacturer}, #{model}, #{owner}, #{civilCode}, #{block}," +
"svc_time_support_mode, create_time, update_time, sub_count, stream_id, has_audio, gps_time, stream_identification, channel_type) " +
"values " +
"(#{deviceId}, #{deviceDbId}, #{name}, #{manufacturer}, #{model}, #{owner}, #{civilCode}, #{block}, " +
"#{address}, #{parental}, #{parentId}, #{safetyWay}, #{registerWay}, #{certNum}, #{certifiable}, #{errCode}, #{endTime}, #{secrecy}, " +
"#{ipAddress}, #{port}, #{password}, #{status}, #{longitude}, #{latitude}, #{ptzType}, #{positionType}, #{roomType}, #{useType}, " +
"#{supplyLightType}, #{directionType}, #{resolution}, #{businessGroupId}, #{downloadSpeed}, #{svcSpaceSupportMod}," +
" #{svcTimeSupportMode}, #{createTime}, #{updateTime}, #{subCount}, #{streamId}, #{hasAudio}, #{gpsTime}, #{streamIdentification}, #{channelType})")
" #{svcTimeSupportMode}, #{createTime}, #{updateTime}, #{subCount}, #{streamId}, #{hasAudio}, #{gpsTime}, #{streamIdentification}, #{channelType}) " +
"</script>")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
int add(DeviceChannel channel);
@ -543,4 +548,57 @@ public interface DeviceChannelMapper {
"</foreach> " +
"</script>")
void updateStreamGPS(List<GPSMsgInfo> gpsMsgInfoList);
@Update("UPDATE wvp_device_channel SET status=#{status} WHERE device_id=#{deviceId} AND channel_id=#{channelId}")
void updateStatus(DeviceChannel channel);
@Update({"<script>" +
" UPDATE" +
" wvp_device_channel" +
" SET update_time=#{updateTime}" +
", device_id=#{deviceId}" +
", device_db_id=#{deviceDbId}" +
", name=#{name}" +
", manufacturer=#{manufacturer}" +
", model=#{model}" +
", owner=#{owner}" +
", civil_code=#{civilCode}" +
", block=#{block}" +
", address=#{address}" +
", parental=#{parental}" +
", parent_id=#{parentId}" +
", safety_way=#{safetyWay}" +
", register_way=#{registerWay}" +
", cert_num=#{certNum}" +
", certifiable=#{certifiable}" +
", err_code=#{errCode}" +
", end_time=#{endTime}" +
", secrecy=#{secrecy}" +
", ip_address=#{ipAddress}" +
", port=#{port}" +
", password=#{password}" +
", status=#{status}" +
", longitude=#{longitude}" +
", latitude=#{latitude}" +
", ptz_type=#{ptzType}" +
", position_type=#{positionType}" +
", room_type=#{roomType}" +
", use_type=#{useType}" +
", supply_light_type=#{supplyLightType}" +
", direction_type=#{directionType}" +
", resolution=#{resolution}" +
", business_group_id=#{businessGroupId}" +
", download_speed=#{downloadSpeed}" +
", svc_space_support_mod=#{svcSpaceSupportMod}" +
", svc_time_support_mode=#{svcTimeSupportMode}" +
", sub_count=#{subCount}" +
", stream_id=#{streamId}" +
", has_audio=#{hasAudio}" +
", gps_time=#{gpsTime}" +
", stream_identification=#{streamIdentification}" +
", channel_type=#{channelType}" +
" WHERE id = #{id}" +
"</script>"})
void updateChannelForNotify(DeviceChannel channel);
}

View File

@ -111,4 +111,12 @@ public interface IDeviceChannelService {
DeviceChannel getBroadcastChannel(int deviceDbId);
void changeAudio(Integer channelId, Boolean audio);
void updateChannelStatus(DeviceChannel channel);
void addChannel(DeviceChannel channel);
void updateChannelForNotify(DeviceChannel channel);
DeviceChannel getOneForSource(int deviceDbId, String channelId);
}

View File

@ -267,6 +267,11 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService {
return channelMapper.getOneByDeviceIdForSource(device.getId(), channelId);
}
@Override
public DeviceChannel getOneForSource(int deviceDbId, String channelId) {
return channelMapper.getOneByDeviceIdForSource(deviceDbId, channelId);
}
@Override
@Transactional
public synchronized void batchUpdateChannelForNotify(List<DeviceChannel> channels) {
@ -654,4 +659,19 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService {
public void changeAudio(Integer channelId, Boolean audio) {
channelMapper.changeAudio(channelId, audio);
}
@Override
public void updateChannelStatus(DeviceChannel channel) {
channelMapper.updateStatus(channel);
}
@Override
public void addChannel(DeviceChannel channel) {
channelMapper.add(channel);
}
@Override
public void updateChannelForNotify(DeviceChannel channel) {
channelMapper.updateChannelForNotify(channel);
}
}

View File

@ -1,15 +1,12 @@
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.CatalogChannelEvent;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.bean.HandlerCatchData;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.utils.DateUtil;
import lombok.extern.slf4j.Slf4j;
@ -27,10 +24,7 @@ import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* SIP NOTIFY
@ -39,11 +33,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
@Component
public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent {
private final List<DeviceChannel> updateChannelForStatusChange = new CopyOnWriteArrayList<>();
private final Map<String, DeviceChannel> updateChannelMap = new ConcurrentHashMap<>();
private final Map<String, DeviceChannel> addChannelMap = new ConcurrentHashMap<>();
private final List<DeviceChannel> deleteChannelList = new CopyOnWriteArrayList<>();
private final ConcurrentLinkedQueue<NotifyCatalogChannel> channelList = new ConcurrentLinkedQueue<>();
private ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>();
@ -64,7 +54,6 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
// log.warn("[notify-目录订阅] 待处理消息数量: {}", taskQueue.size() );
// }
@Transactional
public void process(RequestEvent evt) {
if (taskQueue.size() >= userSetting.getMaxNotifyCountQueue()) {
log.error("[notify-目录订阅] 待处理消息队列已满 {}返回486 BUSY_HERE消息不做处理", userSetting.getMaxNotifyCountQueue());
@ -79,8 +68,12 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
return;
}
List<HandlerCatchData> handlerCatchDataList = new ArrayList<>();
while (!taskQueue.isEmpty()) {
handlerCatchDataList.add(taskQueue.poll());
int size = taskQueue.size();
for (int i = 0; i < size; i++) {
HandlerCatchData poll = taskQueue.poll();
if (poll != null) {
handlerCatchDataList.add(poll);
}
}
if (handlerCatchDataList.isEmpty()) {
return;
@ -91,23 +84,23 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
}
RequestEvent evt = take.getEvt();
try {
long start = System.currentTimeMillis();
FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME);
String deviceId = SipUtils.getUserIdFromFromHeader(fromHeader);
Device device = redisCatchStorage.getDevice(deviceId);
if (device == null || !device.isOnLine()) {
log.warn("[收到目录订阅]{}, 但是设备已经离线", (device != null ? device.getDeviceId() : ""));
return;
continue;
}
Element rootElement = getRootElement(evt, device.getCharset());
if (rootElement == null) {
log.warn("[ 收到目录订阅 ] content cannot be null, {}", evt.getRequest());
return;
continue;
}
Element deviceListElement = rootElement.element("DeviceList");
if (deviceListElement == null) {
return;
log.warn("[ 收到目录订阅 ] content cannot be null, {}", evt.getRequest());
continue;
}
Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
if (deviceListIterator != null) {
@ -139,7 +132,7 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
// 上线
log.info("[收到通道上线通知] 来自设备: {}, 通道 {}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId());
channel.setStatus("ON");
updateChannelForStatusChange.add(channel);
channelList.add(NotifyCatalogChannel.getInstance(NotifyCatalogChannel.Type.STATUS_CHANGED, channel));
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId(), true);
@ -152,7 +145,7 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
log.info("[收到通道离线通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId());
} else {
channel.setStatus("OFF");
updateChannelForStatusChange.add(channel);
channelList.add(NotifyCatalogChannel.getInstance(NotifyCatalogChannel.Type.STATUS_CHANGED, channel));
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId(), false);
@ -166,7 +159,7 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
log.info("[收到通道视频丢失通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId());
} else {
channel.setStatus("OFF");
updateChannelForStatusChange.add(channel);
channelList.add(NotifyCatalogChannel.getInstance(NotifyCatalogChannel.Type.STATUS_CHANGED, channel));
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId(), false);
@ -180,7 +173,7 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
log.info("[收到通道视频故障通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId());
} else {
channel.setStatus("OFF");
updateChannelForStatusChange.add(channel);
channelList.add(NotifyCatalogChannel.getInstance(NotifyCatalogChannel.Type.STATUS_CHANGED, channel));
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId(), false);
@ -191,18 +184,17 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
// 增加
log.info("[收到增加通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId());
// 判断此通道是否存在
DeviceChannel deviceChannel = deviceChannelService.getOne(deviceId, catalogChannelEvent.getChannel().getDeviceId());
DeviceChannel deviceChannel = deviceChannelService.getOneForSource(device.getId(), catalogChannelEvent.getChannel().getDeviceId());
if (deviceChannel != null) {
log.info("[增加通道] 已存在,不发送通知只更新,设备: {}, 通道 {}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId());
channel.setId(deviceChannel.getId());
channel.setHasAudio(deviceChannel.isHasAudio());
channel.setUpdateTime(DateUtil.getNow());
updateChannelMap.put(catalogChannelEvent.getChannel().getDeviceId(), channel);
channelList.add(NotifyCatalogChannel.getInstance(NotifyCatalogChannel.Type.UPDATE, channel));
} else {
catalogChannelEvent.getChannel().setUpdateTime(DateUtil.getNow());
catalogChannelEvent.getChannel().setCreateTime(DateUtil.getNow());
addChannelMap.put(catalogChannelEvent.getChannel().getDeviceId(), catalogChannelEvent.getChannel());
channelList.add(NotifyCatalogChannel.getInstance(NotifyCatalogChannel.Type.ADD, channel));
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendChannelAddOrDelete(device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId(), true);
@ -213,7 +205,7 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
case CatalogEvent.DEL:
// 删除
log.info("[收到删除通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId());
deleteChannelList.add(catalogChannelEvent.getChannel());
channelList.add(NotifyCatalogChannel.getInstance(NotifyCatalogChannel.Type.DELETE, channel));
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendChannelAddOrDelete(device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId(), false);
@ -223,17 +215,17 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
// 更新
log.info("[收到更新通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId());
// 判断此通道是否存在
DeviceChannel deviceChannelForUpdate = deviceChannelService.getOne(deviceId, catalogChannelEvent.getChannel().getDeviceId());
DeviceChannel deviceChannelForUpdate = deviceChannelService.getOneForSource(device.getId(), catalogChannelEvent.getChannel().getDeviceId());
if (deviceChannelForUpdate != null) {
channel.setId(deviceChannelForUpdate.getId());
channel.setHasAudio(deviceChannelForUpdate.isHasAudio());
channel.setUpdateTime(DateUtil.getNow());
channel.setUpdateTime(DateUtil.getNow());
updateChannelMap.put(catalogChannelEvent.getChannel().getDeviceId(), channel);
channelList.add(NotifyCatalogChannel.getInstance(NotifyCatalogChannel.Type.UPDATE, channel));
} else {
catalogChannelEvent.getChannel().setCreateTime(DateUtil.getNow());
catalogChannelEvent.getChannel().setUpdateTime(DateUtil.getNow());
addChannelMap.put(catalogChannelEvent.getChannel().getDeviceId(), catalogChannelEvent.getChannel());
channelList.add(NotifyCatalogChannel.getInstance(NotifyCatalogChannel.Type.ADD, channel));
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendChannelAddOrDelete(device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId(), true);
@ -253,66 +245,39 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
log.error("未处理的异常 ", e);
}
}
taskQueue.clear();
if (!updateChannelMap.keySet().isEmpty()
|| !addChannelMap.keySet().isEmpty()
|| !updateChannelForStatusChange.isEmpty()
|| !deleteChannelList.isEmpty()) {
if (!channelList.isEmpty()) {
executeSave();
}
}
public void executeSave(){
try {
executeSaveForAdd();
} catch (Exception e) {
log.error("[存储收到的增加通道] 异常: ", e );
@Transactional
public void executeSave() {
int size = channelList.size();
List<NotifyCatalogChannel> channelListForSave = new ArrayList<>();
for (int i = 0; i < size; i++) {
channelListForSave.add(channelList.poll());
}
try {
executeSaveForStatus();
} catch (Exception e) {
log.error("[存储收到的通道状态变化] 异常: ", e );
}
try {
executeSaveForUpdate();
} catch (Exception e) {
log.error("[存储收到的更新通道] 异常: ", e );
}
try {
executeSaveForDelete();
} catch (Exception e) {
log.error("[存储收到的删除通道] 异常: ", e );
}
}
private void executeSaveForUpdate(){
if (!updateChannelMap.values().isEmpty()) {
log.info("[存储收到的更新通道], 数量: {}", updateChannelMap.size());
ArrayList<DeviceChannel> deviceChannels = new ArrayList<>(updateChannelMap.values());
deviceChannelService.batchUpdateChannelForNotify(deviceChannels);
updateChannelMap.clear();
}
}
private void executeSaveForAdd(){
if (!addChannelMap.values().isEmpty()) {
ArrayList<DeviceChannel> deviceChannels = new ArrayList<>(addChannelMap.values());
addChannelMap.clear();
deviceChannelService.batchAddChannel(deviceChannels);
}
}
private void executeSaveForDelete(){
if (!deleteChannelList.isEmpty()) {
deviceChannelService.deleteChannelsForNotify(deleteChannelList);
deleteChannelList.clear();
}
}
private void executeSaveForStatus(){
if (!updateChannelForStatusChange.isEmpty()) {
deviceChannelService.updateChannelsStatus(updateChannelForStatusChange);
updateChannelForStatusChange.clear();
for (NotifyCatalogChannel notifyCatalogChannel : channelListForSave) {
try {
switch (notifyCatalogChannel.getType()) {
case STATUS_CHANGED:
deviceChannelService.updateChannelStatus(notifyCatalogChannel.getChannel());
break;
case ADD:
deviceChannelService.addChannel(notifyCatalogChannel.getChannel());
break;
case UPDATE:
deviceChannelService.updateChannelForNotify(notifyCatalogChannel.getChannel());
break;
case DELETE:
deviceChannelService.delete(notifyCatalogChannel.getChannel());
break;
}
}catch (Exception e) {
log.error("[存储收到的通道]类型:{},编号:{}", notifyCatalogChannel.getType(),
notifyCatalogChannel.getChannel().getDeviceId(), e);
}
}
}
}

View File

@ -25,4 +25,16 @@ public class TestController {
public List<Hook> all(){
return subscribe.getAll();
}
// @Bean
// public ServletRegistrationBean<StatViewServlet> druidStatViewServlet() {
// ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
// registrationBean.addInitParameter("allow", "127.0.0.1");// IP白名单 (没有配置或者为空,则允许所有访问)
// registrationBean.addInitParameter("deny", "");// IP黑名单 (存在共同时deny优先于allow)
// registrationBean.addInitParameter("loginUsername", "admin");
// registrationBean.addInitParameter("loginPassword", "admin");
// registrationBean.addInitParameter("resetEnable", "false");
// return registrationBean;
// }
}