Browse Source

测试1 合并至最新代码

kindring 3 years ago
parent
commit
c29dcebdd4
100 changed files with 4010 additions and 3136 deletions
  1. 63 102
      README.md
  2. 3 5
      bootstrap.sh
  3. 57 11
      pom.xml
  4. 28 9
      src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java
  5. 353 151
      src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java
  6. 20 7
      src/main/java/com/genersoft/iot/vmp/common/VersionPo.java
  7. 15 0
      src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
  8. 10 5
      src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java
  9. 10 7
      src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java
  10. 16 5
      src/main/java/com/genersoft/iot/vmp/conf/GlobalExceptionHandler.java
  11. 2 2
      src/main/java/com/genersoft/iot/vmp/conf/GlobalResponseAdvice.java
  12. 5 5
      src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java
  13. 0 1
      src/main/java/com/genersoft/iot/vmp/conf/MediaStatusTimerTask.java
  14. 4 9
      src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java
  15. 9 22
      src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java
  16. 2 3
      src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java
  17. 2 4
      src/main/java/com/genersoft/iot/vmp/conf/SpringDocConfig.java
  18. 5 2
      src/main/java/com/genersoft/iot/vmp/conf/SystemInfoTimerTask.java
  19. 2 0
      src/main/java/com/genersoft/iot/vmp/conf/ThreadPoolTaskConfig.java
  20. 103 0
      src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
  21. 2 0
      src/main/java/com/genersoft/iot/vmp/conf/VersionConfig.java
  22. 1 0
      src/main/java/com/genersoft/iot/vmp/conf/VersionInfo.java
  23. 1 1
      src/main/java/com/genersoft/iot/vmp/conf/WVPTimerTask.java
  24. 0 2
      src/main/java/com/genersoft/iot/vmp/conf/exception/SsrcTransactionNotFoundException.java
  25. 3 3
      src/main/java/com/genersoft/iot/vmp/conf/redis/RedisConfig.java
  26. 10 12
      src/main/java/com/genersoft/iot/vmp/conf/security/AnonymousAuthenticationEntryPoint.java
  27. 9 6
      src/main/java/com/genersoft/iot/vmp/conf/security/DefaultUserDetailsServiceImpl.java
  28. 11 2
      src/main/java/com/genersoft/iot/vmp/conf/security/LoginSuccessHandler.java
  29. 6 1
      src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java
  30. 56 63
      src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java
  31. 9 0
      src/main/java/com/genersoft/iot/vmp/conf/security/dto/LoginUser.java
  32. 3 2
      src/main/java/com/genersoft/iot/vmp/gb28181/GBEventSubscribe.java
  33. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/GBHookSubscribeFactory.java
  34. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/HookSubscribeForKey.java
  35. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/IGBHookSubscribe.java
  36. 107 56
      src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
  37. 9 63
      src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java
  38. 13 2
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/AlarmChannelMessage.java
  39. 51 6
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
  40. 14 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarmMethod.java
  41. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamInfo.java
  42. 11 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java
  43. 22 2
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java
  44. 19 8
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java
  45. 13 2
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java
  46. 1 3
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeInfo.java
  47. 20 7
      src/main/java/com/genersoft/iot/vmp/gb28181/conf/DefaultProperties.java
  48. 5 2
      src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java
  49. 1 4
      src/main/java/com/genersoft/iot/vmp/gb28181/event/device/RequestTimeoutEventImpl.java
  50. 32 12
      src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEventListener.java
  51. 11 5
      src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java
  52. 6 1
      src/main/java/com/genersoft/iot/vmp/gb28181/session/RecordDataCatch.java
  53. 5 6
      src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java
  54. 3 3
      src/main/java/com/genersoft/iot/vmp/gb28181/task/SipDeviceRunner.java
  55. 52 23
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java
  56. 20 26
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  57. 4 4
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
  58. 74 73
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java
  59. 121 133
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java
  60. 182 241
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  61. 234 214
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
  62. 32 98
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java
  63. 23 8
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
  64. 3 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
  65. 294 202
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
  66. 60 48
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java
  67. 54 57
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java
  68. 24 39
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java
  69. 3 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/TestInviteRequestProcessor.java
  70. 7 9
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java
  71. 6 13
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageRequestProcessor.java
  72. 267 53
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java
  73. 126 117
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java
  74. 47 33
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java
  75. 2 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java
  76. 16 16
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MobilePositionNotifyMessageHandler.java
  77. 2 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/AlarmQueryMessageHandler.java
  78. 36 29
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java
  79. 25 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceInfoQueryMessageHandler.java
  80. 12 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceStatusQueryMessageHandler.java
  81. 7 9
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/RecordInfoQueryMessageHandler.java
  82. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/AlarmResponseMessageHandler.java
  83. 9 8
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java
  84. 63 66
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java
  85. 15 13
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/ConfigDownloadResponseMessageHandler.java
  86. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceConfigResponseMessageHandler.java
  87. 15 13
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceControlResponseMessageHandler.java
  88. 15 24
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceInfoResponseMessageHandler.java
  89. 4 6
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceStatusResponseMessageHandler.java
  90. 16 9
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/MobilePositionResponseMessageHandler.java
  91. 17 7
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/PresetQueryResponseMessageHandler.java
  92. 96 98
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java
  93. 8 19
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java
  94. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java
  95. 70 13
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java
  96. 85 4
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java
  97. 2 2
      src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java
  98. 657 733
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
  99. 7 6
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java
  100. 28 29
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java

+ 63 - 102
README.md

@@ -1,4 +1,4 @@
-![logo](https://raw.githubusercontent.com/648540858/wvp-GB28181-pro/wvp-28181-2.0/web_src/static/logo.png)
+![logo](doc/_media/logo.png)
 # 开箱即用的28181协议视频平台
 
 [![Build Status](https://travis-ci.org/xia-chu/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xia-chu/ZLMediaKit)
@@ -17,7 +17,7 @@ WEB VIDEO PLATFORM是一个基于GB28181-2016标准实现的开箱即用的网
 # 应用场景:
 支持浏览器无插件播放摄像头视频。  
 支持摄像机、平台、NVR等设备接入。 
-支持国标级联。  
+支持国标级联。多平台级联。跨网视频预览。
 支持rtsp/rtmp等视频流转发到国标平台。  
 支持rtsp/rtmp等推流转发到国标平台。  
 
@@ -31,62 +31,49 @@ WEB VIDEO PLATFORM是一个基于GB28181-2016标准实现的开箱即用的网
 https://gitee.com/pan648540858/wvp-GB28181-pro.git
 
 # 截图
-![build_1.png](https://images.gitee.com/uploads/images/2022/0304/101513_79632720_1018729.png "2022-03-04_09-51.png")
-![build_1.png](https://images.gitee.com/uploads/images/2022/0304/103025_5df016f9_1018729.png "2022-03-04_10-27.png")
-![build_1.png](https://images.gitee.com/uploads/images/2022/0304/101706_088fbafa_1018729.png "2022-03-04_09-52_1.png")
-![build_1.png](https://images.gitee.com/uploads/images/2022/0304/101756_3d662828_1018729.png "2022-03-04_10-00_1.png")
-![build_1.png](https://images.gitee.com/uploads/images/2022/0304/101823_19050c66_1018729.png "2022-03-04_10-12_1.png")
-![build_1.png](https://images.gitee.com/uploads/images/2022/0304/101848_e5a39557_1018729.png "2022-03-04_10-12_2.png")
-![build_1.png](https://images.gitee.com/uploads/images/2022/0304/101919_ee5b8c79_1018729.png "2022-03-04_10-13.png")
-
-# 1.0 基础特性  
-1. 视频预览;  
-2. 云台控制(方向、缩放控制);  
-3. 视频设备信息同步;   
-4. 离在线监控;  
-5. 录像查询与回放(基于NVR\DVR,暂不支持快进、seek操作);  
-6. 无人观看自动断流;    
-7. 支持UDP和TCP两种国标信令传输模式; 
-8. 集成web界面, 不需要单独部署前端服务, 直接利用wvp内置文件服务部署, 随wvp一起部署;   
-9. 支持平台接入, 针对大平台大量设备的情况进行优化;  
-10. 支持检索,通道筛选;  
-11. 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题;  
-12. 支持启用udp多端口模式, 提高udp模式下媒体传输性能;  
-13. 支持通道是否含有音频的设置;  
-14. 支持通道子目录查询;  
-15. 支持udp/tcp国标流传输模式;  
-16. 支持直接输出RTSP、RTMP、HTTP-FLV、Websocket-FLV、HLS多种协议流地址  
-17. 支持国标网络校时  
-18. 支持公网部署, 支持wvp与zlm分开部署   
-19. 支持播放h265, g.711格式的流(需要将closeWaitRTPInfo设为false)
-20. 报警信息处理,支持向前端推送报警信息
-
-# 1.0 新支持特性  
-1. 集成web界面, 不需要单独部署前端服务, 直接利用wvp内置文件服务部署, 随wvp一起部署;   
-2. 支持平台接入, 针对大平台大量设备的情况进行优化;  
-3. 支持检索,通道筛选;  
-4. 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题;  
-5. 支持启用udp多端口模式, 提高udp模式下媒体传输性能;  
-6. 支持通道是否含有音频的设置;  
-7. 支持通道子目录查询;  
-8. 支持udp/tcp国标流传输模式;  
-9. 支持直接输出RTSP、RTMP、HTTP-FLV、Websocket-FLV、HLS多种协议流地址  
-10. 支持国标网络校时  
-11. 支持公网部署, 支持wvp与zlm分开部署   
-12. 支持播放h265, g.711格式的流   
-13. 支持固定流地址和自动点播,同时支持未点播时直接播放流地址,代码自动发起点播.  ( [查看WIKI](https://github.com/648540858/wvp-GB28181-pro/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E5%9B%BA%E5%AE%9A%E6%92%AD%E6%94%BE%E5%9C%B0%E5%9D%80%E4%B8%8E%E8%87%AA%E5%8A%A8%E7%82%B9%E6%92%AD))
-14. 报警信息处理,支持向前端推送报警信息
-15. 支持订阅与通知方法
-   -  [X] 移动位置订阅
-   -  [X] 移动位置通知处理
-   -  [X] 报警事件订阅
-   -  [X] 报警事件通知处理
-   -  [X] 设备目录订阅
-   -  [X] 设备目录通知处理
-16. 移动位置查询和显示,可通过配置文件设置移动位置历史是否存储
-
-# 2.0 支持特性
-- [X] 国标通道向上级联
+![index](doc/_media/index.png "index.png")
+![2](doc/_media/2.png "2.png")
+![3](doc/_media/3.png "3.png")
+![3-1](doc/_media/3-1.png "3-1.png")
+![3-2](doc/_media/3-2.png "3-2.png")
+![3-3](doc/_media/3-3.png "3-3.png")
+![build_1](https://images.gitee.com/uploads/images/2022/0304/101919_ee5b8c79_1018729.png "2022-03-04_10-13.png")
+
+# 功能特性 
+- [X] 集成web界面
+- [X] 兼容性良好
+- [X] 支持电子地图,支持接入WGS84和GCJ02两种坐标系,并且自动转化为合适的坐标系进行展示和分发
+- [X] 接入设备
+  - [X] 视频预览
+  - [X] 无限制接入路数,能接入多少设备只取决于你的服务器性能
+  - [X] 云台控制,控制设备转向,拉近,拉远
+  - [X] 预置位查询,使用与设置
+  - [X] 查询NVR/IPC上的录像与播放,支持指定时间播放与下载
+  - [X] 无人观看自动断流,节省流量
+  - [X] 视频设备信息同步
+  - [X] 离在线监控
+  - [X] 支持直接输出RTSP、RTMP、HTTP-FLV、Websocket-FLV、HLS多种协议流地址
+  - [X] 支持通过一个流地址直接观看摄像头,无需登录以及调用任何接口
+  - [X] 支持UDP和TCP两种国标信令传输模式
+  - [X] 支持UDP和TCP两种国标流传输模式
+  - [X] 支持检索,通道筛选
+  - [X] 支持通道子目录查询
+  - [X] 支持过滤音频,防止杂音影响观看
+  - [X] 支持国标网络校时
+  - [X] 支持播放H264和H265
+  - [X] 报警信息处理,支持向前端推送报警信息
+  - [X] 支持订阅与通知方法
+    - [X] 移动位置订阅
+    - [X] 移动位置通知处理
+    - [X] 报警事件订阅
+    - [X] 报警事件通知处理
+    - [X] 设备目录订阅
+    - [X] 设备目录通知处理
+  -  [X] 移动位置查询和显示
+  - [X] 支持手动添加设备和给设备设置单独的密码
+-  [X] 支持平台对接接入
+-  [X] 支持国标级联
+  - [X] 国标通道向上级联
     - [X] WEB添加上级平台
     - [X] 注册
     - [X] 心跳保活
@@ -101,53 +88,27 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
     - [X] 目录订阅与通知
     - [X] 录像查看与播放
     - [X] GPS订阅与通知(直播推流)
-- [X] 添加RTSP视频
-- [X] 添加接口鉴权
-- [X] 添加RTMP视频
-- [X] 云端录像(需要部署单独服务配合使用)
+- [X] 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题;  
 - [X] 多流媒体节点,自动选择负载最低的节点使用。
-- [X] WEB端支持播放H264与H265,音频支持G.711A/G.711U/AAC,覆盖国标常用编码格式。
-- [X] 支持电子地图。
-- [X] 支持接入WGS84和GCJ02两种坐标系。
-
-[//]: # (# docker快速体验)
-
-[//]: # (目前作者的docker-compose因为时间有限维护不及时,这里提供第三方提供的供大家使用,维护不易,大家记得给这位小伙伴点个star。  )
-
-[//]: # (https://github.com/SaltFish001/wvp_pro_compose)
-
-[//]: # ([https://github.com/SaltFish001/wvp_pro_compose](https://github.com/SaltFish001/wvp_pro_compose))
-
-[//]: # (这是作者维护的一个镜像,可能存在不及时的问题。)
-
-[//]: # (```shell)
-
-[//]: # (docker pull 648540858/wvp_pro)
-
-[//]: # ()
-[//]: # (docker run  --env WVP_IP="你的IP" -it -p 18080:18080 -p 30000-30500:30000-30500/udp -p 30000-30500:30000-30500/tcp -p 80:80 -p 5060:5060 -p 5060:5060/udp 648540858/wvp_pro)
-
-[//]: # (```)
-
-[//]: # (docker使用详情查看:[https://hub.docker.com/r/648540858/wvp_pro](https://hub.docker.com/r/648540858/wvp_pro))
-
-# gitee同步仓库
-https://gitee.com/pan648540858/wvp-GB28181-pro.git  
-
-# 遇到问题
+- [X] 支持启用udp多端口模式, 提高udp模式下媒体传输性能;
+- [X] 支持公网部署; 
+- [X] 支持wvp与zlm分开部署,提升平台并发能力
+- [X] 支持拉流RTSP/RTMP,分发为各种流格式,或者推送到其他国标平台
+- [X] 支持推流RTSP/RTMP,分发为各种流格式,或者推送到其他国标平台
+- [X] 支持推流鉴权
+- [X] 支持接口鉴权
+- [X] 云端录像,推流/代理/国标视频均可以录制在云端服务器,支持预览和下载
+- [X] 支持打包可执行jar和war
+- [X] 支持跨域请求,支持前后端分离部署
+ 
+
+# 遇到问题如何解决
 国标最麻烦的地方在于设备的兼容性,所以需要大量的设备来测试,目前作者手里的设备有限,再加上作者水平有限,所以遇到问题在所难免;
-1. 查看wiki,仔细的阅读可以帮你避免几乎所有的问题
+1. 查看文档网站,仔细的阅读可以帮你避免几乎所有的问题
 2. 搜索issues,这里有大部分的答案
-3. 加QQ群,这里有大量热心的小伙伴,但是前提新希望你已经仔细阅读了wiki和搜索了issues。
+3. 加QQ群(901799015),这里有大量热心的小伙伴,但是前提新希望你已经仔细阅读了wiki和搜索了issues。
 4. 你可以请作者为你解答,但是我不是免费的。
-5. 你可以把遇到问题的设备寄给我,可以更容易的复现问题。
-
-
-# 合作
-目前很多打着合作的幌子来私聊的,其实大家大可不必,目前作者没有精力,你有问题可以付费找我解答,也可以提PR
-,如果对代码有建议可以提ISSUE;也可以加群一起聊聊。我们欢迎所有有兴趣参与到项目中来的人。
-
-
+5. 你可以把遇到问题的设备寄给我,可以更容易的兼容设备和解决问题。
 
 # 使用帮助
 QQ群: 901799015, ZLM使用文档[https://github.com/ZLMediaKit/ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit)  
@@ -165,7 +126,7 @@ QQ私信一般不回, 精力有限.欢迎大家在群里讨论.觉得项目对
 [hotcoffie](https://github.com/hotcoffie) [xiaomu](https://github.com/nikmu) [TristingChen](https://github.com/TristingChen)
 [chenparty](https://github.com/chenparty) [Hotleave](https://github.com/hotleave) [ydwxb](https://github.com/ydwxb)
 [ydpd](https://github.com/ydpd) [szy833](https://github.com/szy833) [ydwxb](https://github.com/ydwxb) [Albertzhu666](https://github.com/Albertzhu666)
-[mk1990](https://github.com/mk1990)
+[mk1990](https://github.com/mk1990) [SaltFish001](https://github.com/SaltFish001)
 
 ps: 刚增加了这个名单,肯定遗漏了一些大佬,欢迎大佬联系我添加。
 

+ 3 - 5
bootstrap.sh

@@ -19,9 +19,7 @@
 #
 ######### PARAM ######################################
 
-cd ./target
-
-JAVA_OPT= #"-Xmx1024m"
+JAVA_OPT=-Xmx1024m
 JARFILE=`ls -1r *.jar 2>/dev/null | head -n 1`
 PID_FILE=pid.file
 RUNNING=N
@@ -45,9 +43,9 @@ start()
                 then
                         echo "ERROR: jar file not found"
                 else
-                        nohup java  $JAVA_OPT -Djava.security.egd=file:/dev/./urandom -jar $PWD/$JARFILE  --spring.config.location=../src/main/resources/application.yml > nohup.out 2>&1  &
+                        nohup java  $JAVA_OPT -Djava.security.egd=file:/dev/./urandom -jar $PWD/$JARFILE > nohup.out 2>&1  &
                         echo $! > $PID_FILE
-                        echo "Application $JAVA_OPT $JARFILE starting..."
+                        echo "Application $JARFILE starting..."
                         tail -f nohup.out
                 fi
         fi

+ 57 - 11
pom.xml

@@ -14,6 +14,7 @@
 	<version>1.0.1</version>
 	<name>hfy-gb28181</name>
 	<description>hfy国标28181视频平台</description>
+	<packaging>${project.packaging}</packaging>
 
 	<repositories>
 		<repository>
@@ -56,6 +57,42 @@
 		<asciidoctor.pdf.output.directory>${project.build.directory}/asciidoc/pdf</asciidoctor.pdf.output.directory>
 	</properties>
 
+	<profiles>
+		<profile>
+			<id>jar</id>
+			<activation>
+				<activeByDefault>true</activeByDefault>
+			</activation>
+			<properties>
+				<project.packaging>jar</project.packaging>
+			</properties>
+		</profile>
+		<profile>
+			<id>war</id>
+			<properties>
+				<project.packaging>war</project.packaging>
+			</properties>
+			<dependencies>
+				<dependency>
+					<groupId>org.springframework.boot</groupId>
+					<artifactId>spring-boot-starter-web</artifactId>
+					<exclusions>
+						<exclusion>
+							<groupId>org.springframework.boot</groupId>
+							<artifactId>spring-boot-starter-jetty</artifactId>
+						</exclusion>
+					</exclusions>
+				</dependency>
+				<dependency>
+					<groupId>javax.servlet</groupId>
+					<artifactId>javax.servlet-api</artifactId>
+					<version>3.1.0</version>
+					<scope>provided</scope>
+				</dependency>
+			</dependencies>
+		</profile>
+	</profiles>
+
 	<dependencies>
 		<dependency>
 			<groupId>org.springframework.boot</groupId>
@@ -153,13 +190,17 @@
 			<version>2.1.3</version>
 		</dependency>
 
-		<!-- json解析库fastjson -->
+		<!-- json解析库fastjson2 -->
 		<dependency>
-			<groupId>com.alibaba</groupId>
-			<artifactId>fastjson</artifactId>
-			<version>1.2.83</version>
+			<groupId>com.alibaba.fastjson2</groupId>
+			<artifactId>fastjson2</artifactId>
+			<version>2.0.17</version>
+		</dependency>
+		<dependency>
+			<groupId>com.alibaba.fastjson2</groupId>
+			<artifactId>fastjson2-extension</artifactId>
+			<version>2.0.17</version>
 		</dependency>
-
 
 		<!-- okhttp -->
 		<dependency>
@@ -175,8 +216,6 @@
 			<version>4.10.0</version>
 		</dependency>
 
-
-
 		<!-- okhttp-digest -->
 		<dependency>
 			<groupId>io.github.rburgst</groupId>
@@ -185,10 +224,17 @@
 		</dependency>
 
 		<!-- https://mvnrepository.com/artifact/net.sf.kxml/kxml2 -->
+<!--		<dependency>-->
+<!--			<groupId>net.sf.kxml</groupId>-->
+<!--			<artifactId>kxml2</artifactId>-->
+<!--			<version>2.3.0</version>-->
+<!--		</dependency>-->
+
+		<!-- jwt实现 -->
 		<dependency>
-			<groupId>net.sf.kxml</groupId>
-			<artifactId>kxml2</artifactId>
-			<version>2.3.0</version>
+			<groupId>org.bitbucket.b_c</groupId>
+			<artifactId>jose4j</artifactId>
+			<version>0.9.3</version>
 		</dependency>
 
 		<!--反向代理-->
@@ -238,8 +284,8 @@
 			<artifactId>spring-boot-starter-test</artifactId>
 <!--			<scope>test</scope>-->
 		</dependency>
-	</dependencies>
 
+	</dependencies>
 
 
 	<build>

+ 28 - 9
src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java

@@ -1,20 +1,24 @@
 package com.genersoft.iot.vmp;
 
-import java.util.logging.LogManager;
-
 import com.genersoft.iot.vmp.conf.druid.EnableDruidSupport;
-import com.genersoft.iot.vmp.storager.impl.RedisCatchStorageImpl;
 import com.genersoft.iot.vmp.utils.GitUtil;
 import com.genersoft.iot.vmp.utils.SpringBeanFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
 import org.springframework.boot.web.servlet.ServletComponentScan;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
 import org.springframework.context.ConfigurableApplicationContext;
 import org.springframework.scheduling.annotation.EnableScheduling;
 
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import java.util.Collections;
+
 /**
  * 启动类
  */
@@ -22,7 +26,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
 @SpringBootApplication
 @EnableScheduling
 @EnableDruidSupport
-public class VManageBootstrap extends LogManager {
+public class VManageBootstrap extends SpringBootServletInitializer {
 
 	private final static Logger logger = LoggerFactory.getLogger(VManageBootstrap.class);
 
@@ -35,15 +39,30 @@ public class VManageBootstrap extends LogManager {
 		logger.info("--------------------------------------------------");
 		logger.info("------------- HFY GB Server START ----------------");
 		logger.info("--------------------------------------------------");
-//		logger.info("构建版本: {}", gitUtil1.getBuildVersion());
-//		logger.info("构建时间: {}", gitUtil1.getBuildDate());
-//		logger.info("GIT最后提交时间: {}", gitUtil1.getCommitTime());
+		logger.info("构建版本: {}", gitUtil1.getBuildVersion());
+		logger.info("构建时间: {}", gitUtil1.getBuildDate());
+		logger.info("GIT最后提交时间: {}", gitUtil1.getCommitTime());
 	}
 	// 项目重启
 	public static void restart() {
 		context.close();
 		VManageBootstrap.context = SpringApplication.run(VManageBootstrap.class, args);
 	}
-	
 
+	@Override
+	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+		return application.sources(VManageBootstrap.class);
+	}
+
+	@Override
+	public void onStartup(ServletContext servletContext) throws ServletException {
+		super.onStartup(servletContext);
+
+		servletContext.setSessionTrackingModes(
+				Collections.singleton(SessionTrackingMode.COOKIE)
+		);
+		SessionCookieConfig sessionCookieConfig = servletContext.getSessionCookieConfig();
+		sessionCookieConfig.setHttpOnly(true);
+
+	}
 }

+ 353 - 151
src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java

@@ -1,165 +1,349 @@
 package com.genersoft.iot.vmp.common;
 
+import io.swagger.v3.oas.annotations.media.Schema;
 
-public class StreamInfo {
+import java.io.Serializable;
 
+@Schema(description = "霜陓洘")
+public class StreamInfo implements Serializable, Cloneable{
+
+    @Schema(description = "茼蚚靡")
     private String app;
+    @Schema(description = "霜ID")
     private String stream;
+    @Schema(description = "扢掘晤瘍")
     private String deviceID;
+    @Schema(description = "籵耋晤瘍")
     private String channelId;
-    private String flv;
 
+    @Schema(description = "IP")
     private String ip;
 
-    private String https_flv;
-    private String ws_flv;
-    private String wss_flv;
-    private String fmp4;
-    private String https_fmp4;
-    private String ws_fmp4;
-    private String wss_fmp4;
-    private String hls;
-    private String https_hls;
-    private String ws_hls;
-    private String wss_hls;
-    private String ts;
-    private String https_ts;
-    private String ws_ts;
-    private String wss_ts;
-    private String rtmp;
-    private String rtmps;
-    private String rtsp;
-    private String rtsps;
-    private String rtc;
-
-    private String rtcs;
+    @Schema(description = "HTTP-FLV霜華硊")
+    private StreamURL flv;
+
+    @Schema(description = "HTTPS-FLV霜華硊")
+    private StreamURL https_flv;
+    @Schema(description = "Websocket-FLV霜華硊")
+    private StreamURL ws_flv;
+    @Schema(description = "Websockets-FLV霜華硊")
+    private StreamURL wss_flv;
+    @Schema(description = "HTTP-FMP4霜華硊")
+    private StreamURL fmp4;
+    @Schema(description = "HTTPS-FMP4霜華硊")
+    private StreamURL https_fmp4;
+    @Schema(description = "Websocket-FMP4霜華硊")
+    private StreamURL ws_fmp4;
+    @Schema(description = "Websockets-FMP4霜華硊")
+    private StreamURL wss_fmp4;
+    @Schema(description = "HLS霜華硊")
+    private StreamURL hls;
+    @Schema(description = "HTTPS-HLS霜華硊")
+    private StreamURL https_hls;
+    @Schema(description = "Websocket-HLS霜華硊")
+    private StreamURL ws_hls;
+    @Schema(description = "Websockets-HLS霜華硊")
+    private StreamURL wss_hls;
+    @Schema(description = "HTTP-TS霜華硊")
+    private StreamURL ts;
+    @Schema(description = "HTTPS-TS霜華硊")
+    private StreamURL https_ts;
+    @Schema(description = "Websocket-TS霜華硊")
+    private StreamURL ws_ts;
+    @Schema(description = "Websockets-TS霜華硊")
+    private StreamURL wss_ts;
+    @Schema(description = "RTMP霜華硊")
+    private StreamURL rtmp;
+    @Schema(description = "RTMPS霜華硊")
+    private StreamURL rtmps;
+    @Schema(description = "RTSP霜華硊")
+    private StreamURL rtsp;
+    @Schema(description = "RTSPS霜華硊")
+    private StreamURL rtsps;
+    @Schema(description = "RTC霜華硊")
+    private StreamURL rtc;
+
+    @Schema(description = "RTCS霜華硊")
+    private StreamURL rtcs;
+    @Schema(description = "霜羸极ID")
     private String mediaServerId;
+    @Schema(description = "霜晤鎢陓洘")
     private Object tracks;
+    @Schema(description = "羲宎奀潔")
     private String startTime;
+    @Schema(description = "賦旰奀潔")
     private String endTime;
+    @Schema(description = "輛僅ㄗ翹砉狟婥妏蚚ㄘ")
     private double progress;
 
+    @Schema(description = "岆瘁婃礿ㄗ翹砉隙溫妏蚚ㄘ")
     private boolean pause;
 
-    public static class TransactionInfo{
-        public String callId;
-        public String localTag;
-        public String remoteTag;
-        public String branch;
+    public void setFlv(StreamURL flv) {
+        this.flv = flv;
     }
 
-    private TransactionInfo transactionInfo;
+    public void setHttps_flv(StreamURL https_flv) {
+        this.https_flv = https_flv;
+    }
 
-    public String getApp() {
-        return app;
+    public void setWs_flv(StreamURL ws_flv) {
+        this.ws_flv = ws_flv;
     }
 
-    public void setApp(String app) {
-        this.app = app;
+    public void setWss_flv(StreamURL wss_flv) {
+        this.wss_flv = wss_flv;
     }
 
-    public String getDeviceID() {
-        return deviceID;
+    public void setFmp4(StreamURL fmp4) {
+        this.fmp4 = fmp4;
     }
 
-    public void setDeviceID(String deviceID) {
-        this.deviceID = deviceID;
+    public void setHttps_fmp4(StreamURL https_fmp4) {
+        this.https_fmp4 = https_fmp4;
     }
 
-    public String getChannelId() {
-        return channelId;
+    public void setWs_fmp4(StreamURL ws_fmp4) {
+        this.ws_fmp4 = ws_fmp4;
     }
 
-    public void setChannelId(String channelId) {
-        this.channelId = channelId;
+    public void setWss_fmp4(StreamURL wss_fmp4) {
+        this.wss_fmp4 = wss_fmp4;
     }
 
-    public String getFlv() {
-        return flv;
+    public void setHls(StreamURL hls) {
+        this.hls = hls;
     }
 
-    public void setFlv(String flv) {
-        this.flv = flv;
+    public void setHttps_hls(StreamURL https_hls) {
+        this.https_hls = https_hls;
     }
 
-    public String getWs_flv() {
-        return ws_flv;
+    public void setWs_hls(StreamURL ws_hls) {
+        this.ws_hls = ws_hls;
     }
 
-    public void setWs_flv(String ws_flv) {
-        this.ws_flv = ws_flv;
+    public void setWss_hls(StreamURL wss_hls) {
+        this.wss_hls = wss_hls;
     }
 
-    public String getRtmp() {
-        return rtmp;
+    public void setTs(StreamURL ts) {
+        this.ts = ts;
     }
 
-    public void setRtmp(String rtmp) {
-        this.rtmp = rtmp;
+    public void setHttps_ts(StreamURL https_ts) {
+        this.https_ts = https_ts;
     }
 
-    public String getHls() {
-        return hls;
+    public void setWs_ts(StreamURL ws_ts) {
+        this.ws_ts = ws_ts;
     }
 
-    public void setHls(String hls) {
-        this.hls = hls;
+    public void setWss_ts(StreamURL wss_ts) {
+        this.wss_ts = wss_ts;
     }
 
-    public String getRtsp() {
-        return rtsp;
+    public void setRtmp(StreamURL rtmp) {
+        this.rtmp = rtmp;
     }
 
-    public void setRtsp(String rtsp) {
-        this.rtsp = rtsp;
+    public void setRtmps(StreamURL rtmps) {
+        this.rtmps = rtmps;
     }
 
-    public Object getTracks() {
-        return tracks;
+    public void setRtsp(StreamURL rtsp) {
+        this.rtsp = rtsp;
     }
 
-    public void setTracks(Object tracks) {
-        this.tracks = tracks;
+    public void setRtsps(StreamURL rtsps) {
+        this.rtsps = rtsps;
     }
 
-    public String getFmp4() {
-        return fmp4;
+    public void setRtc(StreamURL rtc) {
+        this.rtc = rtc;
     }
 
-    public void setFmp4(String fmp4) {
-        this.fmp4 = fmp4;
+    public void setRtcs(StreamURL rtcs) {
+        this.rtcs = rtcs;
     }
 
-    public String getWs_fmp4() {
-        return ws_fmp4;
+    public void setRtmp(String host, int port, int sslPort, String app, String stream, String callIdParam) {
+        String file = String.format("%s/%s/%s", app, stream, callIdParam);
+        if (port > 0) {
+            this.rtmp = new StreamURL("rtmp", host, port, file);
+        }
+        if (sslPort > 0) {
+            this.rtmps = new StreamURL("rtmps", host, sslPort, file);
+        }
+    }
+
+    public void setRtsp(String host, int port, int sslPort, String app, String stream, String callIdParam) {
+        String file = String.format("%s/%s/%s", app, stream, callIdParam);
+        if (port > 0) {
+            this.rtsp = new StreamURL("rtsp", host, port, file);
+        }
+        if (sslPort > 0) {
+            this.rtsps = new StreamURL("rtsps", host, sslPort, file);
+        }
+    }
+
+    public void setFlv(String host, int port, int sslPort, String app, String stream, String callIdParam) {
+        String file = String.format("%s/%s.live.flv%s", app, stream, callIdParam);
+        if (port > 0) {
+            this.flv = new StreamURL("http", host, port, file);
+        }
+        this.ws_flv = new StreamURL("ws", host, port, file);
+        if (sslPort > 0) {
+            this.https_flv = new StreamURL("https", host, sslPort, file);
+            this.wss_flv = new StreamURL("wss", host, sslPort, file);
+        }
+    }
+
+    public void setFmp4(String host, int port, int sslPort, String app, String stream, String callIdParam) {
+        String file = String.format("%s/%s.live.mp4%s", app, stream, callIdParam);
+        if (port > 0) {
+            this.fmp4 = new StreamURL("http", host, port, file);
+            this.ws_fmp4 = new StreamURL("ws", host, port, file);
+        }
+        if (sslPort > 0) {
+            this.https_fmp4 = new StreamURL("https", host, sslPort, file);
+            this.wss_fmp4 = new StreamURL("wss", host, sslPort, file);
+        }
+    }
+
+    public void setHls(String host, int port, int sslPort, String app, String stream, String callIdParam) {
+        String file = String.format("%s/%s/hls.m3u8%s", app, stream, callIdParam);
+        if (port > 0) {
+            this.hls = new StreamURL("http", host, port, file);
+            this.ws_hls = new StreamURL("ws", host, port, file);
+        }
+        if (sslPort > 0) {
+            this.https_hls = new StreamURL("https", host, sslPort, file);
+            this.wss_hls = new StreamURL("wss", host, sslPort, file);
+        }
+    }
+
+    public void setTs(String host, int port, int sslPort, String app, String stream, String callIdParam) {
+        String file = String.format("%s/%s.live.ts%s", app, stream, callIdParam);
+
+        if (port > 0) {
+            this.ts = new StreamURL("http", host, port, file);
+            this.ws_ts = new StreamURL("ws", host, port, file);
+        }
+        if (sslPort > 0) {
+            this.https_ts = new StreamURL("https", host, sslPort, file);
+            this.wss_ts = new StreamURL("wss", host, sslPort, file);
+        }
+    }
+
+    public void setRtc(String host, int port, int sslPort, String app, String stream, String callIdParam) {
+        String file = String.format("index/api/webrtc?app=%s&stream=%s&type=play%s", app, stream, callIdParam);
+        if (port > 0) {
+            this.rtc = new StreamURL("http", host, port, file);
+        }
+        if (sslPort > 0) {
+            this.rtcs = new StreamURL("https", host, sslPort, file);
+        }
+    }
+
+    public void channgeStreamIp(String localAddr) {
+        if (this.flv != null) {
+            this.flv.setHost(localAddr);
+        }
+        if (this.ws_flv != null ){
+            this.ws_flv.setHost(localAddr);
+        }
+        if (this.hls != null ) {
+            this.hls.setHost(localAddr);
+        }
+        if (this.ws_hls != null ) {
+            this.ws_hls.setHost(localAddr);
+        }
+        if (this.ts != null ) {
+            this.ts.setHost(localAddr);
+        }
+        if (this.ws_ts != null ) {
+            this.ws_ts.setHost(localAddr);
+        }
+        if (this.fmp4 != null ) {
+            this.fmp4.setHost(localAddr);
+        }
+        if (this.ws_fmp4 != null ) {
+            this.ws_fmp4.setHost(localAddr);
+        }
+        if (this.rtc != null ) {
+            this.rtc.setHost(localAddr);
+        }
+        if (this.https_flv != null) {
+            this.https_flv.setHost(localAddr);
+        }
+        if (this.wss_flv != null) {
+            this.wss_flv.setHost(localAddr);
+        }
+        if (this.https_hls != null) {
+            this.https_hls.setHost(localAddr);
+        }
+        if (this.wss_hls != null) {
+            this.wss_hls.setHost(localAddr);
+        }
+        if (this.wss_ts != null) {
+            this.wss_ts.setHost(localAddr);
+        }
+        if (this.https_fmp4 != null) {
+            this.https_fmp4.setHost(localAddr);
+        }
+        if (this.wss_fmp4 != null) {
+            this.wss_fmp4.setHost(localAddr);
+        }
+        if (this.rtcs != null) {
+            this.rtcs.setHost(localAddr);
+        }
+        if (this.rtsp != null) {
+            this.rtsp.setHost(localAddr);
+        }
+        if (this.rtsps != null) {
+            this.rtsps.setHost(localAddr);
+        }
+        if (this.rtmp != null) {
+            this.rtmp.setHost(localAddr);
+        }
+        if (this.rtmps != null) {
+            this.rtmps.setHost(localAddr);
+        }
     }
 
-    public void setWs_fmp4(String ws_fmp4) {
-        this.ws_fmp4 = ws_fmp4;
+
+    public static class TransactionInfo{
+        public String callId;
+        public String localTag;
+        public String remoteTag;
+        public String branch;
     }
 
-    public String getWs_hls() {
-        return ws_hls;
+    private TransactionInfo transactionInfo;
+
+    public String getApp() {
+        return app;
     }
 
-    public void setWs_hls(String ws_hls) {
-        this.ws_hls = ws_hls;
+    public void setApp(String app) {
+        this.app = app;
     }
 
-    public String getTs() {
-        return ts;
+    public String getDeviceID() {
+        return deviceID;
     }
 
-    public void setTs(String ts) {
-        this.ts = ts;
+    public void setDeviceID(String deviceID) {
+        this.deviceID = deviceID;
     }
 
-    public String getWs_ts() {
-        return ws_ts;
+    public String getChannelId() {
+        return channelId;
     }
 
-    public void setWs_ts(String ws_ts) {
-        this.ws_ts = ws_ts;
+    public void setChannelId(String channelId) {
+        this.channelId = channelId;
     }
 
     public String getStream() {
@@ -170,110 +354,125 @@ public class StreamInfo {
         this.stream = stream;
     }
 
-    public String getRtc() {
-        return rtc;
+    public String getIp() {
+        return ip;
     }
 
-    public void setRtc(String rtc) {
-        this.rtc = rtc;
+    public void setIp(String ip) {
+        this.ip = ip;
     }
 
-    public TransactionInfo getTransactionInfo() {
-        return transactionInfo;
+    public StreamURL getFlv() {
+        return flv;
     }
 
-    public void setTransactionInfo(TransactionInfo transactionInfo) {
-        this.transactionInfo = transactionInfo;
+    public StreamURL getHttps_flv() {
+        return https_flv;
     }
 
-    public String getMediaServerId() {
-        return mediaServerId;
+    public StreamURL getWs_flv() {
+        return ws_flv;
     }
 
-    public void setMediaServerId(String mediaServerId) {
-        this.mediaServerId = mediaServerId;
-    }
 
-    public String getHttps_flv() {
-        return https_flv;
+    public StreamURL getWss_flv() {
+        return wss_flv;
     }
 
-    public void setHttps_flv(String https_flv) {
-        this.https_flv = https_flv;
+    public StreamURL getFmp4() {
+        return fmp4;
     }
 
-    public String getWss_flv() {
-        return wss_flv;
+
+
+    public StreamURL getHttps_fmp4() {
+        return https_fmp4;
     }
 
-    public void setWss_flv(String wss_flv) {
-        this.wss_flv = wss_flv;
+    public StreamURL getWs_fmp4() {
+        return ws_fmp4;
     }
 
-    public String getWss_fmp4() {
+    public StreamURL getWss_fmp4() {
         return wss_fmp4;
     }
 
-    public void setWss_fmp4(String wss_fmp4) {
-        this.wss_fmp4 = wss_fmp4;
+    public StreamURL getHls() {
+        return hls;
     }
 
-    public String getWss_hls() {
+
+    public StreamURL getHttps_hls() {
+        return https_hls;
+    }
+
+    public StreamURL getWs_hls() {
+        return ws_hls;
+    }
+
+    public StreamURL getWss_hls() {
         return wss_hls;
     }
 
-    public void setWss_hls(String wss_hls) {
-        this.wss_hls = wss_hls;
+    public StreamURL getTs() {
+        return ts;
     }
 
-    public String getWss_ts() {
-        return wss_ts;
+
+    public StreamURL getHttps_ts() {
+        return https_ts;
     }
 
-    public void setWss_ts(String wss_ts) {
-        this.wss_ts = wss_ts;
+
+    public StreamURL getWs_ts() {
+        return ws_ts;
     }
 
-    public String getRtmps() {
-        return rtmps;
+
+    public StreamURL getWss_ts() {
+        return wss_ts;
     }
 
-    public void setRtmps(String rtmps) {
-        this.rtmps = rtmps;
+
+    public StreamURL getRtmp() {
+        return rtmp;
     }
 
-    public String getRtsps() {
-        return rtsps;
+    public StreamURL getRtmps() {
+        return rtmps;
     }
 
-    public void setRtsps(String rtsps) {
-        this.rtsps = rtsps;
+    public StreamURL getRtsp() {
+        return rtsp;
     }
 
-    public String getHttps_hls() {
-        return https_hls;
+    public StreamURL getRtsps() {
+        return rtsps;
     }
 
-    public void setHttps_hls(String https_hls) {
-        this.https_hls = https_hls;
+    public StreamURL getRtc() {
+        return rtc;
     }
 
-    public String getHttps_fmp4() {
-        return https_fmp4;
+    public StreamURL getRtcs() {
+        return rtcs;
     }
 
-    public void setHttps_fmp4(String https_fmp4) {
-        this.https_fmp4 = https_fmp4;
+    public String getMediaServerId() {
+        return mediaServerId;
     }
 
-    public String getHttps_ts() {
-        return https_ts;
+    public void setMediaServerId(String mediaServerId) {
+        this.mediaServerId = mediaServerId;
     }
 
-    public void setHttps_ts(String https_ts) {
-        this.https_ts = https_ts;
+    public Object getTracks() {
+        return tracks;
     }
 
+    public void setTracks(Object tracks) {
+        this.tracks = tracks;
+    }
 
     public String getStartTime() {
         return startTime;
@@ -299,27 +498,30 @@ public class StreamInfo {
         this.progress = progress;
     }
 
-    public String getIp() {
-        return ip;
-    }
-
-    public void setIp(String ip) {
-        this.ip = ip;
+    public boolean isPause() {
+        return pause;
     }
 
-    public String getRtcs() {
-        return rtcs;
+    public void setPause(boolean pause) {
+        this.pause = pause;
     }
 
-    public void setRtcs(String rtcs) {
-        this.rtcs = rtcs;
+    public TransactionInfo getTransactionInfo() {
+        return transactionInfo;
     }
 
-    public boolean isPause() {
-        return pause;
+    public void setTransactionInfo(TransactionInfo transactionInfo) {
+        this.transactionInfo = transactionInfo;
     }
 
-    public void setPause(boolean pause) {
-        this.pause = pause;
+    @Override
+    public StreamInfo clone() {
+        StreamInfo instance = null;
+        try{
+            instance = (StreamInfo)super.clone();
+        }catch(CloneNotSupportedException e) {
+            e.printStackTrace();
+        }
+        return instance;
     }
 }

+ 20 - 7
src/main/java/com/genersoft/iot/vmp/common/VersionPo.java

@@ -1,33 +1,38 @@
 package com.genersoft.iot.vmp.common;
 
-import com.alibaba.fastjson.annotation.JSONField;
+import com.alibaba.fastjson2.annotation.JSONField;
 
 public class VersionPo {
     /**
      * git的全版本号
      */
-    @JSONField(name="GIT-Revision")
+    @JSONField(name="GIT_Revision")
     private String GIT_Revision;
     /**
      * maven版本
      */
-    @JSONField(name = "Create-By")
+    @JSONField(name = "Create_By")
     private String Create_By;
     /**
      * git的分支
      */
-    @JSONField(name = "GIT-BRANCH")
+    @JSONField(name = "GIT_BRANCH")
     private String GIT_BRANCH;
     /**
      * git的url
      */
-    @JSONField(name = "GIT-URL")
+    @JSONField(name = "GIT_URL")
     private String GIT_URL;
     /**
      * 构建日期
      */
-    @JSONField(name = "BUILD-DATE")
+    @JSONField(name = "BUILD_DATE")
     private String BUILD_DATE;
+    /**
+     * 构建日期
+     */
+    @JSONField(name = "GIT_DATE")
+    private String GIT_DATE;
     /**
      * 项目名称 配合pom使用
      */
@@ -36,7 +41,7 @@ public class VersionPo {
     /**
      * git局部版本号
      */
-    @JSONField(name = "GIT-Revision-SHORT")
+    @JSONField(name = "GIT_Revision_SHORT")
     private String GIT_Revision_SHORT;
     /**
      * 项目的版本如2.0.1.0 配合pom使用
@@ -133,4 +138,12 @@ public class VersionPo {
     public String getBuild_Jdk() {
         return Build_Jdk;
     }
+
+    public String getGIT_DATE() {
+        return GIT_DATE;
+    }
+
+    public void setGIT_DATE(String GIT_DATE) {
+        this.GIT_DATE = GIT_DATE;
+    }
 }

+ 15 - 0
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java

@@ -72,6 +72,10 @@ public class VideoManagerConstants {
 
 	public static final String SYSTEM_INFO_NET_PREFIX = "VMP_SYSTEM_INFO_NET_";
 
+	public static final String SYSTEM_INFO_DISK_PREFIX = "VMP_SYSTEM_INFO_DISK_";
+
+	public static final String REGISTER_EXPIRE_TASK_KEY_PREFIX = "VMP_device_register_expire_";
+
 
 
 
@@ -138,4 +142,15 @@ public class VideoManagerConstants {
 	public static final String WVP_STREAM_GB_ID_PREFIX = "memberNo_";
 	public static final String WVP_STREAM_GPS_MSG_PREFIX = "WVP_STREAM_GPS_MSG_";
 
+	/**
+	 * Redis Const
+	 * 设备录像信息结果前缀
+	 */
+	public static final String REDIS_RECORD_INFO_RES_PRE = "GB_RECORD_INFO_RES_";
+	/**
+	 * Redis Const
+	 * 设备录像信息结果前缀
+	 */
+	public static final String REDIS_RECORD_INFO_RES_COUNT_PRE = "GB_RECORD_INFO_RES_COUNT:";
+
 }

+ 10 - 5
src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java

@@ -10,9 +10,12 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ObjectUtils;
 import org.springframework.web.filter.OncePerRequestFilter;
 
-import javax.servlet.*;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
 import javax.servlet.annotation.WebFilter;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -22,6 +25,7 @@ import java.io.IOException;
  * @author lin
  */
 @WebFilter(filterName = "ApiAccessFilter", urlPatterns = "/api/*", asyncSupported=true)
+@Component
 public class ApiAccessFilter extends OncePerRequestFilter {
 
     private final static Logger logger = LoggerFactory.getLogger(ApiAccessFilter.class);
@@ -47,10 +51,13 @@ public class ApiAccessFilter extends OncePerRequestFilter {
 
         filterChain.doFilter(servletRequest, servletResponse);
 
-        if (uriName != null && userSetting.getLogInDatebase()) {
+        if (uriName != null && userSetting != null && userSetting.getLogInDatebase() != null && userSetting.getLogInDatebase()) {
 
             LogDto logDto = new LogDto();
             logDto.setName(uriName);
+            if (ObjectUtils.isEmpty(username)) {
+                username = "";
+            }
             logDto.setUsername(username);
             logDto.setAddress(servletRequest.getRemoteAddr());
             logDto.setResult(HttpStatus.valueOf(servletResponse.getStatus()).toString());
@@ -59,9 +66,7 @@ public class ApiAccessFilter extends OncePerRequestFilter {
             logDto.setUri(servletRequest.getRequestURI());
             logDto.setCreateTime(DateUtil.getNow());
             logService.add(logDto);
-//            logger.warn("[Api Access]  [{}] [{}] [{}] [{}] [{}] {}ms",
-//                    uriName, servletRequest.getMethod(), servletRequest.getRequestURI(), servletRequest.getRemoteAddr(), HttpStatus.valueOf(servletResponse.getStatus()),
-//                    System.currentTimeMillis() - start);
+
 
         }
     }

+ 10 - 7
src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java

@@ -1,10 +1,7 @@
 package com.genersoft.iot.vmp.conf;
 
-import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
 import org.springframework.stereotype.Component;
@@ -101,12 +98,14 @@ public class DynamicTask {
         }
     }
 
-    public void stop(String key) {
-        if (futureMap.get(key) != null && !futureMap.get(key).isCancelled()) {
-            futureMap.get(key).cancel(false);
+    public boolean stop(String key) {
+        boolean result = false;
+        if (futureMap.get(key) != null && !futureMap.get(key).isCancelled() && !futureMap.get(key).isDone()) {
+            result = futureMap.get(key).cancel(false);
             futureMap.remove(key);
             runnableMap.remove(key);
         }
+        return result;
     }
 
     public boolean contains(String key) {
@@ -128,11 +127,15 @@ public class DynamicTask {
     public void execute(){
         if (futureMap.size() > 0) {
             for (String key : futureMap.keySet()) {
-                if (futureMap.get(key).isDone()) {
+                if (futureMap.get(key).isDone() || futureMap.get(key).isCancelled()) {
                     futureMap.remove(key);
                     runnableMap.remove(key);
                 }
             }
         }
     }
+
+    public boolean isAlive(String key) {
+        return futureMap.get(key) != null && !futureMap.get(key).isDone() && !futureMap.get(key).isCancelled();
+    }
 }

+ 16 - 5
src/main/java/com/genersoft/iot/vmp/conf/GlobalExceptionHandler.java

@@ -1,12 +1,12 @@
 package com.genersoft.iot.vmp.conf;
 
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
-import com.genersoft.iot.vmp.gb28181.event.alarm.AlarmEventListener;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
 import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.ResponseStatus;
@@ -32,6 +32,17 @@ public class GlobalExceptionHandler {
         return WVPResult.fail(ErrorCode.ERROR500.getCode(), e.getMessage());
     }
 
+    /**
+     * 默认异常处理
+     * @param e 异常
+     * @return 统一返回结果
+     */
+    @ExceptionHandler(IllegalStateException.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    public WVPResult<String> exceptionHandler(IllegalStateException e) {
+        return WVPResult.fail(ErrorCode.ERROR400);
+    }
+
 
     /**
      * 自定义异常处理, 处理controller中返回的错误
@@ -40,8 +51,8 @@ public class GlobalExceptionHandler {
      */
     @ExceptionHandler(ControllerException.class)
     @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
-    public WVPResult<String> exceptionHandler(ControllerException e) {
-        return WVPResult.fail(e.getCode(), e.getMsg());
+    public ResponseEntity<WVPResult<String>> exceptionHandler(ControllerException e) {
+        return new ResponseEntity<>(WVPResult.fail(e.getCode(), e.getMsg()), HttpStatus.OK);
     }
 
     /**
@@ -51,7 +62,7 @@ public class GlobalExceptionHandler {
      */
     @ExceptionHandler(BadCredentialsException.class)
     @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
-    public WVPResult<String> exceptionHandler(BadCredentialsException e) {
-        return WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMessage());
+    public ResponseEntity<WVPResult<String>> exceptionHandler(BadCredentialsException e) {
+        return new ResponseEntity<>(WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMessage()), HttpStatus.OK);
     }
 }

+ 2 - 2
src/main/java/com/genersoft/iot/vmp/conf/GlobalResponseAdvice.java

@@ -1,7 +1,7 @@
 package com.genersoft.iot.vmp.conf;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.support.spring.http.converter.FastJsonHttpMessageConverter;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
 import org.jetbrains.annotations.NotNull;

+ 5 - 5
src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java

@@ -2,14 +2,13 @@ package com.genersoft.iot.vmp.conf;
 
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.utils.DateUtil;
-import com.genersoft.iot.vmp.vmanager.gb28181.device.DeviceQuery;
+import org.junit.jupiter.api.Order;
 import com.genersoft.iot.vmp.vmanager.gb28181.aiLib.AiControl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 
 import java.net.InetAddress;
 import java.net.UnknownHostException;
@@ -17,6 +16,7 @@ import java.util.regex.Pattern;
 
 
 @Configuration("mediaConfig")
+@Order(0)
 public class MediaConfig{
 
     private final static Logger logger = LoggerFactory.getLogger(MediaConfig.class);
@@ -28,7 +28,7 @@ public class MediaConfig{
     @Value("${media.ip}")
     private String ip;
 
-    @Value("${media.hook-ip:${sip.ip}}")
+    @Value("${media.hook-ip:}")
     private String hookIp;
 
     @Value("${sip.ip}")
@@ -96,7 +96,7 @@ public class MediaConfig{
 
     public String getHookIp() {
         if (ObjectUtils.isEmpty(hookIp)){
-            return sipIp;
+            return sipIp.split(",")[0];
         }else {
             return hookIp;
         }
@@ -225,7 +225,7 @@ public class MediaConfig{
         mediaServerItem.setRtpPortRange(rtpPortRange);
         mediaServerItem.setSendRtpPortRange(sendRtpPortRange);
         mediaServerItem.setRecordAssistPort(recordAssistPort);
-        mediaServerItem.setHookAliveInterval(120);
+        mediaServerItem.setHookAliveInterval(30.00f);
 
         mediaServerItem.setCreateTime(DateUtil.getNow());
         mediaServerItem.setUpdateTime(DateUtil.getNow());

+ 0 - 1
src/main/java/com/genersoft/iot/vmp/conf/MediaStatusTimerTask.java

@@ -1,6 +1,5 @@
 package com.genersoft.iot.vmp.conf;
 
-import com.alibaba.fastjson.JSONObject;
 import org.springframework.scheduling.annotation.Scheduled;
 
 /**

+ 4 - 9
src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java

@@ -2,10 +2,10 @@ package com.genersoft.iot.vmp.conf;
 
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.IMediaServerService;
-import org.apache.catalina.connector.ClientAbortException;
 import org.apache.http.HttpHost;
 import org.apache.http.HttpRequest;
 import org.apache.http.HttpResponse;
+import org.junit.jupiter.api.Order;
 import org.mitre.dsmiley.httpproxy.ProxyServlet;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -15,11 +15,9 @@ import org.springframework.boot.web.servlet.ServletRegistrationBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.net.ConnectException;
 
@@ -28,6 +26,7 @@ import java.net.ConnectException;
  */
 @SuppressWarnings(value = {"rawtypes", "unchecked"})
 @Configuration
+@Order(1)
 public class ProxyServletConfig {
 
     private final static Logger logger = LoggerFactory.getLogger(ProxyServletConfig.class);
@@ -77,9 +76,7 @@ public class ProxyServletConfig {
             } catch (IOException ioException) {
                 if (ioException instanceof ConnectException) {
                     logger.error("zlm 连接失败");
-                } else if (ioException instanceof ClientAbortException) {
-                    logger.error("zlm: 用户已中断连接,代理终止");
-                } else {
+                }  else {
                     logger.error("zlm 代理失败: ", e);
                 }
             } catch (RuntimeException exception){
@@ -195,9 +192,7 @@ public class ProxyServletConfig {
             } catch (IOException ioException) {
                 if (ioException instanceof ConnectException) {
                     logger.error("录像服务 连接失败");
-                } else if (ioException instanceof ClientAbortException) {
-                    logger.error("录像服务:用户已中断连接,代理终止");
-                } else {
+                }else {
                     logger.error("录像服务 代理失败: ", e);
                 }
             } catch (RuntimeException exception){

+ 9 - 22
src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java

@@ -1,20 +1,18 @@
 package com.genersoft.iot.vmp.conf;
 
 
+import org.junit.jupiter.api.Order;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
+import org.springframework.util.ObjectUtils;
 
 @Component
 @ConfigurationProperties(prefix = "sip", ignoreInvalidFields = true)
+@Order(0)
 public class SipConfig {
 
 	private String ip;
 
-	/**
-	 * 默认使用 0.0.0.0
-	 */
-	private String monitorIp = "0.0.0.0";
-
 	private Integer port;
 
 	private String domain;
@@ -27,8 +25,6 @@ public class SipConfig {
 	
 	Integer ptzSpeed = 50;
 
-	Integer keepaliveTimeOut = 255;
-
 	Integer registerTimeInterval = 120;
 
 
@@ -38,10 +34,6 @@ public class SipConfig {
 		this.ip = ip;
 	}
 
-	public void setMonitorIp(String monitorIp) {
-		this.monitorIp = monitorIp;
-	}
-
 	public void setPort(Integer port) {
 		this.port = port;
 	}
@@ -62,18 +54,11 @@ public class SipConfig {
 		this.ptzSpeed = ptzSpeed;
 	}
 
-	public void setKeepaliveTimeOut(Integer keepaliveTimeOut) {
-		this.keepaliveTimeOut = keepaliveTimeOut;
-	}
 
 	public void setRegisterTimeInterval(Integer registerTimeInterval) {
 		this.registerTimeInterval = registerTimeInterval;
 	}
 
-	public String getMonitorIp() {
-		return monitorIp;
-	}
-
 	public String getIp() {
 		return ip;
 	}
@@ -102,10 +87,6 @@ public class SipConfig {
 		return ptzSpeed;
 	}
 
-	public Integer getKeepaliveTimeOut() {
-		return keepaliveTimeOut;
-	}
-
 	public Integer getRegisterTimeInterval() {
 		return registerTimeInterval;
 	}
@@ -117,7 +98,12 @@ public class SipConfig {
 	public void setAlarm(boolean alarm) {
 		this.alarm = alarm;
 	}
+	
+	public void getLocalIp(String deviceLocalIp) {
+		if (ObjectUtils.isEmpty(deviceLocalIp)) {
 
+		}
+	}
 	public String getMediaPath() {
 		return this.mediaPath;
 	}
@@ -125,4 +111,5 @@ public class SipConfig {
 	public void setMediaPath(String mediaPath) {
 		this.mediaPath = mediaPath;
 	}
+	
 }

+ 2 - 3
src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java

@@ -2,7 +2,6 @@ package com.genersoft.iot.vmp.conf;
 
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatformCatch;
-import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
 import com.genersoft.iot.vmp.service.IPlatformService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
@@ -19,7 +18,7 @@ import java.util.List;
  * @author lin
  */
 @Component
-@Order(value=3)
+@Order(value=13)
 public class SipPlatformRunner implements CommandLineRunner {
 
     @Autowired
@@ -47,7 +46,7 @@ public class SipPlatformRunner implements CommandLineRunner {
             parentPlatformCatch.setId(parentPlatform.getServerGBId());
             redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
             // 设置所有平台离线
-            platformService.offline(parentPlatform);
+            platformService.offline(parentPlatform, true);
             // 取消订阅
             sipCommanderForPlatform.unregister(parentPlatform, null, (eventResult)->{
                 platformService.login(parentPlatform);

+ 2 - 4
src/main/java/com/genersoft/iot/vmp/conf/SpringDocConfig.java

@@ -1,14 +1,11 @@
 package com.genersoft.iot.vmp.conf;
 
-import io.swagger.v3.oas.models.ExternalDocumentation;
 import io.swagger.v3.oas.models.OpenAPI;
 import io.swagger.v3.oas.models.info.Contact;
 import io.swagger.v3.oas.models.info.Info;
 import io.swagger.v3.oas.models.info.License;
-import io.swagger.v3.oas.models.media.StringSchema;
-import io.swagger.v3.oas.models.parameters.HeaderParameter;
+import org.junit.jupiter.api.Order;
 import org.springdoc.core.GroupedOpenApi;
-import org.springdoc.core.SpringDocConfigProperties;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -17,6 +14,7 @@ import org.springframework.context.annotation.Configuration;
  * @author lin
  */
 @Configuration
+@Order(1)
 public class SpringDocConfig {
 
     @Value("${doc.enabled: true}")

+ 5 - 2
src/main/java/com/genersoft/iot/vmp/conf/SystemInfoTimerTask.java

@@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -22,15 +23,17 @@ public class SystemInfoTimerTask {
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
 
-    @Scheduled(fixedRate = 1000)   //每1秒执行一次
+    @Scheduled(fixedRate = 2000)   //每1秒执行一次
     public void execute(){
         try {
             double cpuInfo = SystemInfoUtils.getCpuInfo();
             redisCatchStorage.addCpuInfo(cpuInfo);
             double memInfo = SystemInfoUtils.getMemInfo();
             redisCatchStorage.addMemInfo(memInfo);
-            Map<String, String> networkInterfaces = SystemInfoUtils.getNetworkInterfaces();
+            Map<String, Double> networkInterfaces = SystemInfoUtils.getNetworkInterfaces();
             redisCatchStorage.addNetInfo(networkInterfaces);
+            List<Map<String, Object>> diskInfo =SystemInfoUtils.getDiskInfo();
+            redisCatchStorage.addDiskInfo(diskInfo);
         } catch (InterruptedException e) {
             logger.error("[获取系统信息失败] {}", e.getMessage());
         }

+ 2 - 0
src/main/java/com/genersoft/iot/vmp/conf/ThreadPoolTaskConfig.java

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.conf;
 
+import org.junit.jupiter.api.Order;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.scheduling.annotation.EnableAsync;
@@ -12,6 +13,7 @@ import java.util.concurrent.ThreadPoolExecutor;
  * @author lin
  */
 @Configuration
+@Order(1)
 @EnableAsync(proxyTargetClass = true)
 public class ThreadPoolTaskConfig {
 

+ 103 - 0
src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.conf;
 
+import org.junit.jupiter.api.Order;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
 
@@ -11,6 +12,7 @@ import java.util.List;
  */
 @Component
 @ConfigurationProperties(prefix = "user-settings", ignoreInvalidFields = true)
+@Order(0)
 public class UserSetting {
 
     private Boolean savePositionHistory = Boolean.FALSE;
@@ -33,12 +35,31 @@ public class UserSetting {
 
     private Boolean usePushingAsStatus = Boolean.TRUE;
 
+    private Boolean useSourceIpAsStreamIp = Boolean.FALSE;
+
+    private Boolean sipUseSourceIpAsRemoteAddress = Boolean.FALSE;
+
+    private Boolean streamOnDemand = Boolean.TRUE;
+
+    private Boolean pushAuthority = Boolean.TRUE;
+
+    private Boolean gbSendStreamStrict = Boolean.FALSE;
+
+    private Boolean syncChannelOnDeviceOnline = Boolean.FALSE;
+
+    private Boolean sipLog = Boolean.FALSE;
+    private Boolean sendToPlatformsWhenIdLost = Boolean.FALSE;
+
+    private Boolean refuseChannelStatusChannelFormNotify = Boolean.FALSE;
+
     private String serverId = "000000";
 
     private String thirdPartyGBIdReg = "[\\s\\S]*";
 
     private List<String> interfaceAuthenticationExcludes = new ArrayList<>();
 
+    private List<String> allowedOrigins = new ArrayList<>();
+
     public Boolean getSavePositionHistory() {
         return savePositionHistory;
     }
@@ -146,4 +167,86 @@ public class UserSetting {
     public void setUsePushingAsStatus(Boolean usePushingAsStatus) {
         this.usePushingAsStatus = usePushingAsStatus;
     }
+
+    public Boolean getStreamOnDemand() {
+        return streamOnDemand;
+    }
+
+    public void setStreamOnDemand(Boolean streamOnDemand) {
+        this.streamOnDemand = streamOnDemand;
+    }
+
+    public Boolean getUseSourceIpAsStreamIp() {
+        return useSourceIpAsStreamIp;
+    }
+
+    public void setUseSourceIpAsStreamIp(Boolean useSourceIpAsStreamIp) {
+        this.useSourceIpAsStreamIp = useSourceIpAsStreamIp;
+    }
+
+    public Boolean getPushAuthority() {
+        return pushAuthority;
+    }
+
+    public void setPushAuthority(Boolean pushAuthority) {
+        this.pushAuthority = pushAuthority;
+    }
+
+    public Boolean getGbSendStreamStrict() {
+        return gbSendStreamStrict;
+    }
+
+    public void setGbSendStreamStrict(Boolean gbSendStreamStrict) {
+        this.gbSendStreamStrict = gbSendStreamStrict;
+    }
+
+    public Boolean getSyncChannelOnDeviceOnline() {
+        return syncChannelOnDeviceOnline;
+    }
+
+    public void setSyncChannelOnDeviceOnline(Boolean syncChannelOnDeviceOnline) {
+        this.syncChannelOnDeviceOnline = syncChannelOnDeviceOnline;
+    }
+
+    public Boolean getSipUseSourceIpAsRemoteAddress() {
+        return sipUseSourceIpAsRemoteAddress;
+    }
+
+    public void setSipUseSourceIpAsRemoteAddress(Boolean sipUseSourceIpAsRemoteAddress) {
+        this.sipUseSourceIpAsRemoteAddress = sipUseSourceIpAsRemoteAddress;
+    }
+
+    public Boolean getSipLog() {
+        return sipLog;
+    }
+
+    public void setSipLog(Boolean sipLog) {
+        this.sipLog = sipLog;
+    }
+
+    public List<String> getAllowedOrigins() {
+        return allowedOrigins;
+    }
+
+    public void setAllowedOrigins(List<String> allowedOrigins) {
+        this.allowedOrigins = allowedOrigins;
+    }
+
+    public Boolean getSendToPlatformsWhenIdLost() {
+        return sendToPlatformsWhenIdLost;
+    }
+
+    public void setSendToPlatformsWhenIdLost(Boolean sendToPlatformsWhenIdLost) {
+        this.sendToPlatformsWhenIdLost = sendToPlatformsWhenIdLost;
+    }
+
+    public Boolean getRefuseChannelStatusChannelFormNotify() {
+        return refuseChannelStatusChannelFormNotify;
+    }
+
+    public void setRefuseChannelStatusChannelFormNotify(Boolean refuseChannelStatusChannelFormNotify) {
+        this.refuseChannelStatusChannelFormNotify = refuseChannelStatusChannelFormNotify;
+    }
+
+
 }

+ 2 - 0
src/main/java/com/genersoft/iot/vmp/conf/VersionConfig.java

@@ -1,10 +1,12 @@
 package com.genersoft.iot.vmp.conf;
 
+import org.junit.jupiter.api.Order;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
 
 @Component
 @ConfigurationProperties(prefix = "version")
+@Order(0)
 public class VersionConfig {
 
     private String version;

+ 1 - 0
src/main/java/com/genersoft/iot/vmp/conf/VersionInfo.java

@@ -19,6 +19,7 @@ public class VersionInfo {
         versionPo.setBUILD_DATE(gitUtil.getBuildDate());
         versionPo.setGIT_Revision_SHORT(gitUtil.getCommitIdShort());
         versionPo.setVersion(gitUtil.getBuildVersion());
+        versionPo.setGIT_DATE(gitUtil.getCommitTime());
 
         return versionPo;
     }

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/conf/WVPTimerTask.java

@@ -1,6 +1,6 @@
 package com.genersoft.iot.vmp.conf;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import org.springframework.beans.factory.annotation.Autowired;

+ 0 - 2
src/main/java/com/genersoft/iot/vmp/conf/exception/SsrcTransactionNotFoundException.java

@@ -1,7 +1,5 @@
 package com.genersoft.iot.vmp.conf.exception;
 
-import com.sun.javafx.binding.StringFormatter;
-
 /**
  * @author lin
  */

+ 3 - 3
src/main/java/com/genersoft/iot/vmp/conf/redis/RedisConfig.java

@@ -1,13 +1,13 @@
 package com.genersoft.iot.vmp.conf.redis;
 
 
-import com.alibaba.fastjson.parser.ParserConfig;
 import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.service.redisMsg.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.listener.PatternTopic;
@@ -24,6 +24,7 @@ import com.genersoft.iot.vmp.utils.redis.FastJsonRedisSerializer;
  * 
  */
 @Configuration
+@Order(value=1)
 public class RedisConfig extends CachingConfigurerSupport {
 
 	@Autowired
@@ -55,8 +56,7 @@ public class RedisConfig extends CachingConfigurerSupport {
 		// value值的序列化采用fastJsonRedisSerializer
 		redisTemplate.setValueSerializer(fastJsonRedisSerializer);
 		redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
-		// 全局开启AutoType,不建议使用
-		 ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
+
 		// key的序列化采用StringRedisSerializer
 		redisTemplate.setKeySerializer(new StringRedisSerializer());
 		redisTemplate.setHashKeySerializer(new StringRedisSerializer());

+ 10 - 12
src/main/java/com/genersoft/iot/vmp/conf/security/AnonymousAuthenticationEntryPoint.java

@@ -1,11 +1,11 @@
 package com.genersoft.iot.vmp.conf.security;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSONObject;
+import com.genersoft.iot.vmp.conf.security.dto.JwtUser;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
-import org.apache.poi.hssf.eventmodel.ERFListener;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.stereotype.Component;
 
@@ -18,17 +18,15 @@ import java.io.IOException;
  * @author lin
  */
 @Component
-public class AnonymousAuthenticationEntryPoint implements AuthenticationEntryPoint {
-
-    private final static Logger logger = LoggerFactory.getLogger(DefaultUserDetailsServiceImpl.class);
+public class    AnonymousAuthenticationEntryPoint implements AuthenticationEntryPoint {
 
     @Override
     public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
-        // 允许跨域
-        response.setHeader("Access-Control-Allow-Origin", "*");
-        // 允许自定义请求头token(允许head跨域)
-        response.setHeader("Access-Control-Allow-Headers", "token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
-        response.setHeader("Content-type", "application/json;charset=UTF-8");
+        String jwt = request.getHeader(JwtUtils.getHeader());
+        JwtUser jwtUser = JwtUtils.verifyToken(jwt);
+        String username = jwtUser.getUserName();
+        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, jwtUser.getPassword() );
+        SecurityContextHolder.getContext().setAuthentication(token);
         JSONObject jsonObject = new JSONObject();
         jsonObject.put("code", ErrorCode.ERROR401.getCode());
         jsonObject.put("msg", ErrorCode.ERROR401.getMsg());

+ 9 - 6
src/main/java/com/genersoft/iot/vmp/conf/security/DefaultUserDetailsServiceImpl.java

@@ -1,7 +1,9 @@
 package com.genersoft.iot.vmp.conf.security;
 
-import java.time.LocalDateTime;
-
+import com.alibaba.excel.util.StringUtils;
+import com.genersoft.iot.vmp.conf.security.dto.LoginUser;
+import com.genersoft.iot.vmp.service.IUserService;
+import com.genersoft.iot.vmp.storager.dao.dto.User;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -10,10 +12,7 @@ import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.stereotype.Component;
 
-import com.alibaba.excel.util.StringUtils;
-import com.genersoft.iot.vmp.conf.security.dto.LoginUser;
-import com.genersoft.iot.vmp.service.IUserService;
-import com.genersoft.iot.vmp.storager.dao.dto.User;
+import java.time.LocalDateTime;
 
 /**
  * 用户登录认证逻辑
@@ -45,4 +44,8 @@ public class DefaultUserDetailsServiceImpl implements UserDetailsService {
     }
 
 
+
+
+
+
 }

+ 11 - 2
src/main/java/com/genersoft/iot/vmp/conf/security/LoginSuccessHandler.java

@@ -21,7 +21,16 @@ public class LoginSuccessHandler implements AuthenticationSuccessHandler {
 
     @Override
     public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
-        String username = request.getParameter("username");
-        logger.info("[登录成功] - [{}]", username);
+//        String username = request.getParameter("username");
+//        httpServletResponse.setContentType("application/json;charset=UTF-8");
+//        // 生成JWT,并放置到请求头中
+//        String jwt = JwtUtils.createToken(authentication.getName(), );
+//        httpServletResponse.setHeader(JwtUtils.getHeader(), jwt);
+//        ServletOutputStream outputStream = httpServletResponse.getOutputStream();
+//        outputStream.write(JSON.toJSONString(ErrorCode.SUCCESS).getBytes(StandardCharsets.UTF_8));
+//        outputStream.flush();
+//        outputStream.close();
+
+//        logger.info("[登录成功] - [{}]", username);
     }
 }

+ 6 - 1
src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.conf.security;
 
 import com.genersoft.iot.vmp.conf.security.dto.LoginUser;
+import com.genersoft.iot.vmp.storager.dao.dto.User;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
@@ -9,6 +10,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 
 import javax.security.sasl.AuthenticationException;
+import java.time.LocalDateTime;
 
 public class SecurityUtils {
 
@@ -25,9 +27,12 @@ public class SecurityUtils {
     public static LoginUser login(String username, String password, AuthenticationManager authenticationManager) throws AuthenticationException {
         //使用security框架自带的验证token生成器  也可以自定义。
         UsernamePasswordAuthenticationToken token =new UsernamePasswordAuthenticationToken(username,password);
+        //认证 如果失败,这里会自动异常后返回,所以这里不需要判断返回值是否为空,确定是否登录成功
         Authentication authenticate = authenticationManager.authenticate(token);
-        SecurityContextHolder.getContext().setAuthentication(authenticate);
         LoginUser user = (LoginUser) authenticate.getPrincipal();
+
+        SecurityContextHolder.getContext().setAuthentication(token);
+
         return user;
     }
 

+ 56 - 63
src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.conf.security;
 
 import com.genersoft.iot.vmp.conf.UserSetting;
+import org.junit.jupiter.api.Order;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -14,9 +15,16 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.builders.WebSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.CorsUtils;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
 
-import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
 
 /**
  * 配置Spring Security
@@ -25,6 +33,7 @@ import java.util.List;
 @Configuration
 @EnableWebSecurity
 @EnableGlobalMethodSecurity(prePostEnabled = true)
+@Order(1)
 public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
     private final static Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);
@@ -54,22 +63,9 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
      */
     @Autowired
     private AnonymousAuthenticationEntryPoint anonymousAuthenticationEntryPoint;
-//    /**
-//     * 超时处理
-//     */
-//    @Autowired
-//    private InvalidSessionHandler invalidSessionHandler;
-
-//    /**
-//     * 顶号处理
-//     */
-//    @Autowired
-//    private SessionInformationExpiredHandler sessionInformationExpiredHandler;
-//    /**
-//     * 登录用户没有权限访问资源
-//     */
-//    @Autowired
-//    private LoginUserAccessDeniedHandler accessDeniedHandler;
+    @Autowired
+    private JwtAuthenticationFilter jwtAuthenticationFilter;
+
 
 
     /**
@@ -81,29 +77,19 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         if (!userSetting.isInterfaceAuthentication()) {
             web.ignoring().antMatchers("**");
         }else {
+            ArrayList<String> matchers = new ArrayList<>();
+            matchers.add("/");
+            matchers.add("/#/**");
+            matchers.add("/static/**");
+            matchers.add("/index.html");
+            matchers.add("/doc.html");
+            matchers.add("/webjars/**");
+            matchers.add("/swagger-resources/**");
+            matchers.add("/v3/api-docs/**");
+            matchers.add("/js/**");
+            matchers.addAll(userSetting.getInterfaceAuthenticationExcludes());
             // 可以直接访问的静态数据
-            web.ignoring()
-                    .antMatchers("/")
-                    .antMatchers("/#/**")
-                    .antMatchers("/static/**")
-                    .antMatchers("/index.html")
-                    .antMatchers("/doc.html") // "/webjars/**", "/swagger-resources/**", "/v3/api-docs/**"
-                    .antMatchers("/webjars/**")
-                    .antMatchers("/swagger-resources/**")
-                    .antMatchers("/v3/api-docs/**")
-                    .antMatchers("/favicon.ico")
-                    .antMatchers("/libImages/**")
-                    .antMatchers("/js/**");
-
-            List<String> interfaceAuthenticationExcludes = userSetting.getInterfaceAuthenticationExcludes();
-            for (String interfaceAuthenticationExclude : interfaceAuthenticationExcludes) {
-                if (interfaceAuthenticationExclude.split("/").length < 4 ) {
-                    logger.warn("{}不满足两级目录,已忽略", interfaceAuthenticationExclude);
-                }else {
-                    web.ignoring().antMatchers(interfaceAuthenticationExclude);
-                }
-
-            }
+            web.ignoring().antMatchers(matchers.toArray(new String[0]));
         }
     }
 
@@ -126,36 +112,43 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
     @Override
     protected void configure(HttpSecurity http) throws Exception {
-        http.cors().and().csrf().disable();
-        // 设置允许添加静态文件
-        http.headers().contentTypeOptions().disable();
-        http.authorizeRequests()
-                // 放行接口
-                .antMatchers("/api/user/login","/index/hook/**","/aiLib/**","/aiLib/mFile/**").permitAll()
-                // 除上面外的所有请求全部需要鉴权认证
+        http.headers().contentTypeOptions().disable()
+                .and().cors().configurationSource(configurationSource())
+                .and().csrf().disable()
+                .sessionManagement()
+                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+
+                // 配置拦截规则
+                .and()
+                .authorizeRequests()
+                .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
+                .antMatchers(userSetting.getInterfaceAuthenticationExcludes().toArray(new String[0])).permitAll()
+                .antMatchers("/api/user/login","/index/hook/**").permitAll()
                 .anyRequest().authenticated()
-                // 异常处理(权限拒绝、登录失效等)
-                .and().exceptionHandling()
-                //匿名用户访问无权限资源时的异常处理
+                // 异常处理
+                .and()
+                .exceptionHandling()
                 .authenticationEntryPoint(anonymousAuthenticationEntryPoint)
-//                .accessDeniedHandler(accessDeniedHandler)//登录用户没有权限访问资源
-                // 登入 允许所有用户
-                .and().formLogin().permitAll()
-                //登录成功处理逻辑
-                .successHandler(loginSuccessHandler)
-                //登录失败处理逻辑
-                .failureHandler(loginFailureHandler)
-                // 登出
                 .and().logout().logoutUrl("/api/user/logout").permitAll()
-                //登出成功处理逻辑
                 .logoutSuccessHandler(logoutHandler)
-                .deleteCookies("JSESSIONID")
-                // 会话管理
-//                .and().sessionManagement().invalidSessionStrategy(invalidSessionHandler) // 超时处理
-//                .maximumSessions(1)//同一账号同时登录最大用户数
-//                .expiredSessionStrategy(sessionInformationExpiredHandler) // 顶号处理
         ;
+        http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
+
+    }
 
+    CorsConfigurationSource configurationSource(){
+        // 配置跨域
+        CorsConfiguration corsConfiguration = new CorsConfiguration();
+        corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
+        corsConfiguration.setAllowedMethods(Arrays.asList("*"));
+        corsConfiguration.setMaxAge(3600L);
+        corsConfiguration.setAllowCredentials(true);
+        corsConfiguration.setAllowedOrigins(userSetting.getAllowedOrigins());
+        corsConfiguration.setExposedHeaders(Arrays.asList(JwtUtils.getHeader()));
+
+        UrlBasedCorsConfigurationSource url = new UrlBasedCorsConfigurationSource();
+        url.registerCorsConfiguration("/**",corsConfiguration);
+        return url;
     }
 
     /**

+ 9 - 0
src/main/java/com/genersoft/iot/vmp/conf/security/dto/LoginUser.java

@@ -19,6 +19,8 @@ public class LoginUser implements UserDetails, CredentialsContainer {
      */
     private User user;
 
+    private String accessToken;
+
 
     /**
      * 登录时间
@@ -99,4 +101,11 @@ public class LoginUser implements UserDetails, CredentialsContainer {
     }
 
 
+    public String getAccessToken() {
+        return accessToken;
+    }
+
+    public void setAccessToken(String accessToken) {
+        this.accessToken = accessToken;
+    }
 }

+ 3 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/GBEventSubscribe.java

@@ -1,7 +1,8 @@
 package com.genersoft.iot.vmp.gb28181;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.media.zlm.dto.HookType;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.scheduling.annotation.Scheduled;
@@ -23,7 +24,7 @@ public class GBEventSubscribe {
         void response(int code, JSONObject response);
     }
     public interface InviteEvent {
-        void response(int code, JSONObject response,ServerTransaction serverTransaction);
+        void response(int code, JSONObject response, SIPRequest request);
     }
 
     private Map<String, Map<IGBHookSubscribe, GBEventSubscribe.Event>> allSubscribes = new ConcurrentHashMap<>();

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/GBHookSubscribeFactory.java

@@ -1,6 +1,6 @@
 package com.genersoft.iot.vmp.gb28181;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSONObject;
 
 public class GBHookSubscribeFactory {
 

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/HookSubscribeForKey.java

@@ -1,6 +1,6 @@
 package com.genersoft.iot.vmp.gb28181;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.media.zlm.dto.HookType;
 import com.genersoft.iot.vmp.media.zlm.dto.IHookSubscribe;
 

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/IGBHookSubscribe.java

@@ -1,6 +1,6 @@
 package com.genersoft.iot.vmp.gb28181;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSONObject;
 
 import java.time.Instant;
 

+ 107 - 56
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.gb28181;
 
 import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.conf.DefaultProperties;
 import com.genersoft.iot.vmp.gb28181.transmit.ISIPProcessorObserver;
 import gov.nist.javax.sip.SipProviderImpl;
@@ -8,16 +9,18 @@ import gov.nist.javax.sip.SipStackImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.DependsOn;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ObjectUtils;
 
 import javax.sip.*;
-import java.util.Properties;
-import java.util.TooManyListenersException;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
 
-@Configuration
-public class SipLayer{
+@Component
+@Order(value=10)
+public class SipLayer implements CommandLineRunner {
 
 	private final static Logger logger = LoggerFactory.getLogger(SipLayer.class);
 
@@ -27,70 +30,118 @@ public class SipLayer{
 	@Autowired
 	private ISIPProcessorObserver sipProcessorObserver;
 
-	private SipStackImpl sipStack;
+	@Autowired
+	private UserSetting userSetting;
+
+	private final Map<String, SipProviderImpl> tcpSipProviderMap = new ConcurrentHashMap<>();
+	private final Map<String, SipProviderImpl> udpSipProviderMap = new ConcurrentHashMap<>();
 
 	private SipFactory sipFactory;
 
+	@Override
+	public void run(String... args) {
+		List<String> monitorIps = new ArrayList<>();
+		// 使用逗号分割多个ip
+		String separator = ",";
+		if (sipConfig.getIp().indexOf(separator) > 0) {
+			String[] split = sipConfig.getIp().split(separator);
+			monitorIps.addAll(Arrays.asList(split));
+		}else {
+			monitorIps.add(sipConfig.getIp());
+		}
 
-	@Bean("sipFactory")
-	SipFactory createSipFactory() {
 		sipFactory = SipFactory.getInstance();
 		sipFactory.setPathName("gov.nist");
-		return sipFactory;
-	}
-	
-	@Bean("sipStack")
-	@DependsOn({"sipFactory"})
-	SipStackImpl createSipStack() throws PeerUnavailableException {
-		sipStack = ( SipStackImpl )sipFactory.createSipStack(DefaultProperties.getProperties(sipConfig.getMonitorIp(), false));
-		return sipStack;
+		if (monitorIps.size() > 0) {
+			for (String monitorIp : monitorIps) {
+				addListeningPoint(monitorIp, sipConfig.getPort());
+			}
+			if (udpSipProviderMap.size() + tcpSipProviderMap.size() == 0) {
+				System.exit(1);
+			}
+		}
 	}
 
-	@Bean(name = "tcpSipProvider")
-	@DependsOn("sipStack")
-	SipProviderImpl startTcpListener() {
-		ListeningPoint tcpListeningPoint = null;
-		SipProviderImpl tcpSipProvider  = null;
+	private void addListeningPoint(String monitorIp, int port){
+		SipStackImpl sipStack;
+		try {
+			sipStack = (SipStackImpl)sipFactory.createSipStack(DefaultProperties.getProperties(monitorIp, userSetting.getSipLog()));
+		} catch (PeerUnavailableException e) {
+			logger.error("[Sip Server] SIP服务启动失败, 监听地址{}失败,请检查ip是否正确", monitorIp);
+			return;
+		}
+
 		try {
-			tcpListeningPoint = sipStack.createListeningPoint(sipConfig.getMonitorIp(), sipConfig.getPort(), "TCP");
-			tcpSipProvider = (SipProviderImpl)sipStack.createSipProvider(tcpListeningPoint);
+			ListeningPoint tcpListeningPoint = sipStack.createListeningPoint(monitorIp, port, "TCP");
+			SipProviderImpl tcpSipProvider = (SipProviderImpl)sipStack.createSipProvider(tcpListeningPoint);
+
 			tcpSipProvider.setDialogErrorsAutomaticallyHandled();
 			tcpSipProvider.addSipListener(sipProcessorObserver);
-			logger.info("[Sip Server] TCP 启动成功 {}:{}", sipConfig.getMonitorIp(), sipConfig.getPort());
-		} catch (TransportNotSupportedException e) {
-			e.printStackTrace();
-		} catch (InvalidArgumentException e) {
-			logger.error("[Sip Server]  无法使用 [ {}:{} ]作为SIP[ TCP ]服务,可排查: 1. sip.monitor-ip 是否为本机网卡IP; 2. sip.port 是否已被占用"
-					, sipConfig.getMonitorIp(), sipConfig.getPort());
-		} catch (TooManyListenersException e) {
-			e.printStackTrace();
-		} catch (ObjectInUseException e) {
-			e.printStackTrace();
+			tcpSipProviderMap.put(monitorIp, tcpSipProvider);
+
+			logger.info("[Sip Server] tcp://{}:{} 启动成功", monitorIp, port);
+		} catch (TransportNotSupportedException
+				 | TooManyListenersException
+				 | ObjectInUseException
+				 | InvalidArgumentException e) {
+			logger.error("[Sip Server] tcp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确"
+					, monitorIp, port);
 		}
-		return tcpSipProvider;
-	}
-	
-	@Bean(name = "udpSipProvider")
-	@DependsOn("sipStack")
-	SipProviderImpl startUdpListener() {
-		ListeningPoint udpListeningPoint = null;
-		SipProviderImpl udpSipProvider = null;
+
 		try {
-			udpListeningPoint = sipStack.createListeningPoint(sipConfig.getMonitorIp(), sipConfig.getPort(), "UDP");
-			udpSipProvider = (SipProviderImpl)sipStack.createSipProvider(udpListeningPoint);
+			ListeningPoint udpListeningPoint = sipStack.createListeningPoint(monitorIp, port, "UDP");
+
+			SipProviderImpl udpSipProvider = (SipProviderImpl)sipStack.createSipProvider(udpListeningPoint);
 			udpSipProvider.addSipListener(sipProcessorObserver);
-		} catch (TransportNotSupportedException e) {
-			e.printStackTrace();
-		} catch (InvalidArgumentException e) {
-			logger.error("[Sip Server]  无法使用 [ {}:{} ]作为SIP[ UDP ]服务,可排查: 1. sip.monitor-ip 是否为本机网卡IP; 2. sip.port 是否已被占用"
-					, sipConfig.getMonitorIp(), sipConfig.getPort());
-		} catch (TooManyListenersException e) {
-			e.printStackTrace();
-		} catch (ObjectInUseException e) {
-			e.printStackTrace();
+
+			udpSipProviderMap.put(monitorIp, udpSipProvider);
+
+			logger.info("[Sip Server] udp://{}:{} 启动成功", monitorIp, port);
+		} catch (TransportNotSupportedException
+				 | TooManyListenersException
+				 | ObjectInUseException
+				 | InvalidArgumentException e) {
+			logger.error("[Sip Server] udp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确"
+					, monitorIp, port);
 		}
-		logger.info("[Sip Server] UDP 启动成功 {}:{}", sipConfig.getMonitorIp(), sipConfig.getPort());
-		return udpSipProvider;
 	}
 
+	public SipFactory getSipFactory() {
+		return sipFactory;
+	}
+
+	public SipProviderImpl getUdpSipProvider(String ip) {
+		if (ObjectUtils.isEmpty(ip)) {
+			return null;
+		}
+		return udpSipProviderMap.get(ip);
+	}
+
+	public SipProviderImpl getUdpSipProvider() {
+		if (udpSipProviderMap.size() != 1) {
+			return null;
+		}
+		return udpSipProviderMap.values().stream().findFirst().get();
+	}
+
+	public SipProviderImpl getTcpSipProvider() {
+		if (tcpSipProviderMap.size() != 1) {
+			return null;
+		}
+		return tcpSipProviderMap.values().stream().findFirst().get();
+	}
+
+	public SipProviderImpl getTcpSipProvider(String ip) {
+		if (ObjectUtils.isEmpty(ip)) {
+			return null;
+		}
+		return tcpSipProviderMap.get(ip);
+	}
+
+	public String getLocalIp(String deviceLocalIp) {
+		if (!ObjectUtils.isEmpty(deviceLocalIp)) {
+			return deviceLocalIp;
+		}
+		return getUdpSipProvider().getListeningPoint().getIPAddress();
+	}
 }

+ 9 - 63
src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java

@@ -25,10 +25,9 @@
  */
 package com.genersoft.iot.vmp.gb28181.auth;
 
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.time.Instant;
-import java.util.Random;
+import gov.nist.core.InternalErrorHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import javax.sip.address.URI;
 import javax.sip.header.AuthorizationHeader;
@@ -36,10 +35,10 @@ import javax.sip.header.HeaderFactory;
 import javax.sip.header.WWWAuthenticateHeader;
 import javax.sip.message.Request;
 import javax.sip.message.Response;
-
-import gov.nist.core.InternalErrorHandler;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.time.Instant;
+import java.util.Random;
 
 /**
  * Implements the HTTP digest authentication method server side functionality.
@@ -201,12 +200,13 @@ public class DigestServerAuthenticationHelper  {
         // String ncStr = new DecimalFormat("00000000").format(Integer.parseInt(nc + "", 16));
 
         String A1 = username + ":" + realm + ":" + pass;
+
         String A2 = request.getMethod().toUpperCase() + ":" + uri.toString();
+
         byte mdbytes[] = messageDigest.digest(A1.getBytes());
         String HA1 = toHexString(mdbytes);
         logger.debug("A1: " + A1);
         logger.debug("A2: " + A2);
-
         mdbytes = messageDigest.digest(A2.getBytes());
         String HA2 = toHexString(mdbytes);
         logger.debug("HA1: " + HA1);
@@ -238,58 +238,4 @@ public class DigestServerAuthenticationHelper  {
 
     }
 
-//     public static void main(String[] args) throws NoSuchAlgorithmException {
-//         String realm = "3402000000";
-//         String username = "44010000001180008012";
-
-
-//         String nonce = "07cab60999fbf643264ace27d3b7de8b";
-//         String uri = "sip:34020000002000000001@3402000000";
-//         // qop 保护质量 包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略
-//         String qop = "auth";
-
-//         // 客户端随机数,这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。
-//         // 这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护
-//         //String cNonce = authHeader.getCNonce();
-
-//         // nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量
-//         int nc = 1;
-//         String ncStr = new DecimalFormat("00000000").format(nc);
-// //        String ncStr = new DecimalFormat("00000000").format(Integer.parseInt(nc + "", 16));
-//         MessageDigest messageDigest = MessageDigest.getInstance(DEFAULT_ALGORITHM);
-//         String A1 = username + ":" + realm + ":" + "12345678";
-//         String A2 = "REGISTER" + ":" + uri;
-//         byte mdbytes[] = messageDigest.digest(A1.getBytes());
-//         String HA1 = toHexString(mdbytes);
-//         System.out.println("A1: " + A1);
-//         System.out.println("A2: " + A2);
-
-//         mdbytes = messageDigest.digest(A2.getBytes());
-//         String HA2 = toHexString(mdbytes);
-//         System.out.println("HA1: " + HA1);
-//         System.out.println("HA2: " + HA2);
-//         String cnonce = "0a4f113b";
-//         System.out.println("nonce: " + nonce);
-//         System.out.println("nc: " + ncStr);
-//         System.out.println("cnonce: " + cnonce);
-//         System.out.println("qop: " + qop);
-//         String KD = HA1 + ":" + nonce;
-
-//         if (qop != null && qop.equals("auth") ) {
-//             if (nc != -1) {
-//                 KD += ":" + ncStr;
-//             }
-//             if (cnonce != null) {
-//                 KD += ":" + cnonce;
-//             }
-//             KD += ":" + qop;
-//         }
-//         KD += ":" + HA2;
-//         System.out.println("KD: " + KD);
-//         mdbytes = messageDigest.digest(KD.getBytes());
-//         String mdString = toHexString(mdbytes);
-//         System.out.println("mdString: " + mdString);
-//         String response = "4f0507d4b87cdecff04bdaf4c96348f0";
-//         System.out.println("response: " + response);
-//     }
 }

+ 13 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/bean/AlarmChannelMessage.java

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.bean;
 
+
 /**
  * 通过redis分发报警消息
  */
@@ -8,12 +9,14 @@ public class AlarmChannelMessage {
      * 国标编号
      */
     private String gbId;
-
     /**
      * 报警编号
      */
     private int alarmSn;
-
+    /**
+     * 告警类型
+     */
+    private int alarmType;
 
     /**
      * 报警描述
@@ -36,6 +39,14 @@ public class AlarmChannelMessage {
         this.alarmSn = alarmSn;
     }
 
+    public int getAlarmType() {
+        return alarmType;
+    }
+
+    public void setAlarmType(int alarmType) {
+        this.alarmType = alarmType;
+    }
+
     public String getAlarmDescription() {
         return alarmDescription;
     }

+ 51 - 6
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java

@@ -99,12 +99,6 @@ public class Device {
 	 */
 	@Schema(description = "心跳间隔")
 	private int keepaliveIntervalTime;
-	public int getKeepaliveIntervalTime() {
-		return keepaliveIntervalTime;
-	}
-	public void setKeepaliveIntervalTime(int keepaliveIntervalTime) {
-		this.keepaliveIntervalTime = keepaliveIntervalTime;
-	}
 
 	/**
 	 * 通道个数
@@ -184,6 +178,18 @@ public class Device {
 	@Schema(description = "树类型 国标规定了两种树的展现方式 行政区划:CivilCode 和业务分组:BusinessGroup")
 	private String treeType;
 
+	@Schema(description = "密码")
+	private String password;
+
+	@Schema(description = "收流IP")
+	private String sdpIp;
+
+	@Schema(description = "SIP交互IP(设备访问平台的IP)")
+	private String localIp;
+
+	@Schema(description = "是否作为消息通道")
+	private boolean asMessageChannel;
+
 
 	public String getDeviceId() {
 		return deviceId;
@@ -393,4 +399,43 @@ public class Device {
 		this.treeType = treeType;
 	}
 
+	public String getPassword() {
+		return password;
+	}
+
+	public void setPassword(String password) {
+		this.password = password;
+	}
+
+	public String getSdpIp() {
+		return sdpIp;
+	}
+
+	public void setSdpIp(String sdpIp) {
+		this.sdpIp = sdpIp;
+	}
+
+	public String getLocalIp() {
+		return localIp;
+	}
+
+	public void setLocalIp(String localIp) {
+		this.localIp = localIp;
+	}
+
+	public int getKeepaliveIntervalTime() {
+		return keepaliveIntervalTime;
+	}
+
+	public void setKeepaliveIntervalTime(int keepaliveIntervalTime) {
+		this.keepaliveIntervalTime = keepaliveIntervalTime;
+	}
+
+	public boolean isAsMessageChannel() {
+		return asMessageChannel;
+	}
+
+	public void setAsMessageChannel(boolean asMessageChannel) {
+		this.asMessageChannel = asMessageChannel;
+	}
 }

+ 14 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarmMethod.java

@@ -37,4 +37,18 @@ public enum DeviceAlarmMethod {
     public int getVal() {
         return val;
     }
+
+    /**
+     * 查询是否匹配类型
+     * @param code
+     * @return
+     */
+    public static DeviceAlarmMethod typeOf(int code) {
+        for (DeviceAlarmMethod item : DeviceAlarmMethod.values()) {
+            if (code==item.getVal()) {
+                return item;
+            }
+        }
+        return null;
+    }
 }

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamInfo.java

@@ -1,6 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.bean;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 
 public class InviteStreamInfo {

+ 11 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java

@@ -189,6 +189,9 @@ public class ParentPlatform {
     @Schema(description = "树类型 国标规定了两种树的展现方式 行政区划 CivilCode 和业务分组:BusinessGrou")
     private String treeType;
 
+    @Schema(description = "是否作为消息通道")
+    private boolean asMessageChannel;
+
     public Integer getId() {
         return id;
     }
@@ -428,4 +431,12 @@ public class ParentPlatform {
     public void setTreeType(String treeType) {
         this.treeType = treeType;
     }
+
+    public boolean isAsMessageChannel() {
+        return asMessageChannel;
+    }
+
+    public void setAsMessageChannel(boolean asMessageChannel) {
+        this.asMessageChannel = asMessageChannel;
+    }
 }

+ 22 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java

@@ -1,5 +1,8 @@
 package com.genersoft.iot.vmp.gb28181.bean;
 
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
 import java.time.Instant;
 import java.util.List;
 
@@ -8,20 +11,29 @@ import java.util.List;
  * @author: swwheihei
  * @date:   2020年5月8日 下午2:05:56     
  */
+@Schema(description = "设备录像查询结果信息")
 public class RecordInfo {
 
+	@Schema(description = "设备编号")
 	private String deviceId;
 
+	@Schema(description = "通道编号")
 	private String channelId;
 
+	@Schema(description = "命令序列号")
 	private String sn;
 
+	@Schema(description = "设备名称")
 	private String name;
-	
+
+	@Schema(description = "列表总数")
 	private int sumNum;
 
+	private int count;
+
 	private Instant lastTime;
-	
+
+	@Schema(description = "")
 	private List<RecordItem> recordList;
 
 	public String getDeviceId() {
@@ -79,4 +91,12 @@ public class RecordInfo {
 	public void setLastTime(Instant lastTime) {
 		this.lastTime = lastTime;
 	}
+
+	public int getCount() {
+		return count;
+	}
+
+	public void setCount(int count) {
+		this.count = count;
+	}
 }

+ 19 - 8
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java

@@ -2,9 +2,9 @@ package com.genersoft.iot.vmp.gb28181.bean;
 
 
 import com.genersoft.iot.vmp.utils.DateUtil;
+import io.swagger.v3.oas.annotations.media.Schema;
 import org.jetbrains.annotations.NotNull;
 
-import java.text.ParseException;
 import java.time.Instant;
 import java.time.temporal.TemporalAccessor;
 
@@ -13,26 +13,37 @@ import java.time.temporal.TemporalAccessor;
  * @author: swwheihei
  * @date:   2020年5月8日 下午2:06:54     
  */
+@Schema(description = "设备录像详情")
 public class RecordItem  implements Comparable<RecordItem>{
 
+	@Schema(description = "设备编号")
 	private String deviceId;
-	
+
+	@Schema(description = "名称")
 	private String name;
-	
+
+	@Schema(description = "文件路径名 (可选)")
 	private String filePath;
 
+	@Schema(description = "录像文件大小,单位:Byte(可选)")
 	private String fileSize;
 
+	@Schema(description = "录像地址(可选)")
 	private String address;
-	
+
+	@Schema(description = "录像开始时间(可选)")
 	private String startTime;
-	
+
+	@Schema(description = "录像结束时间(可选)")
 	private String endTime;
-	
+
+	@Schema(description = "保密属性(必选)缺省为0;0:不涉密,1:涉密")
 	private int secrecy;
-	
+
+	@Schema(description = "录像产生类型(可选)time或alarm 或 manua")
 	private String type;
-	
+
+	@Schema(description = "录像触发者ID(可选)")
 	private String recorderId;
 
 	public String getDeviceId() {

+ 13 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java

@@ -1,7 +1,5 @@
 package com.genersoft.iot.vmp.gb28181.bean;
 
-import gov.nist.javax.sip.message.SIPRequest;
-
 public class SendRtpItem {
 
     /**
@@ -108,6 +106,11 @@ public class SendRtpItem {
      */
     private boolean onlyAudio = false;
 
+    /**
+     * 是否开启rtcp保活
+     */
+    private boolean rtcp = false;
+
 
     /**
      * 播放类型
@@ -281,4 +284,12 @@ public class SendRtpItem {
     public void setToTag(String toTag) {
         this.toTag = toTag;
     }
+
+    public boolean isRtcp() {
+        return rtcp;
+    }
+
+    public void setRtcp(boolean rtcp) {
+        this.rtcp = rtcp;
+    }
 }

+ 1 - 3
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeInfo.java

@@ -3,15 +3,13 @@ package com.genersoft.iot.vmp.gb28181.bean;
 import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.message.SIPResponse;
 
-import javax.sip.ServerTransaction;
 import javax.sip.header.*;
 
 public class SubscribeInfo {
 
 
-    public SubscribeInfo(ServerTransaction serverTransaction, String id) {
+    public SubscribeInfo(SIPRequest request, String id) {
         this.id = id;
-        SIPRequest request = (SIPRequest)serverTransaction.getRequest();
         this.request = request;
         this.expires = request.getExpires().getExpires();
         EventHeader eventHeader = (EventHeader)request.getHeader(EventHeader.NAME);

+ 20 - 7
src/main/java/com/genersoft/iot/vmp/gb28181/conf/DefaultProperties.java

@@ -1,5 +1,9 @@
 package com.genersoft.iot.vmp.gb28181.conf;
 
+import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd.AlarmNotifyMessageHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.util.Properties;
 
 /**
@@ -8,10 +12,11 @@ import java.util.Properties;
  */
 public class DefaultProperties {
 
-    public static Properties getProperties(String ip, boolean isDebug) {
+    public static Properties getProperties(String ip, boolean sipLog) {
         Properties properties = new Properties();
         properties.setProperty("javax.sip.STACK_NAME", "GB28181_SIP");
         properties.setProperty("javax.sip.IP_ADDRESS", ip);
+        // 关闭自动会话
         properties.setProperty("javax.sip.AUTOMATIC_DIALOG_SUPPORT", "off");
         /**
          * 完整配置参考 gov.nist.javax.sip.SipStackImpl,需要下载源码
@@ -20,13 +25,10 @@ public class DefaultProperties {
          */
 
 //		 * gov/nist/javax/sip/SipStackImpl.class
-        if (isDebug) {
-            properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "false");
-        }
         // 接收所有notify请求,即使没有订阅
         properties.setProperty("gov.nist.javax.sip.DELIVER_UNSOLICITED_NOTIFY", "true");
         properties.setProperty("gov.nist.javax.sip.AUTOMATIC_DIALOG_ERROR_HANDLING", "false");
-        properties.setProperty("gov.nist.javax.sip.CANCEL_CLIENT_TRANSACTION_CHECKED", "false");
+        properties.setProperty("gov.nist.javax.sip.CANCEL_CLIENT_TRANSACTION_CHECKED", "true");
         // 为_NULL _对话框传递_终止的_事件
         properties.setProperty("gov.nist.javax.sip.DELIVER_TERMINATED_EVENT_FOR_NULL_DIALOG", "true");
         // 会话清理策略
@@ -35,12 +37,23 @@ public class DefaultProperties {
         properties.setProperty("gov.nist.javax.sip.RELIABLE_CONNECTION_KEEP_ALIVE_TIMEOUT", "60");
         // 获取实际内容长度,不使用header中的长度信息
         properties.setProperty("gov.nist.javax.sip.COMPUTE_CONTENT_LENGTH_FROM_MESSAGE_BODY", "true");
+        // 线程可重入
+        properties.setProperty("gov.nist.javax.sip.REENTRANT_LISTENER", "true");
+        // 定义应用程序打算多久审计一次 SIP 堆栈,了解其内部线程的健康状况(该属性指定连续审计之间的时间(以毫秒为单位))
+        properties.setProperty("gov.nist.javax.sip.THREAD_AUDIT_INTERVAL_IN_MILLISECS", "30000");
 
         /**
          * sip_server_log.log 和 sip_debug_log.log ERROR, INFO, WARNING, OFF, DEBUG, TRACE
          */
-        properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "ERROR");
-
+        Logger logger = LoggerFactory.getLogger(AlarmNotifyMessageHandler.class);
+        if (sipLog) {
+            properties.setProperty("gov.nist.javax.sip.STACK_LOGGER", "com.genersoft.iot.vmp.gb28181.conf.StackLoggerImpl");
+            properties.setProperty("gov.nist.javax.sip.SERVER_LOGGER", "com.genersoft.iot.vmp.gb28181.conf.ServerLoggerImpl");
+            properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "true");
+            logger.info("[SIP日志]已开启");
+        }else {
+            logger.info("[SIP日志]已关闭");
+        }
         return properties;
     }
 }

+ 5 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java

@@ -7,10 +7,12 @@ import org.slf4j.LoggerFactory;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
-import javax.sip.*;
+import javax.sip.DialogTerminatedEvent;
+import javax.sip.ResponseEvent;
+import javax.sip.TimeoutEvent;
+import javax.sip.TransactionTerminatedEvent;
 import javax.sip.header.CallIdHeader;
 import javax.sip.message.Response;
-import java.text.ParseException;
 import java.time.Instant;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -29,6 +31,7 @@ public class SipSubscribe {
     private Map<String, SipSubscribe.Event> okSubscribes = new ConcurrentHashMap<>();
 
     private Map<String, Instant> okTimeSubscribes = new ConcurrentHashMap<>();
+
     private Map<String, Instant> errorTimeSubscribes = new ConcurrentHashMap<>();
 
     //    @Scheduled(cron="*/5 * * * * ?")   //每五秒执行一次

+ 1 - 4
src/main/java/com/genersoft/iot/vmp/gb28181/event/device/RequestTimeoutEventImpl.java

@@ -1,7 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.event.device;
 
 import com.genersoft.iot.vmp.gb28181.bean.Device;
-import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.service.IDeviceService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationListener;
@@ -9,8 +8,6 @@ import org.springframework.stereotype.Component;
 
 import javax.sip.ClientTransaction;
 import javax.sip.address.SipURI;
-import javax.sip.header.CallIdHeader;
-import javax.sip.header.ToHeader;
 import javax.sip.message.Request;
 
 /**
@@ -34,7 +31,7 @@ public class RequestTimeoutEventImpl implements ApplicationListener<RequestTimeo
                 if (device == null) {
                     return;
                 }
-                deviceService.offline(device.getDeviceId());
+                deviceService.offline(device.getDeviceId(), "µÈ´ýÏûÏ¢³¬Ê±");
             }
 
         }

+ 32 - 12
src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEventListener.java

@@ -1,15 +1,15 @@
 package com.genersoft.iot.vmp.gb28181.event.record;
 
 import com.genersoft.iot.vmp.gb28181.bean.RecordInfo;
-import com.genersoft.iot.vmp.gb28181.bean.RecordItem;
+import com.genersoft.iot.vmp.utils.redis.RedisUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationListener;
 import org.springframework.stereotype.Component;
-import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 
-import java.io.IOException;
-import java.util.*;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * @description: 录像查询结束时间
@@ -22,26 +22,46 @@ public class RecordEndEventListener implements ApplicationListener<RecordEndEven
 
     private final static Logger logger = LoggerFactory.getLogger(RecordEndEventListener.class);
 
-    private static Map<String, SseEmitter> sseEmitters = new Hashtable<>();
-
+    private Map<String, RecordEndEventHandler> handlerMap = new ConcurrentHashMap<>();
     public interface RecordEndEventHandler{
         void  handler(RecordInfo recordInfo);
     }
 
-    private Map<String, RecordEndEventHandler> handlerMap = new HashMap<>();
     @Override
     public void onApplicationEvent(RecordEndEvent event) {
-        logger.info("录像查询完成事件触发,deviceId:{}, channelId: {}, 录像数量{}条", event.getRecordInfo().getDeviceId(),
-                event.getRecordInfo().getChannelId(), event.getRecordInfo().getSumNum() );
+        String deviceId = event.getRecordInfo().getDeviceId();
+        String channelId = event.getRecordInfo().getChannelId();
+        int count = event.getRecordInfo().getCount();
+        int sumNum = event.getRecordInfo().getSumNum();
+        logger.info("录像查询完成事件触发,deviceId:{}, channelId: {}, 录像数量{}/{}条", event.getRecordInfo().getDeviceId(),
+                event.getRecordInfo().getChannelId(), count,sumNum);
         if (handlerMap.size() > 0) {
-            for (RecordEndEventHandler recordEndEventHandler : handlerMap.values()) {
-                recordEndEventHandler.handler(event.getRecordInfo());
+            RecordEndEventHandler handler = handlerMap.get(deviceId + channelId);
+            if (handler !=null){
+                handler.handler(event.getRecordInfo());
+                if (count ==sumNum){
+                    handlerMap.remove(deviceId + channelId);
+                }
             }
         }
-
     }
 
+    /**
+     * 添加
+     * @param device
+     * @param channelId
+     * @param recordEndEventHandler
+     */
     public void addEndEventHandler(String device, String channelId, RecordEndEventHandler recordEndEventHandler) {
         handlerMap.put(device + channelId, recordEndEventHandler);
     }
+    /**
+     * 添加
+     * @param device
+     * @param channelId
+     */
+    public void delEndEventHandler(String device, String channelId) {
+        handlerMap.remove(device + channelId);
+    }
+
 }

+ 11 - 5
src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java

@@ -42,7 +42,7 @@ public class CatalogDataCatch {
             catalogData.setSn(sn);
             catalogData.setTotal(total);
             catalogData.setDevice(device);
-            catalogData.setChannelList(Collections.synchronizedList(new ArrayList<>()));
+            catalogData.setChannelList(deviceChannelList);
             catalogData.setStatus(CatalogData.CatalogDataStatus.runIng);
             catalogData.setLastTime(Instant.now());
             data.put(deviceId, catalogData);
@@ -109,12 +109,18 @@ public class CatalogDataCatch {
 
         for (String deviceId : keys) {
             CatalogData catalogData = data.get(deviceId);
-            if ( catalogData.getLastTime().isBefore(instantBefore5S)) { // 超过五秒收不到消息任务超时, 只更新这一部分数据
+            if ( catalogData.getLastTime().isBefore(instantBefore5S)) {
+                // 超过五秒收不到消息任务超时, 只更新这一部分数据, 收到数据与声明的总数一致,则重置通道数据,数据不全则只对收到的数据做更新操作
                 if (catalogData.getStatus().equals(CatalogData.CatalogDataStatus.runIng)) {
-                    storager.resetChannels(catalogData.getDevice().getDeviceId(), catalogData.getChannelList());
+                    if (catalogData.getTotal() == catalogData.getChannelList().size()) {
+                        storager.resetChannels(catalogData.getDevice().getDeviceId(), catalogData.getChannelList());
+                    }else {
+                        storager.updateChannels(catalogData.getDevice().getDeviceId(), catalogData.getChannelList());
+                    }
+                    String errorMsg = "更新成功,共" + catalogData.getTotal() + "条,已更新" + catalogData.getChannelList().size() + "条";
+                    catalogData.setErrorMsg(errorMsg);
                     if (catalogData.getTotal() != catalogData.getChannelList().size()) {
-                        String errorMsg = "更新成功,共" + catalogData.getTotal() + "条,已更新" + catalogData.getChannelList().size() + "条";
-                        catalogData.setErrorMsg(errorMsg);
+
                     }
                 }else if (catalogData.getStatus().equals(CatalogData.CatalogDataStatus.ready)) {
                     String errorMsg = "同步失败,等待回复超时";

+ 6 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/session/RecordDataCatch.java

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.gb28181.session;
 
 import com.genersoft.iot.vmp.gb28181.bean.*;
+import com.genersoft.iot.vmp.gb28181.event.record.RecordEndEventListener;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
@@ -23,14 +24,17 @@ public class RecordDataCatch {
 
     @Autowired
     private DeferredResultHolder deferredResultHolder;
+    @Autowired
+    private RecordEndEventListener recordEndEventListener;
 
 
-    public int put(String deviceId, String sn, int sumNum, List<RecordItem> recordItems) {
+    public int put(String deviceId,String channelId, String sn, int sumNum, List<RecordItem> recordItems) {
         String key = deviceId + sn;
         RecordInfo recordInfo = data.get(key);
         if (recordInfo == null) {
             recordInfo = new RecordInfo();
             recordInfo.setDeviceId(deviceId);
+            recordInfo.setChannelId(channelId);
             recordInfo.setSn(sn.trim());
             recordInfo.setSumNum(sumNum);
             recordInfo.setRecordList(Collections.synchronizedList(new ArrayList<>()));
@@ -67,6 +71,7 @@ public class RecordDataCatch {
                 msg.setKey(msgKey);
                 msg.setData(recordInfo);
                 deferredResultHolder.invokeAllResult(msg);
+                recordEndEventListener.delEndEventHandler(recordInfo.getDeviceId(),recordInfo.getChannelId());
                 data.remove(key);
             }
         }

+ 5 - 6
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java

@@ -1,18 +1,19 @@
 package com.genersoft.iot.vmp.gb28181.session;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
 import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
+import com.genersoft.iot.vmp.utils.JsonUtil;
 import com.genersoft.iot.vmp.utils.redis.RedisUtil;
 import gov.nist.javax.sip.message.SIPResponse;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**    
  * @description:视频流session管理器,管理视频预览、预览回放的通信句柄 
  * @author: swwheihei
@@ -53,8 +54,6 @@ public class VideoStreamSessionManager {
 
 		RedisUtil.set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId()
 				+ "_" +  deviceId + "_" + channelId + "_" + callId + "_" + stream, ssrcTransaction);
-		RedisUtil.set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId()
-				+ "_" +  deviceId + "_" + channelId + "_" + callId + "_" + stream, ssrcTransaction);
 	}
 
 	public SsrcTransaction getSsrcTransaction(String deviceId, String channelId, String callId, String stream){
@@ -135,7 +134,7 @@ public class VideoStreamSessionManager {
 		List<SsrcTransaction> result= new ArrayList<>();
 		for (int i = 0; i < ssrcTransactionKeys.size(); i++) {
 			String key = (String)ssrcTransactionKeys.get(i);
-			SsrcTransaction ssrcTransaction = (SsrcTransaction)RedisUtil.get(key);
+			SsrcTransaction ssrcTransaction = JsonUtil.redisJsonToObject(key, SsrcTransaction.class);
 			result.add(ssrcTransaction);
 		}
 		return result;

+ 3 - 3
src/main/java/com/genersoft/iot/vmp/gb28181/task/SipDeviceRunner.java

@@ -18,8 +18,8 @@ import java.util.List;
  * 系统启动时控制设备
  * @author lin
  */
-@Component
-@Order(value=4)
+//@Component
+//@Order(value=4)
 public class SipDeviceRunner implements CommandLineRunner {
 
     @Autowired
@@ -40,7 +40,7 @@ public class SipDeviceRunner implements CommandLineRunner {
 
         for (Device device : deviceList) {
             if (deviceService.expire(device)){
-                deviceService.offline(device.getDeviceId());
+                //deviceService.offline(device.getDeviceId());
             }else {
                 deviceService.online(device);
             }

+ 52 - 23
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java

@@ -1,15 +1,15 @@
 package com.genersoft.iot.vmp.gb28181.transmit.callback;
 
-import java.util.HashMap;
+import com.genersoft.iot.vmp.vmanager.bean.DeferredResultEx;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ObjectUtils;
+import org.springframework.web.context.request.async.DeferredResult;
+
+import java.util.Collection;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Component;
-import org.springframework.web.context.request.async.DeferredResult;
-
 /**    
  * @description: 异步请求处理
  * @author: swwheihei
@@ -55,31 +55,48 @@ public class DeferredResultHolder {
 	public static final String CALLBACK_CMD_BROADCAST = "CALLBACK_BROADCAST";
 
 	public static final String CALLBACK_CMD_BROADCAST_INVITE = "CALLBACK_BROADCAST_INVITE";
-	private Map<String, Map<String, DeferredResult>> map = new ConcurrentHashMap<>();
+	private Map<String, Map<String, DeferredResultEx>> map = new ConcurrentHashMap<>();
 
 
-	public void put(String key, String id, DeferredResult result) {
-		Map<String, DeferredResult> deferredResultMap = map.get(key);
+	public void put(String key, String id, DeferredResultEx result) {
+		Map<String, DeferredResultEx> deferredResultMap = map.get(key);
 		if (deferredResultMap == null) {
 			deferredResultMap = new ConcurrentHashMap<>();
 			map.put(key, deferredResultMap);
 		}
 		deferredResultMap.put(id, result);
 	}
-	
-	public DeferredResult get(String key, String id) {
-		Map<String, DeferredResult> deferredResultMap = map.get(key);
+
+	public void put(String key, String id, DeferredResult result) {
+		Map<String, DeferredResultEx> deferredResultMap = map.get(key);
 		if (deferredResultMap == null) {
+			deferredResultMap = new ConcurrentHashMap<>();
+			map.put(key, deferredResultMap);
+		}
+		deferredResultMap.put(id, new DeferredResultEx(result));
+	}
+	
+	public DeferredResultEx get(String key, String id) {
+		Map<String, DeferredResultEx> deferredResultMap = map.get(key);
+		if (deferredResultMap == null || ObjectUtils.isEmpty(id)) {
 			return null;
 		}
 		return deferredResultMap.get(id);
 	}
 
+	public Collection<DeferredResultEx> getAllByKey(String key) {
+		Map<String, DeferredResultEx> deferredResultMap = map.get(key);
+		if (deferredResultMap == null) {
+			return null;
+		}
+		return deferredResultMap.values();
+	}
+
 	public boolean exist(String key, String id){
 		if (key == null) {
 			return false;
 		}
-		Map<String, DeferredResult> deferredResultMap = map.get(key);
+		Map<String, DeferredResultEx> deferredResultMap = map.get(key);
 		if (id == null) {
 			return deferredResultMap != null;
 		}else {
@@ -92,15 +109,15 @@ public class DeferredResultHolder {
 	 * @param msg
 	 */
 	public void invokeResult(RequestMessage msg) {
-		Map<String, DeferredResult> deferredResultMap = map.get(msg.getKey());
+		Map<String, DeferredResultEx> deferredResultMap = map.get(msg.getKey());
 		if (deferredResultMap == null) {
 			return;
 		}
-		DeferredResult result = deferredResultMap.get(msg.getId());
+		DeferredResultEx result = deferredResultMap.get(msg.getId());
 		if (result == null) {
 			return;
 		}
-		result.setResult(msg.getData());
+		result.getDeferredResult().setResult(msg.getData());
 		deferredResultMap.remove(msg.getId());
 		if (deferredResultMap.size() == 0) {
 			map.remove(msg.getKey());
@@ -112,18 +129,30 @@ public class DeferredResultHolder {
 	 * @param msg
 	 */
 	public void invokeAllResult(RequestMessage msg) {
-		Map<String, DeferredResult> deferredResultMap = map.get(msg.getKey());
+		Map<String, DeferredResultEx> deferredResultMap = map.get(msg.getKey());
 		if (deferredResultMap == null) {
 			return;
 		}
-		Set<String> ids = deferredResultMap.keySet();
-		for (String id : ids) {
-			DeferredResult result = deferredResultMap.get(id);
-			if (result == null) {
+		synchronized (this) {
+			deferredResultMap = map.get(msg.getKey());
+			if (deferredResultMap == null) {
 				return;
 			}
-			result.setResult(msg.getData());
+			Set<String> ids = deferredResultMap.keySet();
+			for (String id : ids) {
+				DeferredResultEx result = deferredResultMap.get(id);
+				if (result == null) {
+					return;
+				}
+				if (result.getFilter() != null) {
+					Object handler = result.getFilter().handler(msg.getData());
+					result.getDeferredResult().setResult(handler);
+				}else {
+					result.getDeferredResult().setResult(msg.getData());
+				}
+
+			}
+			map.remove(msg.getKey());
 		}
-		map.remove(msg.getKey());
 	}
 }

+ 20 - 26
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java

@@ -3,23 +3,21 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd;
 import com.genersoft.iot.vmp.common.HfyAiInfo;
 import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
-import com.genersoft.iot.vmp.gb28181.bean.*;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
+import com.genersoft.iot.vmp.gb28181.bean.InviteStreamCallback;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.bean.SSRCInfo;
 import gov.nist.javax.sip.message.SIPRequest;
 
-import javax.sip.Dialog;
 import javax.sip.InvalidArgumentException;
-import javax.sip.PeerUnavailableException;
 import javax.sip.SipException;
-import javax.sip.message.Request;
 import java.text.ParseException;
 import java.util.List;
 
-
-/**
+/**    
  * @description:设备能力接口,用于定义设备的控制、查询能力   
  * @author: swwheihei
  * @date:   2020年5月3日 下午9:16:34     
@@ -128,7 +126,7 @@ public interface ISIPCommander {
 	 */ 
 	void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
 						   String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
-						   SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+						   SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
 
 	/**
 	 * 视频流停止
@@ -188,7 +186,7 @@ public interface ISIPCommander {
 	 * @param channelId  	预览通道
 	 * @param recordCmdStr	录像命令:Record / StopRecord
 	 */
-	void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+	void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 远程启动控制命令
@@ -202,7 +200,7 @@ public interface ISIPCommander {
 	 * 
 	 * @param device  	视频设备
 	 */
-	void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+	void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 报警复位命令
@@ -211,7 +209,7 @@ public interface ISIPCommander {
 	 * @param alarmMethod	报警方式(可选)
 	 * @param alarmType		报警类型(可选)
 	 */
-	void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+	void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
@@ -220,17 +218,18 @@ public interface ISIPCommander {
 	 * @param channelId  预览通道
 	 */
 	void iFrameCmd(Device device, String channelId) throws InvalidArgumentException, SipException, ParseException;
-	
+
 	/**
 	 * 看守位控制命令
-	 * 
-	 * @param device		视频设备
-	 * @param enabled		看守位使能:1 = 开启,0 = 关闭
-	 * @param resetTime		自动归位时间间隔,开启看守位时使用,单位:秒(s)
-	 * @param presetIndex	调用预置位编号,开启看守位时使用,取值范围0~255
+	 *
+	 * @param device      视频设备
+	 * @param channelId      通道id,非通道则是设备本身
+	 * @param enabled     看守位使能:1 = 开启,0 = 关闭
+	 * @param resetTime   自动归位时间间隔,开启看守位时使用,单位:秒(s)
+	 * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255
 	 */
-	void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
-	
+	void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
+
 	/**
 	 * 设备配置命令
 	 * 
@@ -238,7 +237,7 @@ public interface ISIPCommander {
 	 */
 	void deviceConfigCmd(Device device);
 	
-		/**
+	/**
 	 * 设备配置命令:basicParam
 	 * 
 	 * @param device  			视频设备
@@ -249,7 +248,7 @@ public interface ISIPCommander {
 	 * @param heartBeatCount	心跳超时次数(可选)
 	 */  
 	void deviceBasicConfigCmd(Device device, String channelId, String name, String expiration, String heartBeatInterval, String heartBeatCount, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
-	
+
 	/**
 	 * 查询设备状态
 	 * 
@@ -349,7 +348,7 @@ public interface ISIPCommander {
 	 * @param endTime
 	 * @param aiDataList
 	 */
-	SIPRequest hfyAiAlarmSubScribe(Device device, int expires, String startTime, String endTime,  List<HfyAiInfo> aiDataList,SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+	SIPRequest hfyAiAlarmSubScribe(Device device, int expires, String startTime, String endTime, List<HfyAiInfo> aiDataList, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 
 	/**
 	 * 订阅、取消订阅目录信息
@@ -376,9 +375,4 @@ public interface ISIPCommander {
 	 */
 	void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException;
 
-	void transmitRequest(String transport, Request request) throws SipException, ParseException ;
-
-	void transmitRequest(String transport, Request request, SipSubscribe.Event errorEvent) throws SipException, ParseException;
-
-	void transmitRequest(String transport, Request request, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException, ParseException;
 }

+ 4 - 4
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java

@@ -51,11 +51,11 @@ public interface ISIPCommanderForPlatform {
     /**
      * 向上级回复DeviceInfo查询信息
      * @param parentPlatform 平台信息
-     * @param sn
-     * @param fromTag
+     * @param sn SN
+     * @param fromTag FROM头的tag信息
      * @return
      */
-    void deviceInfoResponse(ParentPlatform parentPlatform, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException;
+    void deviceInfoResponse(ParentPlatform parentPlatform,Device device, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException;
 
     /**
      * 向上级回复DeviceStatus查询信息
@@ -64,7 +64,7 @@ public interface ISIPCommanderForPlatform {
      * @param fromTag
      * @return
      */
-    void deviceStatusResponse(ParentPlatform parentPlatform, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException;
+    void deviceStatusResponse(ParentPlatform parentPlatform,String channelId, String sn, String fromTag,int status) throws SipException, InvalidArgumentException, ParseException;
 
     /**
      * 向上级回复移动位置订阅消息

+ 74 - 73
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.gb28181.transmit.cmd;
 
 import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.gb28181.SipLayer;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
 import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo;
@@ -36,7 +37,7 @@ public class SIPRequestHeaderPlarformProvider {
 	private SipConfig sipConfig;
 	
 	@Autowired
-	private SipFactory sipFactory;
+	private SipLayer sipLayer;
 
 	@Autowired
 	private GitUtil gitUtil;
@@ -44,42 +45,42 @@ public class SIPRequestHeaderPlarformProvider {
 	@Autowired
 	private IRedisCatchStorage redisCatchStorage;
 
-	public Request createRegisterRequest(@NotNull ParentPlatform platform, long CSeq, String fromTag, String viaTag, CallIdHeader callIdHeader, boolean isRegister) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+	public Request createRegisterRequest(@NotNull ParentPlatform parentPlatform, long CSeq, String fromTag, String viaTag, CallIdHeader callIdHeader, boolean isRegister) throws ParseException, InvalidArgumentException, PeerUnavailableException {
 		Request request = null;
-		String sipAddress = sipConfig.getIp() + ":" + sipConfig.getPort();
+		String sipAddress = parentPlatform.getDeviceIp() + ":" + parentPlatform.getDevicePort();
 		//请求行
-		SipURI requestLine = sipFactory.createAddressFactory().createSipURI(platform.getServerGBId(),
-				platform.getServerIP() + ":" + platform.getServerPort());
+		SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(),
+				parentPlatform.getServerIP() + ":" + parentPlatform.getServerPort());
 		//via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(platform.getServerIP(), platform.getServerPort(), platform.getTransport(), viaTag);
+		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getServerIP(), parentPlatform.getServerPort(), parentPlatform.getTransport(), viaTag);
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
 		//from
-		SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(platform.getDeviceGBId(), sipConfig.getDomain());
-		Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, fromTag);
+		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain());
+		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag);
 		//to
-		SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(platform.getDeviceGBId(), sipConfig.getDomain());
-		Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress,null);
+		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain());
+		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,null);
 
 		//Forwards
-		MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
 
 		//ceq
-		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(CSeq, Request.REGISTER);
-		request = sipFactory.createMessageFactory().createRequest(requestLine, Request.REGISTER, callIdHeader,
+		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(CSeq, Request.REGISTER);
+		request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.REGISTER, callIdHeader,
 				cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
 
-		Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory()
-				.createSipURI(platform.getDeviceGBId(), sipAddress));
-		request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
+		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory()
+				.createSipURI(parentPlatform.getDeviceGBId(), sipAddress));
+		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
 
-		ExpiresHeader expires = sipFactory.createHeaderFactory().createExpiresHeader(isRegister ? platform.getExpires() : 0);
+		ExpiresHeader expires = sipLayer.getSipFactory().createHeaderFactory().createExpiresHeader(isRegister ? parentPlatform.getExpires() : 0);
 		request.addHeader(expires);
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
 
 		return request;
 	}
@@ -89,9 +90,9 @@ public class SIPRequestHeaderPlarformProvider {
 
 
 		Request registerRequest = createRegisterRequest(parentPlatform, redisCatchStorage.getCSEQ(), fromTag, viaTag, callIdHeader, isRegister);
-		SipURI requestURI = sipFactory.createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP() + ":" + parentPlatform.getServerPort());
+		SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP() + ":" + parentPlatform.getServerPort());
 		if (www == null) {
-			AuthorizationHeader authorizationHeader = sipFactory.createHeaderFactory().createAuthorizationHeader("Digest");
+			AuthorizationHeader authorizationHeader = sipLayer.getSipFactory().createHeaderFactory().createAuthorizationHeader("Digest");
 			authorizationHeader.setUsername(parentPlatform.getDeviceGBId());
 			authorizationHeader.setURI(requestURI);
 			authorizationHeader.setAlgorithm("MD5");
@@ -140,7 +141,7 @@ public class SIPRequestHeaderPlarformProvider {
 
 		String RESPONSE = DigestUtils.md5DigestAsHex(reStr.toString().getBytes());
 
-		AuthorizationHeader authorizationHeader = sipFactory.createHeaderFactory().createAuthorizationHeader(scheme);
+		AuthorizationHeader authorizationHeader = sipLayer.getSipFactory().createHeaderFactory().createAuthorizationHeader(scheme);
 		authorizationHeader.setUsername(parentPlatform.getDeviceGBId());
 		authorizationHeader.setRealm(realm);
 		authorizationHeader.setNonce(nonce);
@@ -158,7 +159,7 @@ public class SIPRequestHeaderPlarformProvider {
 	}
 
 	public Request createMessageRequest(ParentPlatform parentPlatform, String content, SendRtpItem sendRtpItem) throws PeerUnavailableException, ParseException, InvalidArgumentException {
-		CallIdHeader callIdHeader = sipFactory.createHeaderFactory().createCallIdHeader(sendRtpItem.getCallId());
+		CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(sendRtpItem.getCallId());
 		return createMessageRequest(parentPlatform, content, sendRtpItem.getToTag(), SipUtils.getNewViaTag(), sendRtpItem.getFromTag(), callIdHeader);
 	}
 
@@ -171,36 +172,36 @@ public class SIPRequestHeaderPlarformProvider {
 		Request request = null;
 		String serverAddress = parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort();
 		// sipuri
-		SipURI requestURI = sipFactory.createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress);
+		SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress);
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), Integer.parseInt(parentPlatform.getDevicePort()),
+		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), Integer.parseInt(parentPlatform.getDevicePort()),
 				parentPlatform.getTransport(), viaTag);
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
 		// from
-		// SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), parentPlatform.getDeviceIp() + ":" + parentPlatform.getDeviceIp());
-		SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain());
-		Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, fromTag);
+		// SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), parentPlatform.getDeviceIp() + ":" + parentPlatform.getDeviceIp());
+		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain());
+		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag);
 		// to
-		SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress);
-		Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress, toTag);
+		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress);
+		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, toTag);
 
 		// Forwards
-		MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
 		// ceq
-		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE);
-		MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipFactory.createMessageFactory();
+		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE);
+		MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipLayer.getSipFactory().createMessageFactory();
 		// 设置编码, 防止中文乱码
 		messageFactory.setDefaultContentEncodingCharset(parentPlatform.getCharacterSet());
 		request = messageFactory.createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
 				toHeader, viaHeaders, maxForwards);
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
 
-		ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
+		ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
 		request.setContent(content, contentTypeHeader);
 		return request;
 	}
@@ -208,54 +209,54 @@ public class SIPRequestHeaderPlarformProvider {
 	public SIPRequest createNotifyRequest(ParentPlatform parentPlatform, String content, SubscribeInfo subscribeInfo) throws PeerUnavailableException, ParseException, InvalidArgumentException {
 		SIPRequest request = null;
 		// sipuri
-		SipURI requestURI = sipFactory.createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort());
+		SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<>();
-		ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), Integer.parseInt(parentPlatform.getDevicePort()),
+		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), Integer.parseInt(parentPlatform.getDevicePort()),
 				parentPlatform.getTransport(), SipUtils.getNewViaTag());
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
 		// from
-		SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(),
+		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(),
 				parentPlatform.getDeviceIp() + ":" + parentPlatform.getDevicePort());
-		Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, subscribeInfo.getResponse().getToTag());
+		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, subscribeInfo.getResponse().getToTag());
 		// to
-		SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerGBDomain());
-		Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress, subscribeInfo.getRequest().getFromTag());
+		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerGBDomain());
+		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, subscribeInfo.getRequest().getFromTag());
 
 		// Forwards
-		MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
 		// ceq
-		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.NOTIFY);
-		MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipFactory.createMessageFactory();
+		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.NOTIFY);
+		MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipLayer.getSipFactory().createMessageFactory();
 		// 设置编码, 防止中文乱码
 		messageFactory.setDefaultContentEncodingCharset("gb2312");
 
-		CallIdHeader callIdHeader = sipFactory.createHeaderFactory().createCallIdHeader(subscribeInfo.getRequest().getCallIdHeader().getCallId());
+		CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(subscribeInfo.getRequest().getCallIdHeader().getCallId());
 
 		request = (SIPRequest) messageFactory.createRequest(requestURI, Request.NOTIFY, callIdHeader, cSeqHeader, fromHeader,
 				toHeader, viaHeaders, maxForwards);
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
 
-		EventHeader event = sipFactory.createHeaderFactory().createEventHeader(subscribeInfo.getEventType());
+		EventHeader event = sipLayer.getSipFactory().createHeaderFactory().createEventHeader(subscribeInfo.getEventType());
 		if (subscribeInfo.getEventId() != null) {
 			event.setEventId(subscribeInfo.getEventId());
 		}
 
 		request.addHeader(event);
 
-		SubscriptionStateHeader active = sipFactory.createHeaderFactory().createSubscriptionStateHeader("active");
+		SubscriptionStateHeader active = sipLayer.getSipFactory().createHeaderFactory().createSubscriptionStateHeader("active");
 		request.setHeader(active);
 
-		String sipAddress = sipConfig.getIp() + ":" + sipConfig.getPort();
-		Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory()
+		String sipAddress = parentPlatform.getDeviceIp() + ":" + parentPlatform.getDevicePort();
+		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory()
 				.createSipURI(parentPlatform.getDeviceGBId(), sipAddress));
-		request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
+		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
 
-		ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
+		ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
 		request.setContent(content, contentTypeHeader);
 		return request;
     }
@@ -268,42 +269,42 @@ public class SIPRequestHeaderPlarformProvider {
 
 		SIPRequest request = null;
 		// sipuri
-		SipURI requestURI = sipFactory.createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerIP()+ ":" + platform.getServerPort());
+		SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerIP()+ ":" + platform.getServerPort());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<>();
-		ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(platform.getDeviceIp(), Integer.parseInt(platform.getDevicePort()),
+		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(platform.getDeviceIp(), Integer.parseInt(platform.getDevicePort()),
 				platform.getTransport(), SipUtils.getNewViaTag());
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
 		// from
-		SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(platform.getDeviceGBId(),
+		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getDeviceGBId(),
 				platform.getDeviceIp() + ":" + platform.getDevicePort());
-		Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, sendRtpItem.getToTag());
+		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, sendRtpItem.getToTag());
 		// to
-		SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerGBDomain());
-		Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress, sendRtpItem.getFromTag());
+		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerGBDomain());
+		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, sendRtpItem.getFromTag());
 
 		// Forwards
-		MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
 		// ceq
-		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
-		MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipFactory.createMessageFactory();
+		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
+		MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipLayer.getSipFactory().createMessageFactory();
 		// 设置编码, 防止中文乱码
 		messageFactory.setDefaultContentEncodingCharset("gb2312");
 
-		CallIdHeader callIdHeader = sipFactory.createHeaderFactory().createCallIdHeader(sendRtpItem.getCallId());
+		CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(sendRtpItem.getCallId());
 
 		request = (SIPRequest) messageFactory.createRequest(requestURI, Request.BYE, callIdHeader, cSeqHeader, fromHeader,
 				toHeader, viaHeaders, maxForwards);
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
 
-		String sipAddress = sipConfig.getIp() + ":" + sipConfig.getPort();
-		Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory()
+		String sipAddress = platform.getDeviceIp() + ":" + platform.getDevicePort();
+		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory()
 				.createSipURI(platform.getDeviceGBId(), sipAddress));
-		request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
+		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
 
 		return request;
 	}

+ 121 - 133
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java

@@ -1,33 +1,27 @@
 package com.genersoft.iot.vmp.gb28181.transmit.cmd;
 
-import java.text.ParseException;
-import java.util.ArrayList;
-
-import javax.sip.*;
-import javax.sip.address.Address;
-import javax.sip.address.SipURI;
-import javax.sip.header.*;
-import javax.sip.message.Request;
-
-import com.genersoft.iot.vmp.common.StreamInfo;
-import com.genersoft.iot.vmp.gb28181.bean.SipMsgInfo;
+import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.gb28181.SipLayer;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
-import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.utils.GitUtil;
-import gov.nist.javax.sip.SipProviderImpl;
-import gov.nist.javax.sip.SipStackImpl;
 import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.message.SIPResponse;
-import gov.nist.javax.sip.stack.SIPDialog;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Component;
 
-import com.genersoft.iot.vmp.conf.SipConfig;
-import com.genersoft.iot.vmp.gb28181.bean.Device;
+import javax.sip.InvalidArgumentException;
+import javax.sip.PeerUnavailableException;
+import javax.sip.SipException;
+import javax.sip.address.Address;
+import javax.sip.address.SipURI;
+import javax.sip.header.*;
+import javax.sip.message.Request;
+import java.text.ParseException;
+import java.util.ArrayList;
 
 /**
  * @description:摄像头命令request创造器 TODO 冗余代码太多待优化
@@ -41,7 +35,7 @@ public class SIPRequestHeaderProvider {
 	private SipConfig sipConfig;
 	
 	@Autowired
-	private SipFactory sipFactory;
+	private SipLayer sipLayer;
 
 	@Autowired
 	private GitUtil gitUtil;
@@ -51,44 +45,36 @@ public class SIPRequestHeaderProvider {
 
 	@Autowired
 	private VideoStreamSessionManager streamSession;
-
-	@Autowired
-	@Qualifier(value="tcpSipProvider")
-	private SipProviderImpl tcpSipProvider;
-
-	@Autowired
-	@Qualifier(value="udpSipProvider")
-	private SipProviderImpl udpSipProvider;
 	
 	public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
 		Request request = null;
 		// sipuri
-		SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
+		SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), device.getTransport(), viaTag);
+		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
 		// from
-		SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
-		Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, fromTag);
+		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
+		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag);
 		// to
-		SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
-		Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress, toTag);
+		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
+		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, toTag);
 
 		// Forwards
-		MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
 		// ceq
-		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE);
+		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE);
 
-		request = sipFactory.createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
+		request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
 				toHeader, viaHeaders, maxForwards);
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
 
-		ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
+		ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
 		request.setContent(content, contentTypeHeader);
 		return request;
 	}
@@ -96,38 +82,39 @@ public class SIPRequestHeaderProvider {
 	public Request createInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, String ssrc, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
 		Request request = null;
 		//请求行
-		SipURI requestLine = sipFactory.createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
 		//via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), device.getTransport(), viaTag);
+		HeaderFactory headerFactory = sipLayer.getSipFactory().createHeaderFactory();
+		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
 
 		//from
-		SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
-		Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
+		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
+		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
 		//to
-		SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress,null);
+		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,null);
 		
 		//Forwards
-		MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
 		
 		//ceq
-		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
-		request = sipFactory.createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
+		request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
 
-		Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getIp()+":"+sipConfig.getPort()));
-		// Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort()));
-		request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
+		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
+		// Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort()));
+		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
 		// Subject
-		SubjectHeader subjectHeader = sipFactory.createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
+		SubjectHeader subjectHeader = sipLayer.getSipFactory().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
 		request.addHeader(subjectHeader);
-		ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
+		ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
 		request.setContent(content, contentTypeHeader);
 		return request;
 	}
@@ -135,39 +122,39 @@ public class SIPRequestHeaderProvider {
 	public Request createPlaybackInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader, String ssrc) throws ParseException, InvalidArgumentException, PeerUnavailableException {
 		Request request = null;
 		//请求行
-		SipURI requestLine = sipFactory.createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), device.getTransport(), viaTag);
+		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
 		//from
-		SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
-		Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
+		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
+		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
 		//to
-		SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress,null);
+		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,null);
 		
 		//Forwards
-		MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
 		
 		//ceq
-		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
-		request = sipFactory.createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
+		request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
 		
-		Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getIp()+":"+sipConfig.getPort()));
-		// Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort()));
-		request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
+		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
+		// Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort()));
+		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
 
 		// Subject
-		SubjectHeader subjectHeader = sipFactory.createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
+		SubjectHeader subjectHeader = sipLayer.getSipFactory().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
 		request.addHeader(subjectHeader);
 
-		ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
+		ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
 		request.setContent(content, contentTypeHeader);
 		return request;
 	}
@@ -175,34 +162,34 @@ public class SIPRequestHeaderProvider {
 	public Request createByteRequest(Device device, String channelId, SipTransactionInfo transactionInfo) throws ParseException, InvalidArgumentException, PeerUnavailableException {
 		Request request = null;
 		//请求行
-		SipURI requestLine = sipFactory.createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
+		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
 		viaHeaders.add(viaHeader);
 		//from
-		SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
-		Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag());
+		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
+		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag());
 		//to
-		SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(channelId,device.getHostAddress());
-		Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress,	transactionInfo.getToTag());
+		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId,device.getHostAddress());
+		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,	transactionInfo.getToTag());
 
 		//Forwards
-		MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
 
 		//ceq
-		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
-		CallIdHeader callIdHeader = sipFactory.createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
-		request = sipFactory.createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
+		CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
+		request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
 
-		Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getIp()+":"+sipConfig.getPort()));
-		request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
+		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
+		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
 
 		return request;
 	}
@@ -210,50 +197,50 @@ public class SIPRequestHeaderProvider {
 	public Request createSubscribeRequest(Device device, String content, SIPRequest requestOld, Integer expires, String event, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
 		Request request = null;
 		// sipuri
-		SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
+		SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(),
+		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(),
 				device.getTransport(), SipUtils.getNewViaTag());
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
 		// from
-		SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
-		Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, requestOld == null ? SipUtils.getNewFromTag() :requestOld.getFromTag());
+		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
+		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, requestOld == null ? SipUtils.getNewFromTag() :requestOld.getFromTag());
 		// to
-		SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
-		Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress, requestOld == null ? null :requestOld.getToTag());
+		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
+		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, requestOld == null ? null :requestOld.getToTag());
 
 		// Forwards
-		MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
 
 		// ceq
-		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.SUBSCRIBE);
+		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.SUBSCRIBE);
 
-		request = sipFactory.createMessageFactory().createRequest(requestURI, Request.SUBSCRIBE, callIdHeader, cSeqHeader, fromHeader,
+		request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestURI, Request.SUBSCRIBE, callIdHeader, cSeqHeader, fromHeader,
 				toHeader, viaHeaders, maxForwards);
 
 
-		Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getIp()+":"+sipConfig.getPort()));
-		request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
+		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
+		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
 
 		// Expires
-		ExpiresHeader expireHeader = sipFactory.createHeaderFactory().createExpiresHeader(expires);
+		ExpiresHeader expireHeader = sipLayer.getSipFactory().createHeaderFactory().createExpiresHeader(expires);
 		request.addHeader(expireHeader);
 
 		// Event
-		EventHeader eventHeader = sipFactory.createHeaderFactory().createEventHeader(event);
+		EventHeader eventHeader = sipLayer.getSipFactory().createHeaderFactory().createEventHeader(event);
 
 		int random = (int) Math.floor(Math.random() * 10000);
 		eventHeader.setEventId(random + "");
 		request.addHeader(eventHeader);
 
-		ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
+		ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
 		request.setContent(content, contentTypeHeader);
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
 
 		return request;
 	}
@@ -265,64 +252,65 @@ public class SIPRequestHeaderProvider {
 		}
 		SIPRequest request = null;
 		//请求行
-		SipURI requestLine = sipFactory.createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
+		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
 		viaHeaders.add(viaHeader);
 		//from
-		SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
-		Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag());
+		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
+		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag());
 		//to
-		SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(channelId,device.getHostAddress());
-		Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress,	transactionInfo.getToTag());
+		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId,device.getHostAddress());
+		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,	transactionInfo.getToTag());
 
 		//Forwards
-		MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
 
 		//ceq
-		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INFO);
-		CallIdHeader callIdHeader = sipFactory.createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
-		request = (SIPRequest)sipFactory.createMessageFactory().createRequest(requestLine, Request.INFO, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INFO);
+		CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
+		request = (SIPRequest)sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.INFO, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
 
-		Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getIp()+":"+sipConfig.getPort()));
-		request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
+		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
+		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
 
 		if (content != null) {
-			ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application",
+			ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("Application",
 					"MANSRTSP");
 			request.setContent(content, contentTypeHeader);
 		}
 		return request;
 	}
 
-	public Request createAckRequest(SipURI sipURI, SIPResponse sipResponse) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+	public Request createAckRequest(String localIp, SipURI sipURI, SIPResponse sipResponse) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+
 
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), sipResponse.getTopmostViaHeader().getTransport(), SipUtils.getNewViaTag());
+		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(localIp, sipConfig.getPort(), sipResponse.getTopmostViaHeader().getTransport(), SipUtils.getNewViaTag());
 		viaHeaders.add(viaHeader);
 
 		//Forwards
-		MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
 
 		//ceq
-		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(sipResponse.getCSeqHeader().getSeqNumber(), Request.ACK);
+		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(sipResponse.getCSeqHeader().getSeqNumber(), Request.ACK);
 
-		Request request = sipFactory.createMessageFactory().createRequest(sipURI, Request.ACK, sipResponse.getCallIdHeader(), cSeqHeader, sipResponse.getFromHeader(), sipResponse.getToHeader(), viaHeaders, maxForwards);
+		Request request = sipLayer.getSipFactory().createMessageFactory().createRequest(sipURI, Request.ACK, sipResponse.getCallIdHeader(), cSeqHeader, sipResponse.getFromHeader(), sipResponse.getToHeader(), viaHeaders, maxForwards);
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
 
-		Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getIp()+":"+sipConfig.getPort()));
-		request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
+		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), localIp + ":"+sipConfig.getPort()));
+		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
 
 		return request;
 	}

+ 182 - 241
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java

@@ -1,44 +1,41 @@
 package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.common.HfyAiInfo;
 import com.genersoft.iot.vmp.common.StreamInfo;
-import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
+import com.genersoft.iot.vmp.gb28181.SipLayer;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
-import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
+import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.TestInviteRequestProcessor;
+import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
+import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
 import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
 import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
-
-import com.genersoft.iot.vmp.utils.DateUtil;
-import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
-import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.bean.SSRCInfo;
-import com.genersoft.iot.vmp.utils.GitUtil;
-import gov.nist.javax.sip.SipProviderImpl;
+import com.genersoft.iot.vmp.utils.DateUtil;
 import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.message.SIPResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.annotation.DependsOn;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
-import org.springframework.web.context.request.async.DeferredResult;
 
-import javax.sip.*;
-import javax.sip.header.*;
+import javax.sip.InvalidArgumentException;
+import javax.sip.ResponseEvent;
+import javax.sip.SipException;
+import javax.sip.header.CallIdHeader;
 import javax.sip.message.Request;
 import java.text.ParseException;
 import java.util.List;
@@ -58,19 +55,11 @@ public class SIPCommander implements ISIPCommander {
     private SipConfig sipConfig;
 
     @Autowired
-    private SipFactory sipFactory;
-
-    @Autowired
-    private GitUtil gitUtil;
-
-    @Autowired
-    @Qualifier(value = "tcpSipProvider")
-    private SipProviderImpl tcpSipProvider;
+    private SipLayer sipLayer;
 
     @Autowired
-    @Qualifier(value = "udpSipProvider")
-    private SipProviderImpl udpSipProvider;
-
+    private SIPSender sipSender;
+    
     @Autowired
     private SIPRequestHeaderProvider headerProvider;
 
@@ -83,20 +72,14 @@ public class SIPCommander implements ISIPCommander {
     @Autowired
     private ZlmHttpHookSubscribe subscribe;
 
-    @Autowired
-    private SipSubscribe sipSubscribe;
 
-    @Autowired
-    private IMediaServerService mediaServerService;
 
     @Autowired
-    private DynamicTask dynamicTask;
+    private IMediaServerService mediaServerService;
 
     @Autowired
     private TestInviteRequestProcessor testInviteRequestProcessor;
 
-
-
     /**
      * 云台方向放控制,使用配置文件中的默认镜头移动速度
      *
@@ -201,7 +184,7 @@ public class SIPCommander implements ISIPCommander {
     public void ptzCmd(Device device, String channelId, int leftRight, int upDown, int inOut, int moveSpeed,
                        int zoomSpeed) throws InvalidArgumentException, SipException, ParseException {
         String cmdStr = SipUtils.cmdString(leftRight, upDown, inOut, moveSpeed, zoomSpeed);
-        StringBuffer ptzXml = new StringBuffer(200);
+        StringBuilder ptzXml = new StringBuilder(200);
         String charset = device.getCharset();
         ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
         ptzXml.append("<Control>\r\n");
@@ -213,13 +196,10 @@ public class SIPCommander implements ISIPCommander {
         ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
         ptzXml.append("</Info>\r\n");
         ptzXml.append("</Control>\r\n");
+        
+        Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
 
-        CallIdHeader callIdHeader = device.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
-
-        Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
-
-        transmitRequest(device.getTransport(), request);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request);
     }
 
 
@@ -239,12 +219,16 @@ public class SIPCommander implements ISIPCommander {
         ptzXml.append("</Info>\r\n");
         ptzXml.append("</Control>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
 
-        Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
+        Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
 
-        transmitRequest(device.getTransport(), request);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request);
+//        CallIdHeader callIdHeader = device.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
+//                : udpSipProvider.getNewCallId();
+//
+//        Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
+//
+//        transmitRequest(device.getTransport(), request);
     };
 
     /**
@@ -275,11 +259,10 @@ public class SIPCommander implements ISIPCommander {
         ptzXml.append("</Control>\r\n");
 
 
-        CallIdHeader callIdHeader = device.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
 
-        SIPRequest request = (SIPRequest) headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
-        transmitRequest(device.getTransport(), request);
+
+        SIPRequest request = (SIPRequest) headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request);
 
     }
 
@@ -305,13 +288,10 @@ public class SIPCommander implements ISIPCommander {
         ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
         ptzXml.append("</Info>\r\n");
         ptzXml.append("</Control>\r\n");
-
-
-        CallIdHeader callIdHeader = device.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
-
-        Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
-        transmitRequest(device.getTransport(), request, errorEvent, okEvent);
+        
+        
+        Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request, errorEvent, okEvent);
 
     }
 
@@ -342,12 +322,17 @@ public class SIPCommander implements ISIPCommander {
                 subscribe.removeSubscribe(hookSubscribe);
             }
         });
-        //
+        String sdpIp;
+        if (!ObjectUtils.isEmpty(device.getSdpIp())) {
+            sdpIp = device.getSdpIp();
+        }else {
+            sdpIp = mediaServerItem.getSdpIp();
+        }
         StringBuffer content = new StringBuffer(200);
         content.append("v=0\r\n");
-        content.append("o=" + channelId + " 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
+        content.append("o=" + channelId + " 0 0 IN IP4 " + sdpIp + "\r\n");
         content.append("s=Play\r\n");
-        content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
+        content.append("c=IN IP4 " + sdpIp + "\r\n");
         content.append("t=0 0\r\n");
 
         if (userSetting.isSeniorSdp()) {
@@ -404,11 +389,10 @@ public class SIPCommander implements ISIPCommander {
         // f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率
 //			content.append("f=v/2/5/25/1/4000a/1/8/1" + "\r\n"); // 未发现支持此特性的设备
 
-        CallIdHeader callIdHeader = device.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
 
-        Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, ssrcInfo.getSsrc(), callIdHeader);
-        transmitRequest(device.getTransport(), request, (e -> {
+
+        Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, ssrcInfo.getSsrc(),sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, (e -> {
             streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
             mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
             errorEvent.response(e);
@@ -436,13 +420,18 @@ public class SIPCommander implements ISIPCommander {
 
 
         logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
-
+        String sdpIp;
+        if (!ObjectUtils.isEmpty(device.getSdpIp())) {
+            sdpIp = device.getSdpIp();
+        }else {
+            sdpIp = mediaServerItem.getSdpIp();
+        }
         StringBuffer content = new StringBuffer(200);
         content.append("v=0\r\n");
-        content.append("o=" + channelId + " 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
+        content.append("o=" + channelId + " 0 0 IN IP4 " + sdpIp + "\r\n");
         content.append("s=Playback\r\n");
         content.append("u=" + channelId + ":0\r\n");
-        content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
+        content.append("c=IN IP4 " + sdpIp + "\r\n");
         content.append("t=" + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime) + " "
                 + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) + "\r\n");
 
@@ -496,27 +485,25 @@ public class SIPCommander implements ISIPCommander {
 
         content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
         HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
         // 添加订阅
         subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
             if (hookEvent != null) {
-                InviteStreamInfo inviteStreamInfo = new InviteStreamInfo(mediaServerItemInUse, json, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream());
+                InviteStreamInfo inviteStreamInfo = new InviteStreamInfo(mediaServerItemInUse, json,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId(), "rtp", ssrcInfo.getStream());
                 hookEvent.call(inviteStreamInfo);
             }
             subscribe.removeSubscribe(hookSubscribe);
         });
-        Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader, ssrcInfo.getSsrc());
+        Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()), ssrcInfo.getSsrc());
 
-        transmitRequest(device.getTransport(), request, errorEvent, event -> {
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, event -> {
             ResponseEvent responseEvent = (ResponseEvent) event.event;
             SIPResponse response = (SIPResponse) responseEvent.getResponse();
-            streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.playback);
+            streamSession.put(device.getDeviceId(), channelId,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.playback);
             okEvent.response(event);
         });
         if (inviteStreamCallback != null) {
-            inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
+            inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId(), "rtp", ssrcInfo.getStream()));
         }
     }
 
@@ -542,7 +529,7 @@ public class SIPCommander implements ISIPCommander {
 //        String _ssrc = ssrcInfo.getSsrc();
         return content.toString();
     }
-    public void sendBoradcastInviteCmd(ServerTransaction serverTransaction,MediaServerItem mediaServerItem,SSRCInfo ssrcInfo,Device device,
+    public void sendBoradcastInviteCmd(SIPRequest request,MediaServerItem mediaServerItem,SSRCInfo ssrcInfo,Device device,
                                        String startTime, String endTime,
                                        InviteStreamCallback inviteStreamCallback,
                                        SipSubscribe.Event okEvent,
@@ -553,8 +540,8 @@ public class SIPCommander implements ISIPCommander {
         // 使用 返回sdp ack 数据给设备
         logger.info("返回给设备的 audio invite sdp部分为{}",sdpContent);
         try {
-            CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                    : udpSipProvider.getNewCallId();
+//            CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+//                    : udpSipProvider.getNewCallId();
 //            dynamicTask.startDelay(callIdHeader.getCallId(), () -> {
 //                logger.info("Ack 等待超时");
 //                // 释放 zlm 的推流端口
@@ -572,7 +559,7 @@ public class SIPCommander implements ISIPCommander {
 //                    device.getHostAddress(),
 //                    device.getPort()
 //            );
-            testInviteRequestProcessor.responseBroadcastSdpACK(serverTransaction,
+            testInviteRequestProcessor.responseBroadcastSdpACK(request,
                     sdpContent,
                     sipConfig.getId(),
                     mediaServerItem.getSdpIp(),
@@ -612,17 +599,23 @@ public class SIPCommander implements ISIPCommander {
      */
     @Override
     public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
-                                  String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
-                                  SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+                                  String startTime, String endTime, int downloadSpeed,
+                                  InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
+                                  SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
 
         logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
-
+        String sdpIp;
+        if (!ObjectUtils.isEmpty(device.getSdpIp())) {
+            sdpIp = device.getSdpIp();
+        }else {
+            sdpIp = mediaServerItem.getSdpIp();
+        }
         StringBuffer content = new StringBuffer(200);
         content.append("v=0\r\n");
-        content.append("o=" + channelId + " 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
+        content.append("o=" + channelId + " 0 0 IN IP4 " + sdpIp + "\r\n");
         content.append("s=Download\r\n");
         content.append("u=" + channelId + ":0\r\n");
-        content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
+        content.append("c=IN IP4 " + sdpIp + "\r\n");
         content.append("t=" + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime) + " "
                 + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) + "\r\n");
 
@@ -677,14 +670,14 @@ public class SIPCommander implements ISIPCommander {
         content.append("a=downloadspeed:" + downloadSpeed + "\r\n");
 
         content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
-
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
-
+        logger.debug("此时请求下载信令的ssrc===>{}",ssrcInfo.getSsrc());
         HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, null, mediaServerItem.getId());
         // 添加订阅
+        CallIdHeader newCallIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport());
+        String callId=newCallIdHeader.getCallId();
         subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
-            hookEvent.call(new InviteStreamInfo(mediaServerItem, json, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
+            logger.debug("sipc 添加订阅===callId {}",callId);
+            hookEvent.call(new InviteStreamInfo(mediaServerItem, json,callId, "rtp", ssrcInfo.getStream()));
             subscribe.removeSubscribe(hookSubscribe);
             hookSubscribe.getContent().put("regist", false);
             hookSubscribe.getContent().put("schema", "rtsp");
@@ -693,7 +686,7 @@ public class SIPCommander implements ISIPCommander {
                     (MediaServerItem mediaServerItemForEnd, JSONObject jsonForEnd) -> {
                         logger.info("[录像]下载结束, 发送BYE");
                         try {
-                            streamByeCmd(device, channelId, ssrcInfo.getStream(), callIdHeader.getCallId());
+                            streamByeCmd(device, channelId, ssrcInfo.getStream(),callId);
                         } catch (InvalidArgumentException | ParseException | SipException |
                                  SsrcTransactionNotFoundException e) {
                             logger.error("[录像]下载结束, 发送BYE失败 {}", e.getMessage());
@@ -701,14 +694,24 @@ public class SIPCommander implements ISIPCommander {
                     });
         });
 
-        Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader, ssrcInfo.getSsrc());
+        Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null,newCallIdHeader, ssrcInfo.getSsrc());
         if (inviteStreamCallback != null) {
-            inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
+            inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null,callId, "rtp", ssrcInfo.getStream()));
         }
-        transmitRequest(device.getTransport(), request, errorEvent, okEvent -> {
-            ResponseEvent responseEvent = (ResponseEvent) okEvent.event;
+
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, event -> {
+            ResponseEvent responseEvent = (ResponseEvent) event.event;
             SIPResponse response = (SIPResponse) responseEvent.getResponse();
-            streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.download);
+            String contentString =new String(response.getRawContent());
+            int ssrcIndex = contentString.indexOf("y=");
+            String ssrc=ssrcInfo.getSsrc();
+            if (ssrcIndex >= 0) {
+                ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
+            }
+            logger.debug("接收到的下载响应ssrc====>{}",ssrcInfo.getSsrc());
+            logger.debug("接收到的下载响应ssrc====>{}",ssrc);
+            streamSession.put(device.getDeviceId(), channelId, response.getCallIdHeader().getCallId(), ssrcInfo.getStream(), ssrc, mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.download);
+            okEvent.response(event);
         });
     }
 
@@ -735,7 +738,7 @@ public class SIPCommander implements ISIPCommander {
         streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
 
         Request byteRequest = headerProvider.createByteRequest(device, channelId, ssrcTransaction.getSipTransactionInfo());
-        transmitRequest(device.getTransport(), byteRequest, null, okEvent);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest, null, okEvent);
     }
 
     /**
@@ -768,12 +771,13 @@ public class SIPCommander implements ISIPCommander {
         broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
         broadcastXml.append("</Notify>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+//        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+//                : udpSipProvider.getNewCallId();
+//        Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
+//        transmitRequest(device.getTransport(), request);
         logger.info("[下发broadcast]");
-        Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
-        transmitRequest(device.getTransport(), request);
-
+        Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
     }
 
     public void startAudioBroadcastCmd(MediaServerItem mediaServerItem,
@@ -789,8 +793,8 @@ public class SIPCommander implements ISIPCommander {
         }
         logger.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
         // 创建broadcast 触发器
-        DeferredResult<String> result = new DeferredResult<>(3 * 1000L);
-        String key  = DeferredResultHolder.CALLBACK_CMD_BROADCAST + device.getDeviceId();
+//        DeferredResult<String> result = new DeferredResult<>(3 * 1000L);
+//        String key  = DeferredResultHolder.CALLBACK_CMD_BROADCAST + device.getDeviceId();
         // 下发broadcast命令
         StringBuffer broadcastXml = new StringBuffer(200);
         String charset = device.getCharset();
@@ -802,6 +806,11 @@ public class SIPCommander implements ISIPCommander {
         broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
         broadcastXml.append("</Notify>\r\n");
 
+        
+
+        Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
+
     }
 
     @Override
@@ -817,11 +826,10 @@ public class SIPCommander implements ISIPCommander {
         broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
         broadcastXml.append("</Notify>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        
 
-        Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
-        transmitRequest(device.getTransport(), request, errorEvent);
+        Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
 
     }
 
@@ -834,7 +842,7 @@ public class SIPCommander implements ISIPCommander {
      * @param recordCmdStr 录像命令:Record / StopRecord
      */
     @Override
-    public void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+    public void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
         cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -849,11 +857,10 @@ public class SIPCommander implements ISIPCommander {
         cmdXml.append("<RecordCmd>" + recordCmdStr + "</RecordCmd>\r\n");
         cmdXml.append("</Control>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        
 
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-        transmitRequest(device.getTransport(), request, errorEvent);
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
     }
 
     /**
@@ -874,11 +881,10 @@ public class SIPCommander implements ISIPCommander {
         cmdXml.append("<TeleBoot>Boot</TeleBoot>\r\n");
         cmdXml.append("</Control>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        
 
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-        transmitRequest(device.getTransport(), request);
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
     }
 
     /**
@@ -888,7 +894,7 @@ public class SIPCommander implements ISIPCommander {
      * @param guardCmdStr "SetGuard"/"ResetGuard"
      */
     @Override
-    public void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+    public void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
 
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
@@ -900,11 +906,8 @@ public class SIPCommander implements ISIPCommander {
         cmdXml.append("<GuardCmd>" + guardCmdStr + "</GuardCmd>\r\n");
         cmdXml.append("</Control>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
-
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-        transmitRequest(device.getTransport(), request, errorEvent);
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
     }
 
     /**
@@ -913,7 +916,7 @@ public class SIPCommander implements ISIPCommander {
      * @param device 视频设备
      */
     @Override
-    public void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+    public void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
 
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
@@ -937,11 +940,10 @@ public class SIPCommander implements ISIPCommander {
         }
         cmdXml.append("</Control>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        
 
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-        transmitRequest(device.getTransport(), request, errorEvent);
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
     }
 
     /**
@@ -967,23 +969,23 @@ public class SIPCommander implements ISIPCommander {
         cmdXml.append("<IFameCmd>Send</IFameCmd>\r\n");
         cmdXml.append("</Control>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        
 
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-        transmitRequest(device.getTransport(), request);
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
     }
 
     /**
      * 看守位控制命令
      *
      * @param device      视频设备
+     * @param channelId      通道id,非通道则是设备本身
      * @param enabled     看守位使能:1 = 开启,0 = 关闭
      * @param resetTime   自动归位时间间隔,开启看守位时使用,单位:秒(s)
      * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255
      */
     @Override
-    public void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+    public void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
 
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
@@ -1015,11 +1017,10 @@ public class SIPCommander implements ISIPCommander {
         cmdXml.append("</HomePosition>\r\n");
         cmdXml.append("</Control>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        
 
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-        transmitRequest(device.getTransport(), request, errorEvent);
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
     }
 
     /**
@@ -1079,11 +1080,10 @@ public class SIPCommander implements ISIPCommander {
         cmdXml.append("</BasicParam>\r\n");
         cmdXml.append("</Control>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        
 
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-        transmitRequest(device.getTransport(), request, errorEvent);
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
     }
 
     /**
@@ -1103,12 +1103,9 @@ public class SIPCommander implements ISIPCommander {
         catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
         catalogXml.append("</Query>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
 
-        Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-
-        transmitRequest(device.getTransport(), request, errorEvent);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
     }
 
     /**
@@ -1128,12 +1125,11 @@ public class SIPCommander implements ISIPCommander {
         catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
         catalogXml.append("</Query>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        
 
-        Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
+        Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
 
-        transmitRequest(device.getTransport(), request);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
 
     }
 
@@ -1154,12 +1150,11 @@ public class SIPCommander implements ISIPCommander {
         catalogXml.append("  <DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
         catalogXml.append("</Query>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        
 
-        Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
+        Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
 
-        transmitRequest(device.getTransport(), request, errorEvent);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
     }
 
     /**
@@ -1200,13 +1195,12 @@ public class SIPCommander implements ISIPCommander {
         }
         recordInfoXml.append("</Query>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        
 
         Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(),
-                SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
+                SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
 
-        transmitRequest(device.getTransport(), request, errorEvent, okEvent);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent);
     }
 
     /**
@@ -1252,11 +1246,10 @@ public class SIPCommander implements ISIPCommander {
         }
         cmdXml.append("</Query>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        
 
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-        transmitRequest(device.getTransport(), request, errorEvent);
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
     }
 
     /**
@@ -1283,11 +1276,10 @@ public class SIPCommander implements ISIPCommander {
         cmdXml.append("<ConfigType>" + configType + "</ConfigType>\r\n");
         cmdXml.append("</Query>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        
 
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-        transmitRequest(device.getTransport(), request, errorEvent);
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
     }
 
     /**
@@ -1311,11 +1303,10 @@ public class SIPCommander implements ISIPCommander {
         }
         cmdXml.append("</Query>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        
 
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-        transmitRequest(device.getTransport(), request, errorEvent);
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
     }
 
     /**
@@ -1336,12 +1327,11 @@ public class SIPCommander implements ISIPCommander {
         mobilePostitionXml.append("<Interval>60</Interval>\r\n");
         mobilePostitionXml.append("</Query>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        
 
-        Request request = headerProvider.createMessageRequest(device, mobilePostitionXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
+        Request request = headerProvider.createMessageRequest(device, mobilePostitionXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
 
-        transmitRequest(device.getTransport(), request, errorEvent);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
 
     }
 
@@ -1369,14 +1359,13 @@ public class SIPCommander implements ISIPCommander {
         CallIdHeader callIdHeader;
 
         if (requestOld != null) {
-            callIdHeader = sipFactory.createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId());
+            callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId());
         } else {
-            callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                    : udpSipProvider.getNewCallId();
+            callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport());
         }
-        SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, subscribePostitionXml.toString(), requestOld, device.getSubscribeCycleForMobilePosition(), "presence", callIdHeader); //Position;id=" + tm.substring(tm.length() - 4));
+        SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, subscribePostitionXml.toString(), requestOld, device.getSubscribeCycleForMobilePosition(), "presence",callIdHeader); //Position;id=" + tm.substring(tm.length() - 4));
 
-        transmitRequest(device.getTransport(), request, errorEvent, okEvent);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent);
         return request;
     }
 
@@ -1423,11 +1412,10 @@ public class SIPCommander implements ISIPCommander {
         }
         cmdXml.append("</Query>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        
 
-        Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), null, expires, "presence", callIdHeader);
-        transmitRequest(device.getTransport(), request);
+        Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), null, expires, "presence",sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
 
     }
 
@@ -1446,20 +1434,18 @@ public class SIPCommander implements ISIPCommander {
         CallIdHeader callIdHeader;
 
         if (requestOld != null) {
-            callIdHeader = sipFactory.createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId());
+            callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId());
         } else {
-            callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                    : udpSipProvider.getNewCallId();
+            callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport());
         }
 
         // 有效时间默认为60秒以上
         SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, cmdXml.toString(), requestOld, device.getSubscribeCycleForCatalog(), "Catalog",
                 callIdHeader);
-        transmitRequest(device.getTransport(), request, errorEvent, okEvent);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent);
         return request;
     }
 
-
     @Override
     public SIPRequest hfyAiAlarmSubScribe(Device device, int expires, String startTime, String endTime, List<HfyAiInfo> aiDataList, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent)throws InvalidArgumentException, SipException, ParseException  {
         StringBuffer cmdXml = new StringBuffer(200);
@@ -1501,11 +1487,12 @@ public class SIPCommander implements ISIPCommander {
 
         CallIdHeader callIdHeader;
 
-        callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport());
 
+//        SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, cmdXml.toString(), null, expires, "hfyAi", callIdHeader);
+//        transmitRequest(device.getTransport(), request, errorEvent, okEvent);
         SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, cmdXml.toString(), null, expires, "hfyAi", callIdHeader);
-        transmitRequest(device.getTransport(), request, errorEvent, okEvent);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request,errorEvent, okEvent);
         return request;
     }
 
@@ -1525,59 +1512,14 @@ public class SIPCommander implements ISIPCommander {
         }
         dragXml.append(cmdString);
         dragXml.append("</Control>\r\n");
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
-        Request request = headerProvider.createMessageRequest(device, dragXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
+        
+        Request request = headerProvider.createMessageRequest(device, dragXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
         logger.debug("拉框信令: " + request.toString());
-        transmitRequest(device.getTransport(), request);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request);
     }
 
 
-    @Override
-    public void transmitRequest(String transport, Request request) throws SipException, ParseException {
-        transmitRequest(transport, request, null, null);
-    }
-
-    @Override
-    public void transmitRequest(String transport, Request request, SipSubscribe.Event errorEvent) throws SipException, ParseException {
-        transmitRequest(transport, request, errorEvent, null);
-    }
-
-    @Override
-    public void transmitRequest(String transport, Request request, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, ParseException {
-
-        if (request.getHeader(UserAgentHeader.NAME) == null) {
-            try {
-                request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
-            } catch (ParseException e) {
-                logger.error("添加UserAgentHeader失败", e);
-            }
-        }
-
-        CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
-        // 添加错误订阅
-        if (errorEvent != null) {
-            sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> {
-                errorEvent.response(eventResult);
-                sipSubscribe.removeErrorSubscribe(eventResult.callId);
-                sipSubscribe.removeOkSubscribe(eventResult.callId);
-            }));
-        }
-        // 添加订阅
-        if (okEvent != null) {
-            sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult -> {
-                okEvent.response(eventResult);
-                sipSubscribe.removeOkSubscribe(eventResult.callId);
-                sipSubscribe.removeErrorSubscribe(eventResult.callId);
-            });
-        }
-        if ("TCP".equals(transport)) {
-            tcpSipProvider.sendRequest(request);
-        } else if ("UDP".equals(transport)) {
-            udpSipProvider.sendRequest(request);
-        }
-
-    }
+    
 
 
     /**
@@ -1652,7 +1594,7 @@ public class SIPCommander implements ISIPCommander {
             return;
         }
 
-        transmitRequest(device.getTransport(), request, errorEvent, okEvent);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent);
     }
 
     @Override
@@ -1660,7 +1602,7 @@ public class SIPCommander implements ISIPCommander {
         if (device == null) {
             return;
         }
-        logger.info("[发送 报警通知] {}/{}->{},{}", device.getDeviceId(), deviceAlarm.getChannelId(),
+        logger.info("[发送报警通知]设备: {}/{}->{},{}", device.getDeviceId(), deviceAlarm.getChannelId(),
                 deviceAlarm.getLongitude(), deviceAlarm.getLatitude());
 
         String characterSet = device.getCharset();
@@ -1672,7 +1614,7 @@ public class SIPCommander implements ISIPCommander {
         deviceStatusXml.append("<DeviceID>" + deviceAlarm.getChannelId() + "</DeviceID>\r\n");
         deviceStatusXml.append("<AlarmPriority>" + deviceAlarm.getAlarmPriority() + "</AlarmPriority>\r\n");
         deviceStatusXml.append("<AlarmMethod>" + deviceAlarm.getAlarmMethod() + "</AlarmMethod>\r\n");
-        deviceStatusXml.append("<AlarmTime>" + deviceAlarm.getAlarmTime() + "</AlarmTime>\r\n");
+        deviceStatusXml.append("<AlarmTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(deviceAlarm.getAlarmTime()) + "</AlarmTime>\r\n");
         deviceStatusXml.append("<AlarmDescription>" + deviceAlarm.getAlarmDescription() + "</AlarmDescription>\r\n");
         deviceStatusXml.append("<Longitude>" + deviceAlarm.getLongitude() + "</Longitude>\r\n");
         deviceStatusXml.append("<Latitude>" + deviceAlarm.getLatitude() + "</Latitude>\r\n");
@@ -1681,10 +1623,9 @@ public class SIPCommander implements ISIPCommander {
         deviceStatusXml.append("</info>\r\n");
         deviceStatusXml.append("</Notify>\r\n");
 
-        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
-        Request request = headerProvider.createMessageRequest(device, deviceStatusXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
-        transmitRequest(device.getTransport(), request);
+        
+        Request request = headerProvider.createMessageRequest(device, deviceStatusXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request);
 
 
     }

+ 234 - 214
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java

@@ -1,33 +1,36 @@
 package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSON;
+import com.genersoft.iot.vmp.conf.DynamicTask;
+import com.genersoft.iot.vmp.gb28181.SipLayer;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
+import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderPlarformProvider;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
-import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
-import com.genersoft.iot.vmp.utils.DateUtil;
 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import gov.nist.javax.sip.SipProviderImpl;
+import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
+import com.genersoft.iot.vmp.utils.DateUtil;
+import com.genersoft.iot.vmp.utils.GitUtil;
 import gov.nist.javax.sip.message.MessageFactoryImpl;
 import gov.nist.javax.sip.message.SIPRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.annotation.DependsOn;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.lang.Nullable;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
 
-import javax.sip.*;
-import javax.sip.header.*;
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import javax.sip.header.CallIdHeader;
+import javax.sip.header.WWWAuthenticateHeader;
 import javax.sip.message.Request;
 import java.text.ParseException;
 import java.util.ArrayList;
@@ -54,21 +57,17 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
     @Autowired
     private ZLMRTPServerFactory zlmrtpServerFactory;
 
-    @Lazy
     @Autowired
-    @Qualifier(value="tcpSipProvider")
-    private SipProviderImpl tcpSipProvider;
+    private SipLayer sipLayer;
 
-    @Lazy
     @Autowired
-    @Qualifier(value="udpSipProvider")
-    private SipProviderImpl udpSipProvider;
+    private SIPSender sipSender;
 
     @Autowired
-    private SipFactory sipFactory;
+    private DynamicTask dynamicTask;
 
     @Autowired
-    private SubscribeHolder subscribeHolder;
+    private GitUtil gitUtil;
 
     @Override
     public void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException {
@@ -85,13 +84,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                             SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister) throws SipException, InvalidArgumentException, ParseException {
             Request request;
             if (!registerAgain ) {
-                CallIdHeader callIdHeader = null;
-                if(parentPlatform.getTransport().equalsIgnoreCase("TCP")) {
-                    callIdHeader = tcpSipProvider.getNewCallId();
-                }
-                if(parentPlatform.getTransport().equalsIgnoreCase("UDP")) {
-                    callIdHeader = udpSipProvider.getNewCallId();
-                }
+                CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
 
                 request = headerProviderPlatformProvider.createRegisterRequest(parentPlatform,
                         redisCatchStorage.getCSEQ(), SipUtils.getNewFromTag(),
@@ -113,28 +106,27 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                 });
 
             }else {
-                CallIdHeader callIdHeader = parentPlatform.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-                        : udpSipProvider.getNewCallId();
+                CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
                 request = headerProviderPlatformProvider.createRegisterRequest(parentPlatform, SipUtils.getNewFromTag(), null, callId, www, callIdHeader, isRegister);
             }
 
-            transmitRequest(parentPlatform, request, null, okEvent);
+            sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, null, okEvent);
     }
 
     @Override
     public String keepalive(ParentPlatform parentPlatform,SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException {
             String characterSet = parentPlatform.getCharacterSet();
             StringBuffer keepaliveXml = new StringBuffer(200);
-            keepaliveXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-            keepaliveXml.append("<Notify>\r\n");
-            keepaliveXml.append("<CmdType>Keepalive</CmdType>\r\n");
-            keepaliveXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-            keepaliveXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
-            keepaliveXml.append("<Status>OK</Status>\r\n");
-            keepaliveXml.append("</Notify>\r\n");
+            keepaliveXml.append("<?xml version=\"1.0\" encoding=\"")
+                    .append(characterSet).append("\"?>\r\n")
+                    .append("<Notify>\r\n")
+                    .append("<CmdType>Keepalive</CmdType>\r\n")
+                    .append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n")
+                    .append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n")
+                    .append("<Status>OK</Status>\r\n")
+                    .append("</Notify>\r\n");
 
-            CallIdHeader callIdHeader = parentPlatform.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-                    : udpSipProvider.getNewCallId();
+        CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
 
             Request request = headerProviderPlatformProvider.createMessageRequest(
                     parentPlatform,
@@ -142,39 +134,10 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                     SipUtils.getNewFromTag(),
                     SipUtils.getNewViaTag(),
                     callIdHeader);
-            transmitRequest(parentPlatform, request, errorEvent, okEvent);
+            sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, errorEvent, okEvent);
         return callIdHeader.getCallId();
     }
 
-    private void transmitRequest(ParentPlatform parentPlatform, Request request) throws SipException {
-        transmitRequest(parentPlatform, request, null, null);
-    }
-
-    private void transmitRequest(ParentPlatform parentPlatform, Request request, SipSubscribe.Event errorEvent) throws SipException {
-        transmitRequest(parentPlatform, request, errorEvent, null);
-    }
-
-    private void transmitRequest(ParentPlatform parentPlatform, Request request, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException {
-        logger.debug("\n发送消息:\n{}", request);
-        if("TCP".equalsIgnoreCase(parentPlatform.getTransport())) {
-            tcpSipProvider.sendRequest(request);
-
-        } else if("UDP".equalsIgnoreCase(parentPlatform.getTransport())) {
-            udpSipProvider.sendRequest(request);
-        }
-
-        CallIdHeader callIdHeader = (CallIdHeader)request.getHeader(CallIdHeader.NAME);
-        // 添加错误订阅
-        if (errorEvent != null) {
-            sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), errorEvent);
-        }
-        // 添加订阅
-        if (okEvent != null) {
-            sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), okEvent);
-        }
-
-    }
-
     /**
      * 向上级回复通道信息
      * @param channel 通道信息
@@ -194,11 +157,10 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         String catalogXml = getCatalogXml(channels, sn, parentPlatform, size);
 
         // callid
-        CallIdHeader callIdHeader = parentPlatform.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
 
         Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, catalogXml.toString(), fromTag, SipUtils.getNewViaTag(), callIdHeader);
-        transmitRequest(parentPlatform, request);
+        sipSender.transmitRequest(parentPlatform.getDeviceIp(), request);
 
     }
 
@@ -207,18 +169,18 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         if ( parentPlatform ==null) {
             return ;
         }
-        sendCatalogResponse(channels, parentPlatform, sn, fromTag, 0);
+        sendCatalogResponse(channels, parentPlatform, sn, fromTag, 0, true);
     }
     private String getCatalogXml(List<DeviceChannel> channels, String sn, ParentPlatform parentPlatform, int size) {
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer catalogXml = new StringBuffer(600);
-        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet +"\"?>\r\n");
-        catalogXml.append("<Response>\r\n");
-        catalogXml.append("<CmdType>Catalog</CmdType>\r\n");
-        catalogXml.append("<SN>" +sn + "</SN>\r\n");
-        catalogXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
-        catalogXml.append("<SumNum>" + size + "</SumNum>\r\n");
-        catalogXml.append("<DeviceList Num=\"" + channels.size() +"\">\r\n");
+        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet +"\"?>\r\n")
+                .append("<Response>\r\n")
+                .append("<CmdType>Catalog</CmdType>\r\n")
+                .append("<SN>" +sn + "</SN>\r\n")
+                .append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n")
+                .append("<SumNum>" + size + "</SumNum>\r\n")
+                .append("<DeviceList Num=\"" + channels.size() +"\">\r\n");
         if (channels.size() > 0) {
             for (DeviceChannel channel : channels) {
                 if (parentPlatform.getServerGBId().equals(channel.getParentId())) {
@@ -258,6 +220,26 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                         }else {
                             catalogXml.append("<Address>" + channel.getAddress() + "</Address>\r\n");
                         }
+                        catalogXml.append("<Block>" + channel.getBlock() + "</Block>\r\n");
+                        catalogXml.append("<SafetyWay>" + channel.getSafetyWay() + "</SafetyWay>\r\n");
+                        catalogXml.append("<CertNum>" + channel.getCertNum() + "</CertNum>\r\n");
+                        catalogXml.append("<Certifiable>" + channel.getCertifiable() + "</Certifiable>\r\n");
+                        catalogXml.append("<ErrCode>" + channel.getErrCode() + "</ErrCode>\r\n");
+                        catalogXml.append("<EndTime>" + channel.getEndTime() + "</EndTime>\r\n");
+                        catalogXml.append("<Secrecy>" + channel.getSecrecy() + "</Secrecy>\r\n");
+                        catalogXml.append("<IPAddress>" + channel.getIpAddress() + "</IPAddress>\r\n");
+                        catalogXml.append("<Port>" + channel.getPort() + "</Port>\r\n");
+                        catalogXml.append("<Password>" + channel.getPort() + "</Password>\r\n");
+                        catalogXml.append("<PTZType>" + channel.getPTZType() + "</PTZType>\r\n");
+                        catalogXml.append("<Status>" + (channel.getStatus() == 1?"ON":"OFF") + "</Status>\r\n");
+                        catalogXml.append("<Longitude>" +
+                                (channel.getLongitudeWgs84() != 0? channel.getLongitudeWgs84():channel.getLongitude())
+                                + "</Longitude>\r\n");
+                        catalogXml.append("<Latitude>" +
+                                (channel.getLatitudeWgs84() != 0? channel.getLatitudeWgs84():channel.getLatitude())
+                                + "</Latitude>\r\n");
+
+
                     }
                 }
                 catalogXml.append("</Item>\r\n");
@@ -269,7 +251,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         return catalogXml.toString();
     }
 
-    private void sendCatalogResponse(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag, int index) throws SipException, InvalidArgumentException, ParseException {
+    private void sendCatalogResponse(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag, int index, boolean sendAfterResponse) throws SipException, InvalidArgumentException, ParseException {
         if (index >= channels.size()) {
             return;
         }
@@ -281,18 +263,54 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         }
         String catalogXml = getCatalogXml(deviceChannels, sn, parentPlatform, channels.size());
         // callid
-        CallIdHeader callIdHeader = parentPlatform.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
-
-        Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, catalogXml, fromTag, SipUtils.getNewViaTag(), callIdHeader);
-        transmitRequest(parentPlatform, request, null, eventResult -> {
-            int indexNext = index + parentPlatform.getCatalogGroup();
-            try {
-                sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext);
-            } catch (SipException | InvalidArgumentException | ParseException e) {
-                logger.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage());
-            }
-        });
+        CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
+
+        SIPRequest request = (SIPRequest)headerProviderPlatformProvider.createMessageRequest(parentPlatform, catalogXml, fromTag, SipUtils.getNewViaTag(), callIdHeader);
+
+        String timeoutTaskKey = "catalog_task_" + parentPlatform.getServerGBId() + sn;
+
+        String callId = request.getCallIdHeader().getCallId();
+
+        logger.info("[命令发送] 国标级联{} 目录查询回复: 共{}条,已发送{}条", parentPlatform.getServerGBId(),
+                channels.size(), Math.min(index + parentPlatform.getCatalogGroup(), channels.size()));
+        logger.debug(catalogXml);
+        if (sendAfterResponse) {
+            // 默认按照收到200回复后发送下一条, 如果超时收不到回复,就以30毫秒的间隔直接发送。
+            dynamicTask.startDelay(timeoutTaskKey, ()->{
+                sipSubscribe.removeOkSubscribe(callId);
+                int indexNext = index + parentPlatform.getCatalogGroup();
+                try {
+                    sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext, false);
+                } catch (SipException | InvalidArgumentException | ParseException e) {
+                    logger.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage());
+                }
+            }, 3000);
+            sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, eventResult -> {
+                logger.error("[目录推送失败] 国标级联 platform : {}, code: {}, msg: {}, 停止发送", parentPlatform.getServerGBId(), eventResult.statusCode, eventResult.msg);
+                dynamicTask.stop(timeoutTaskKey);
+            }, eventResult -> {
+                dynamicTask.stop(timeoutTaskKey);
+                int indexNext = index + parentPlatform.getCatalogGroup();
+                try {
+                    sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext, true);
+                } catch (SipException | InvalidArgumentException | ParseException e) {
+                    logger.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage());
+                }
+            });
+        }else {
+            sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, eventResult -> {
+                logger.error("[目录推送失败] 国标级联 platform : {}, code: {}, msg: {}, 停止发送", parentPlatform.getServerGBId(), eventResult.statusCode, eventResult.msg);
+                dynamicTask.stop(timeoutTaskKey);
+            }, null);
+            dynamicTask.startDelay(timeoutTaskKey, ()->{
+                int indexNext = index + parentPlatform.getCatalogGroup();
+                try {
+                    sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext, false);
+                } catch (SipException | InvalidArgumentException | ParseException e) {
+                    logger.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage());
+                }
+            }, 30);
+        }
     }
 
     /**
@@ -303,29 +321,33 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
      * @return
      */
     @Override
-    public void deviceInfoResponse(ParentPlatform parentPlatform, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException {
+    public void deviceInfoResponse(ParentPlatform parentPlatform,Device device, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException {
         if (parentPlatform == null) {
             return;
         }
+        String deviceId = device == null ? parentPlatform.getDeviceGBId() : device.getDeviceId();
+        String deviceName = device == null ? parentPlatform.getName() : device.getName();
+        String manufacturer = device == null ? "WVP-28181-PRO" : device.getManufacturer();
+        String model = device == null ? "platform" : device.getModel();
+        String firmware = device == null ? gitUtil.getBuildVersion() : device.getFirmware();
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer deviceInfoXml = new StringBuffer(600);
         deviceInfoXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
         deviceInfoXml.append("<Response>\r\n");
         deviceInfoXml.append("<CmdType>DeviceInfo</CmdType>\r\n");
         deviceInfoXml.append("<SN>" +sn + "</SN>\r\n");
-        deviceInfoXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
-        deviceInfoXml.append("<DeviceName>" + parentPlatform.getName() + "</DeviceName>\r\n");
-        deviceInfoXml.append("<Manufacturer>wvp</Manufacturer>\r\n");
-        deviceInfoXml.append("<Model>wvp-28181-2.0</Model>\r\n");
-        deviceInfoXml.append("<Firmware>2.0.202107</Firmware>\r\n");
+        deviceInfoXml.append("<DeviceID>" + deviceId + "</DeviceID>\r\n");
+        deviceInfoXml.append("<DeviceName>" + deviceName + "</DeviceName>\r\n");
+        deviceInfoXml.append("<Manufacturer>" + manufacturer + "</Manufacturer>\r\n");
+        deviceInfoXml.append("<Model>" + model + "</Model>\r\n");
+        deviceInfoXml.append("<Firmware>" + firmware + "</Firmware>\r\n");
         deviceInfoXml.append("<Result>OK</Result>\r\n");
         deviceInfoXml.append("</Response>\r\n");
 
-        CallIdHeader callIdHeader = parentPlatform.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
 
         Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, deviceInfoXml.toString(), fromTag, SipUtils.getNewViaTag(), callIdHeader);
-        transmitRequest(parentPlatform, request);
+        sipSender.transmitRequest(parentPlatform.getDeviceIp(), request);
     }
 
     /**
@@ -336,28 +358,27 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
      * @return
      */
     @Override
-    public void deviceStatusResponse(ParentPlatform parentPlatform, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException {
+    public void deviceStatusResponse(ParentPlatform parentPlatform,String channelId, String sn, String fromTag,int status) throws SipException, InvalidArgumentException, ParseException {
         if (parentPlatform == null) {
             return ;
         }
+        String statusStr = (status==1)?"ONLINE":"OFFLINE";
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer deviceStatusXml = new StringBuffer(600);
-        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        deviceStatusXml.append("<Response>\r\n");
-        deviceStatusXml.append("<CmdType>DeviceStatus</CmdType>\r\n");
-        deviceStatusXml.append("<SN>" +sn + "</SN>\r\n");
-        deviceStatusXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
-        deviceStatusXml.append("<Result>OK</Result>\r\n");
-        deviceStatusXml.append("<Online>ONLINE</Online>\r\n");
-        deviceStatusXml.append("<Status>OK</Status>\r\n");
-        deviceStatusXml.append("</Response>\r\n");
-
-        CallIdHeader callIdHeader = parentPlatform.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Response>\r\n")
+                .append("<CmdType>DeviceStatus</CmdType>\r\n")
+                .append("<SN>" +sn + "</SN>\r\n")
+                .append("<DeviceID>" + channelId + "</DeviceID>\r\n")
+                .append("<Result>OK</Result>\r\n")
+                .append("<Online>"+statusStr+"</Online>\r\n")
+                .append("<Status>OK</Status>\r\n")
+                .append("</Response>\r\n");
+
+        CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
 
         Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, deviceStatusXml.toString(), fromTag, SipUtils.getNewViaTag(), callIdHeader);
-        transmitRequest(parentPlatform, request);
-
+        sipSender.transmitRequest(parentPlatform.getDeviceIp(), request);
     }
 
     @Override
@@ -371,18 +392,18 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
 
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer deviceStatusXml = new StringBuffer(600);
-        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        deviceStatusXml.append("<Notify>\r\n");
-        deviceStatusXml.append("<CmdType>MobilePosition</CmdType>\r\n");
-        deviceStatusXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-        deviceStatusXml.append("<DeviceID>" + gpsMsgInfo.getId() + "</DeviceID>\r\n");
-        deviceStatusXml.append("<Time>" + gpsMsgInfo.getTime() + "</Time>\r\n");
-        deviceStatusXml.append("<Longitude>" + gpsMsgInfo.getLng() + "</Longitude>\r\n");
-        deviceStatusXml.append("<Latitude>" + gpsMsgInfo.getLat() + "</Latitude>\r\n");
-        deviceStatusXml.append("<Speed>" + gpsMsgInfo.getSpeed() + "</Speed>\r\n");
-        deviceStatusXml.append("<Direction>" + gpsMsgInfo.getDirection() + "</Direction>\r\n");
-        deviceStatusXml.append("<Altitude>" + gpsMsgInfo.getAltitude() + "</Altitude>\r\n");
-        deviceStatusXml.append("</Notify>\r\n");
+        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Notify>\r\n")
+                .append("<CmdType>MobilePosition</CmdType>\r\n")
+                .append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n")
+                .append("<DeviceID>" + gpsMsgInfo.getId() + "</DeviceID>\r\n")
+                .append("<Time>" + gpsMsgInfo.getTime() + "</Time>\r\n")
+                .append("<Longitude>" + gpsMsgInfo.getLng() + "</Longitude>\r\n")
+                .append("<Latitude>" + gpsMsgInfo.getLat() + "</Latitude>\r\n")
+                .append("<Speed>" + gpsMsgInfo.getSpeed() + "</Speed>\r\n")
+                .append("<Direction>" + gpsMsgInfo.getDirection() + "</Direction>\r\n")
+                .append("<Altitude>" + gpsMsgInfo.getAltitude() + "</Altitude>\r\n")
+                .append("</Notify>\r\n");
 
        sendNotify(parentPlatform, deviceStatusXml.toString(), subscribeInfo, eventResult -> {
             logger.error("发送NOTIFY通知消息失败。错误:{} {}", eventResult.statusCode, eventResult.msg);
@@ -395,31 +416,30 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         if (parentPlatform == null) {
             return;
         }
-        logger.info("[发送报警通知] {}/{}->{},{}: {}", parentPlatform.getServerGBId(), deviceAlarm.getChannelId(),
-                deviceAlarm.getLongitude(), deviceAlarm.getLatitude(), JSONObject.toJSON(deviceAlarm));
+        logger.info("[发送报警通知]平台: {}/{}->{},{}: {}", parentPlatform.getServerGBId(), deviceAlarm.getChannelId(),
+                deviceAlarm.getLongitude(), deviceAlarm.getLatitude(), JSON.toJSONString(deviceAlarm));
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer deviceStatusXml = new StringBuffer(600);
-        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        deviceStatusXml.append("<Notify>\r\n");
-        deviceStatusXml.append("<CmdType>Alarm</CmdType>\r\n");
-        deviceStatusXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-        deviceStatusXml.append("<DeviceID>" + deviceAlarm.getChannelId() + "</DeviceID>\r\n");
-        deviceStatusXml.append("<AlarmPriority>" + deviceAlarm.getAlarmPriority() + "</AlarmPriority>\r\n");
-        deviceStatusXml.append("<AlarmMethod>" + deviceAlarm.getAlarmMethod() + "</AlarmMethod>\r\n");
-        deviceStatusXml.append("<AlarmTime>" + deviceAlarm.getAlarmTime() + "</AlarmTime>\r\n");
-        deviceStatusXml.append("<AlarmDescription>" + deviceAlarm.getAlarmDescription() + "</AlarmDescription>\r\n");
-        deviceStatusXml.append("<Longitude>" + deviceAlarm.getLongitude() + "</Longitude>\r\n");
-        deviceStatusXml.append("<Latitude>" + deviceAlarm.getLatitude() + "</Latitude>\r\n");
-        deviceStatusXml.append("<info>\r\n");
-        deviceStatusXml.append("<AlarmType>" + deviceAlarm.getAlarmType() + "</AlarmType>\r\n");
-        deviceStatusXml.append("</info>\r\n");
-        deviceStatusXml.append("</Notify>\r\n");
-
-        CallIdHeader callIdHeader = parentPlatform.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Notify>\r\n")
+                .append("<CmdType>Alarm</CmdType>\r\n")
+                .append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n")
+                .append("<DeviceID>" + deviceAlarm.getChannelId() + "</DeviceID>\r\n")
+                .append("<AlarmPriority>" + deviceAlarm.getAlarmPriority() + "</AlarmPriority>\r\n")
+                .append("<AlarmMethod>" + deviceAlarm.getAlarmMethod() + "</AlarmMethod>\r\n")
+                .append("<AlarmTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(deviceAlarm.getAlarmTime()) + "</AlarmTime>\r\n")
+                .append("<AlarmDescription>" + deviceAlarm.getAlarmDescription() + "</AlarmDescription>\r\n")
+                .append("<Longitude>" + deviceAlarm.getLongitude() + "</Longitude>\r\n")
+                .append("<Latitude>" + deviceAlarm.getLatitude() + "</Latitude>\r\n")
+                .append("<info>\r\n")
+                .append("<AlarmType>" + deviceAlarm.getAlarmType() + "</AlarmType>\r\n")
+                .append("</info>\r\n")
+                .append("</Notify>\r\n");
+
+        CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
 
         Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, deviceStatusXml.toString(), SipUtils.getNewFromTag(), SipUtils.getNewViaTag(), callIdHeader);
-        transmitRequest(parentPlatform, request);
+        sipSender.transmitRequest(parentPlatform.getDeviceIp(), request);
 
     }
 
@@ -459,27 +479,27 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
     private void sendNotify(ParentPlatform parentPlatform, String catalogXmlContent,
                                    SubscribeInfo subscribeInfo, SipSubscribe.Event errorEvent,  SipSubscribe.Event okEvent )
             throws SipException, ParseException, InvalidArgumentException {
-		MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipFactory.createMessageFactory();
+		MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipLayer.getSipFactory().createMessageFactory();
         String characterSet = parentPlatform.getCharacterSet();
  		// 设置编码, 防止中文乱码
 		messageFactory.setDefaultContentEncodingCharset(characterSet);
 
         SIPRequest notifyRequest = headerProviderPlatformProvider.createNotifyRequest(parentPlatform, catalogXmlContent, subscribeInfo);
 
-        transmitRequest(parentPlatform, notifyRequest);
+        sipSender.transmitRequest(parentPlatform.getDeviceIp(), notifyRequest);
     }
 
     private  String getCatalogXmlContentForCatalogAddOrUpdate(ParentPlatform parentPlatform, List<DeviceChannel> channels, int sumNum, String type, SubscribeInfo subscribeInfo) {
         StringBuffer catalogXml = new StringBuffer(600);
 
         String characterSet = parentPlatform.getCharacterSet();
-        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        catalogXml.append("<Notify>\r\n");
-        catalogXml.append("<CmdType>Catalog</CmdType>\r\n");
-        catalogXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        catalogXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
-        catalogXml.append("<SumNum>1</SumNum>\r\n");
-        catalogXml.append("<DeviceList Num=\"" + channels.size() + "\">\r\n");
+        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Notify>\r\n")
+                .append("<CmdType>Catalog</CmdType>\r\n")
+                .append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n")
+                .append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n")
+                .append("<SumNum>1</SumNum>\r\n")
+                .append("<DeviceList Num=\"" + channels.size() + "\">\r\n");
         if (channels.size() > 0) {
             for (DeviceChannel channel : channels) {
                 if (parentPlatform.getServerGBId().equals(channel.getParentId())) {
@@ -500,16 +520,16 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                 catalogXml.append("<Parental>" + channel.getParental() + "</Parental>\r\n");
                 if (channel.getParental() == 0) {
                     // 通道项
-                    catalogXml.append("<Manufacturer>" + channel.getManufacture() + "</Manufacturer>\r\n");
-                    catalogXml.append("<Secrecy>" + channel.getSecrecy() + "</Secrecy>\r\n");
-                    catalogXml.append("<RegisterWay>" + channel.getRegisterWay() + "</RegisterWay>\r\n");
-                    catalogXml.append("<Status>" + (channel.getStatus() == 0 ? "OFF" : "ON") + "</Status>\r\n");
+                    catalogXml.append("<Manufacturer>" + channel.getManufacture() + "</Manufacturer>\r\n")
+                            .append("<Secrecy>" + channel.getSecrecy() + "</Secrecy>\r\n")
+                            .append("<RegisterWay>" + channel.getRegisterWay() + "</RegisterWay>\r\n")
+                            .append("<Status>" + (channel.getStatus() == 0 ? "OFF" : "ON") + "</Status>\r\n");
 
                     if (channel.getChannelType() != 2) {  // 业务分组/虚拟组织/行政区划 不设置以下属性
-                        catalogXml.append("<Model>" + channel.getModel() + "</Model>\r\n");
-                        catalogXml.append("<Owner> " + channel.getOwner()+ "</Owner>\r\n");
-                        catalogXml.append("<CivilCode>" + channel.getCivilCode() + "</CivilCode>\r\n");
-                        catalogXml.append("<Address>" + channel.getAddress() + "</Address>\r\n");
+                        catalogXml.append("<Model>" + channel.getModel() + "</Model>\r\n")
+                                .append("<Owner> " + channel.getOwner()+ "</Owner>\r\n")
+                                .append("<CivilCode>" + channel.getCivilCode() + "</CivilCode>\r\n")
+                                .append("<Address>" + channel.getAddress() + "</Address>\r\n");
                     }
                     if (!"presence".equals(subscribeInfo.getEventType())) {
                         catalogXml.append("<Event>" + type + "</Event>\r\n");
@@ -519,8 +539,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                 catalogXml.append("</Item>\r\n");
             }
         }
-        catalogXml.append("</DeviceList>\r\n");
-        catalogXml.append("</Notify>\r\n");
+        catalogXml.append("</DeviceList>\r\n")
+                .append("</Notify>\r\n");
         return catalogXml.toString();
     }
 
@@ -566,26 +586,26 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
 
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer catalogXml = new StringBuffer(600);
-        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        catalogXml.append("<Notify>\r\n");
-        catalogXml.append("<CmdType>Catalog</CmdType>\r\n");
-        catalogXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        catalogXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
-        catalogXml.append("<SumNum>1</SumNum>\r\n");
-        catalogXml.append("<DeviceList Num=\" " + channels.size() + " \">\r\n");
+        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Notify>\r\n")
+                .append("<CmdType>Catalog</CmdType>\r\n")
+                .append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n")
+                .append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n")
+                .append("<SumNum>1</SumNum>\r\n")
+                .append("<DeviceList Num=\" " + channels.size() + " \">\r\n");
         if (channels.size() > 0) {
             for (DeviceChannel channel : channels) {
                 if (parentPlatform.getServerGBId().equals(channel.getParentId())) {
                     channel.setParentId(parentPlatform.getDeviceGBId());
                 }
-                catalogXml.append("<Item>\r\n");
-                catalogXml.append("<DeviceID>" + channel.getChannelId() + "</DeviceID>\r\n");
-                catalogXml.append("<Event>" + type + "</Event>\r\n");
-                catalogXml.append("</Item>\r\n");
+                catalogXml.append("<Item>\r\n")
+                        .append("<DeviceID>" + channel.getChannelId() + "</DeviceID>\r\n")
+                        .append("<Event>" + type + "</Event>\r\n")
+                        .append("</Item>\r\n");
             }
         }
-        catalogXml.append("</DeviceList>\r\n");
-        catalogXml.append("</Notify>\r\n");
+        catalogXml.append("</DeviceList>\r\n")
+                .append("</Notify>\r\n");
         return catalogXml.toString();
     }
     @Override
@@ -595,12 +615,12 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         }
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer recordXml = new StringBuffer(600);
-        recordXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        recordXml.append("<Response>\r\n");
-        recordXml.append("<CmdType>RecordInfo</CmdType>\r\n");
-        recordXml.append("<SN>" +recordInfo.getSn() + "</SN>\r\n");
-        recordXml.append("<DeviceID>" + recordInfo.getDeviceId() + "</DeviceID>\r\n");
-        recordXml.append("<SumNum>" + recordInfo.getSumNum() + "</SumNum>\r\n");
+        recordXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Response>\r\n")
+                .append("<CmdType>RecordInfo</CmdType>\r\n")
+                .append("<SN>" +recordInfo.getSn() + "</SN>\r\n")
+                .append("<DeviceID>" + recordInfo.getDeviceId() + "</DeviceID>\r\n")
+                .append("<SumNum>" + recordInfo.getSumNum() + "</SumNum>\r\n");
         if (recordInfo.getRecordList() == null ) {
             recordXml.append("<RecordList Num=\"0\">\r\n");
         }else {
@@ -609,12 +629,12 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                 for (RecordItem recordItem : recordInfo.getRecordList()) {
                     recordXml.append("<Item>\r\n");
                     if (deviceChannel != null) {
-                        recordXml.append("<DeviceID>" + recordItem.getDeviceId() + "</DeviceID>\r\n");
-                        recordXml.append("<Name>" + recordItem.getName() + "</Name>\r\n");
-                        recordXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getStartTime()) + "</StartTime>\r\n");
-                        recordXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getEndTime()) + "</EndTime>\r\n");
-                        recordXml.append("<Secrecy>" + recordItem.getSecrecy() + "</Secrecy>\r\n");
-                        recordXml.append("<Type>" + recordItem.getType() + "</Type>\r\n");
+                        recordXml.append("<DeviceID>" + recordItem.getDeviceId() + "</DeviceID>\r\n")
+                                .append("<Name>" + recordItem.getName() + "</Name>\r\n")
+                                .append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getStartTime()) + "</StartTime>\r\n")
+                                .append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getEndTime()) + "</EndTime>\r\n")
+                                .append("<Secrecy>" + recordItem.getSecrecy() + "</Secrecy>\r\n")
+                                .append("<Type>" + recordItem.getType() + "</Type>\r\n");
                         if (!ObjectUtils.isEmpty(recordItem.getFileSize())) {
                             recordXml.append("<FileSize>" + recordItem.getFileSize() + "</FileSize>\r\n");
                         }
@@ -627,38 +647,38 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
             }
         }
 
-        recordXml.append("</RecordList>\r\n");
-        recordXml.append("</Response>\r\n");
+        recordXml.append("</RecordList>\r\n")
+                .append("</Response>\r\n");
 
         // callid
-        CallIdHeader callIdHeader = parentPlatform.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                : udpSipProvider.getNewCallId();
+        CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
+
         Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, recordXml.toString(), fromTag, SipUtils.getNewViaTag(), callIdHeader);
-        transmitRequest(parentPlatform, request);
+        sipSender.transmitRequest(parentPlatform.getDeviceIp(), request);
 
     }
 
     @Override
-    public void sendMediaStatusNotify(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException {
-        if (sendRtpItem == null || platform == null) {
+    public void sendMediaStatusNotify(ParentPlatform parentPlatform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException {
+        if (sendRtpItem == null || parentPlatform == null) {
             return;
         }
 
 
-        String characterSet = platform.getCharacterSet();
+        String characterSet = parentPlatform.getCharacterSet();
         StringBuffer mediaStatusXml = new StringBuffer(200);
-        mediaStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        mediaStatusXml.append("<Notify>\r\n");
-        mediaStatusXml.append("<CmdType>MediaStatus</CmdType>\r\n");
-        mediaStatusXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-        mediaStatusXml.append("<DeviceID>" + sendRtpItem.getChannelId() + "</DeviceID>\r\n");
-        mediaStatusXml.append("<NotifyType>121</NotifyType>\r\n");
-        mediaStatusXml.append("</Notify>\r\n");
-
-        SIPRequest messageRequest = (SIPRequest)headerProviderPlatformProvider.createMessageRequest(platform, mediaStatusXml.toString(),
+        mediaStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Notify>\r\n")
+                .append("<CmdType>MediaStatus</CmdType>\r\n")
+                .append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n")
+                .append("<DeviceID>" + sendRtpItem.getChannelId() + "</DeviceID>\r\n")
+                .append("<NotifyType>121</NotifyType>\r\n")
+                .append("</Notify>\r\n");
+
+        SIPRequest messageRequest = (SIPRequest)headerProviderPlatformProvider.createMessageRequest(parentPlatform, mediaStatusXml.toString(),
                 sendRtpItem);
 
-        transmitRequest(platform, messageRequest);
+        sipSender.transmitRequest(parentPlatform.getDeviceIp(),messageRequest);
 
     }
 
@@ -674,26 +694,26 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
     }
 
     @Override
-    public void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException {
+    public void streamByeCmd(ParentPlatform parentPlatform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException {
         if (sendRtpItem == null ) {
             logger.info("[向上级发送BYE], sendRtpItem 为NULL");
             return;
         }
-        if (platform == null) {
+        if (parentPlatform == null) {
             logger.info("[向上级发送BYE], platform 为NULL");
             return;
         }
-        logger.info("[向上级发送BYE], {}/{}", platform.getServerGBId(), sendRtpItem.getChannelId());
+        logger.info("[向上级发送BYE], {}/{}", parentPlatform.getServerGBId(), sendRtpItem.getChannelId());
         String mediaServerId = sendRtpItem.getMediaServerId();
         MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
         if (mediaServerItem != null) {
             mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
-            zlmrtpServerFactory.closeRTPServer(mediaServerItem, sendRtpItem.getStreamId());
+            zlmrtpServerFactory.closeRtpServer(mediaServerItem, sendRtpItem.getStreamId());
         }
-        SIPRequest byeRequest = headerProviderPlatformProvider.createByeRequest(platform, sendRtpItem);
+        SIPRequest byeRequest = headerProviderPlatformProvider.createByeRequest(parentPlatform, sendRtpItem);
         if (byeRequest == null) {
             logger.warn("[向上级发送bye]:无法创建 byeRequest");
         }
-        transmitRequest(platform,byeRequest);
+        sipSender.transmitRequest(parentPlatform.getDeviceIp(),byeRequest);
     }
 }

+ 32 - 98
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java

@@ -1,13 +1,10 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request;
 
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
+import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
-import gov.nist.javax.sip.SipProviderImpl;
-import gov.nist.javax.sip.SipStackImpl;
 import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.message.SIPResponse;
-import gov.nist.javax.sip.stack.SIPServerTransaction;
-import gov.nist.javax.sip.stack.SIPServerTransactionImpl;
 import org.apache.commons.lang3.ArrayUtils;
 import org.dom4j.Document;
 import org.dom4j.DocumentException;
@@ -16,19 +13,17 @@ import org.dom4j.io.SAXReader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
 
 import javax.sip.*;
 import javax.sip.address.Address;
-import javax.sip.address.AddressFactory;
 import javax.sip.address.SipURI;
-import javax.sip.header.*;
+import javax.sip.header.ContentTypeHeader;
+import javax.sip.header.ExpiresHeader;
+import javax.sip.header.HeaderFactory;
 import javax.sip.message.MessageFactory;
 import javax.sip.message.Request;
 import javax.sip.message.Response;
 import java.io.ByteArrayInputStream;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -44,69 +39,13 @@ public abstract class SIPRequestProcessorParent {
 	private final static Logger logger = LoggerFactory.getLogger(SIPRequestProcessorParent.class);
 
 	@Autowired
-	@Qualifier(value="tcpSipProvider")
-	private SipProviderImpl tcpSipProvider;
-
-	@Autowired
-	@Qualifier(value="udpSipProvider")
-	private SipProviderImpl udpSipProvider;
-
-	/**
-	 * 根据 RequestEvent 获取 ServerTransaction
-	 * @param evt
-	 * @return
-	 */
-	public ServerTransaction getServerTransaction(RequestEvent evt) {
-		Request request = evt.getRequest();
-		SIPServerTransactionImpl serverTransaction = (SIPServerTransactionImpl)evt.getServerTransaction();
-		// 判断TCP还是UDP
-		boolean isTcp = false;
-		ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
-		String transport = reqViaHeader.getTransport();
-		if (transport.equalsIgnoreCase("TCP")) {
-			isTcp = true;
-		}
-		if (serverTransaction != null && serverTransaction.getOriginalRequest() == null) {
-			serverTransaction.setOriginalRequest((SIPRequest) evt.getRequest());
-		}
-		if (serverTransaction == null) {
-			try {
-				if (isTcp) {
-					SipStackImpl stack = (SipStackImpl)tcpSipProvider.getSipStack();
-					serverTransaction = (SIPServerTransactionImpl) stack.findTransaction((SIPRequest)request, true);
-					if (serverTransaction == null) {
-						serverTransaction = (SIPServerTransactionImpl)tcpSipProvider.getNewServerTransaction(request);
-					}
-				} else {
-					SipStackImpl stack = (SipStackImpl)udpSipProvider.getSipStack();
-					serverTransaction = (SIPServerTransactionImpl) stack.findTransaction((SIPRequest)request, true);
-					if (serverTransaction == null) {
-						serverTransaction = (SIPServerTransactionImpl)udpSipProvider.getNewServerTransaction(request);
-					}
-				}
-			} catch (TransactionAlreadyExistsException e) {
-				logger.error(e.getMessage());
-			} catch (TransactionUnavailableException e) {
-				logger.error(e.getMessage());
-			}
-		}
-		return serverTransaction;
-	}
-	
-	public AddressFactory getAddressFactory() {
-		try {
-			return SipFactory.getInstance().createAddressFactory();
-		} catch (PeerUnavailableException e) {
-			e.printStackTrace();
-		}
-		return null;
-	}
+	private SIPSender sipSender;
 
 	public HeaderFactory getHeaderFactory() {
 		try {
 			return SipFactory.getInstance().createHeaderFactory();
 		} catch (PeerUnavailableException e) {
-			e.printStackTrace();
+			logger.error("未处理的异常 ", e);
 		}
 		return null;
 	}
@@ -115,7 +54,7 @@ public abstract class SIPRequestProcessorParent {
 		try {
 			return SipFactory.getInstance().createMessageFactory();
 		} catch (PeerUnavailableException e) {
-			e.printStackTrace();
+			logger.error("未处理的异常 ", e);
 		}
 		return null;
 	}
@@ -134,31 +73,28 @@ public abstract class SIPRequestProcessorParent {
 	 * 400
 	 * 404
 	 */
-	public SIPResponse responseAck(ServerTransaction serverTransaction, int statusCode) throws SipException, InvalidArgumentException, ParseException {
-		logger.debug("response Ack 重构");
-		return responseAck(serverTransaction, statusCode, null);
+	public SIPResponse responseAck(SIPRequest sipRequest, int statusCode) throws SipException, InvalidArgumentException, ParseException {
+		return responseAck(sipRequest, statusCode, null);
 	}
 
-	public SIPResponse responseAck(ServerTransaction serverTransaction, int statusCode, String msg) throws SipException, InvalidArgumentException, ParseException {
-		logger.debug("response Ack 多态");
-		return responseAck(serverTransaction, statusCode, msg, null);
+	public SIPResponse responseAck(SIPRequest sipRequest, int statusCode, String msg) throws SipException, InvalidArgumentException, ParseException {
+		return responseAck(sipRequest, statusCode, msg, null);
 	}
 
-	public SIPResponse responseAck(ServerTransaction serverTransaction, int statusCode, String msg, ResponseAckExtraParam responseAckExtraParam) throws SipException, InvalidArgumentException, ParseException {
-		logger.info("response Ack 1");
-		ToHeader toHeader = (ToHeader) serverTransaction.getRequest().getHeader(ToHeader.NAME);
-		if (toHeader.getTag() == null) {
-			toHeader.setTag(SipUtils.getNewTag());
+	public SIPResponse responseAck(SIPRequest sipRequest, int statusCode, String msg, ResponseAckExtraParam responseAckExtraParam) throws SipException, InvalidArgumentException, ParseException {
+		if (sipRequest.getToHeader().getTag() == null) {
+			sipRequest.getToHeader().setTag(SipUtils.getNewTag());
 		}
 
 		logger.info("response Ack 2");
-		SIPResponse response = (SIPResponse)getMessageFactory().createResponse(statusCode, serverTransaction.getRequest());
+		SIPResponse response = (SIPResponse)getMessageFactory().createResponse(statusCode, sipRequest);
+		response.setStatusCode(statusCode);
 		if (msg != null) {
 			response.setReasonPhrase(msg);
 		}
 		logger.info("response Ack 3");
 		if (responseAckExtraParam != null) {
-			if (responseAckExtraParam.sipURI != null && serverTransaction.getRequest().getMethod().equals(Request.INVITE)) {
+			if (responseAckExtraParam.sipURI != null && sipRequest.getMethod().equals(Request.INVITE)) {
 				logger.debug("responseSdpAck SipURI: {}:{}", responseAckExtraParam.sipURI.getHost(), responseAckExtraParam.sipURI.getPort());
 				Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(
 						SipFactory.getInstance().createAddressFactory().createSipURI(responseAckExtraParam.sipURI.getUser(),  responseAckExtraParam.sipURI.getHost()+":"+responseAckExtraParam.sipURI.getPort()
@@ -169,7 +105,7 @@ public abstract class SIPRequestProcessorParent {
 				response.setContent(responseAckExtraParam.content, responseAckExtraParam.contentTypeHeader);
 			}
 
-			if (serverTransaction.getRequest().getMethod().equals(Request.SUBSCRIBE)) {
+			if (sipRequest.getMethod().equals(Request.SUBSCRIBE)) {
 				if (responseAckExtraParam.expires == -1) {
 					logger.error("[参数不全] 2xx的SUBSCRIBE回复,必须设置Expires header");
 				}else {
@@ -178,28 +114,26 @@ public abstract class SIPRequestProcessorParent {
 				}
 			}
 		}else {
-			if (serverTransaction.getRequest().getMethod().equals(Request.SUBSCRIBE)) {
+			if (sipRequest.getMethod().equals(Request.SUBSCRIBE)) {
 				logger.error("[参数不全] 2xx的SUBSCRIBE回复,必须设置Expires header");
 			}
 		}
-		serverTransaction.sendResponse(response);
-		if (statusCode >= 200 && !"NOTIFY".equalsIgnoreCase(serverTransaction.getRequest().getMethod())) {
-			if (serverTransaction.getDialog() != null) {
-				serverTransaction.getDialog().delete();
-			}
-		}
+
+		// 发送response
+		sipSender.transmitRequest(sipRequest.getLocalAddress().getHostAddress(), response);
+
 		return response;
 	}
 
 	/**
 	 * 回复带sdp的200
 	 */
-	public SIPResponse responseSdpAck(ServerTransaction serverTransaction, String sdp, ParentPlatform platform) throws SipException, InvalidArgumentException, ParseException {
+	public SIPResponse responseSdpAck(SIPRequest request, String sdp, ParentPlatform platform) throws SipException, InvalidArgumentException, ParseException {
 		logger.info("response sdp Ack 2");
 		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
 
 		// 兼容国标中的使用编码@域名作为RequestURI的情况
-		SipURI sipURI = (SipURI)serverTransaction.getRequest().getRequestURI();
+		SipURI sipURI = (SipURI)request.getRequestURI();
 		if (sipURI.getPort() == -1) {
 			sipURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getServerGBId(),  platform.getServerIP()+":"+platform.getServerPort());
 		}
@@ -208,15 +142,15 @@ public abstract class SIPRequestProcessorParent {
 		responseAckExtraParam.content = sdp;
 		responseAckExtraParam.sipURI = sipURI;
 
-		return responseAck(serverTransaction, Response.OK, null, responseAckExtraParam);
+		return responseAck(request, Response.OK, null, responseAckExtraParam);
 	}
 
-	public SIPResponse responseSdpACK(ServerTransaction serverTransaction, String sdp,String gbId,String addr,int port) throws SipException, InvalidArgumentException, ParseException {
+	public SIPResponse responseSdpACK(SIPRequest request, String sdp,String gbId,String addr,int port) throws SipException, InvalidArgumentException, ParseException {
 		logger.info("response sdp Ack by broadcast");
 		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
 
 		// 兼容国标中的使用编码@域名作为RequestURI的情况
-		SipURI sipURI = (SipURI)serverTransaction.getRequest().getRequestURI();
+		SipURI sipURI = (SipURI)request.getRequestURI();
 		if (sipURI.getPort() == -1) {
 			// 此段为回复id
 			sipURI = SipFactory.getInstance().createAddressFactory().createSipURI(gbId,  addr+":"+port);
@@ -226,17 +160,17 @@ public abstract class SIPRequestProcessorParent {
 		responseAckExtraParam.content = sdp;
 		responseAckExtraParam.sipURI = sipURI;
 
-		return responseAck(serverTransaction, Response.OK, null, responseAckExtraParam);
+		return responseAck(request, Response.OK, null, responseAckExtraParam);
 	}
 
 	/**
 	 * 回复带xml的200
 	 */
-	public SIPResponse responseXmlAck(ServerTransaction serverTransaction, String xml, ParentPlatform platform, Integer expires) throws SipException, InvalidArgumentException, ParseException {
+	public SIPResponse responseXmlAck(SIPRequest request, String xml, ParentPlatform platform, Integer expires) throws SipException, InvalidArgumentException, ParseException {
 		logger.info("response xml Ack 1");
 		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
 
-		SipURI sipURI = (SipURI)serverTransaction.getRequest().getRequestURI();
+		SipURI sipURI = (SipURI)request.getRequestURI();
 		if (sipURI.getPort() == -1) {
 			sipURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getServerGBId(),  platform.getServerIP()+":"+platform.getServerPort());
 		}
@@ -245,7 +179,7 @@ public abstract class SIPRequestProcessorParent {
 		responseAckExtraParam.content = xml;
 		responseAckExtraParam.sipURI = sipURI;
 		responseAckExtraParam.expires = expires;
-		return responseAck(serverTransaction, Response.OK, null, responseAckExtraParam);
+		return responseAck(request, Response.OK, null, responseAckExtraParam);
 	}
 
 	public Element getRootElement(RequestEvent evt) throws DocumentException {

+ 23 - 8
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
@@ -9,8 +10,8 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
-import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
+import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg;
@@ -32,7 +33,8 @@ import javax.sip.header.FromHeader;
 import javax.sip.header.HeaderAddress;
 import javax.sip.header.ToHeader;
 import java.text.ParseException;
-import java.util.*;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * SIP命令类型: ACK请求
@@ -61,6 +63,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
 	@Autowired
 	private ZLMRTPServerFactory zlmrtpServerFactory;
 
+	@Autowired
+	private ZlmHttpHookSubscribe hookSubscribe;
+
 	@Autowired
 	private IMediaServerService mediaServerService;
 
@@ -115,9 +120,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
 		param.put("pt", sendRtpItem.getPt());
 		param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
 		param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
-		if (!sendRtpItem.isTcp() && parentPlatform.isRtcp()) {
+		if (!sendRtpItem.isTcp()) {
 			// 开启rtcp保活
-			param.put("udp_rtcp_timeout", "1");
+			param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0");
 		}
 
 		if (mediaInfo == null) {
@@ -129,8 +134,18 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
 				startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader);
 			});
 		}else {
-			JSONObject jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
-			startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader);
+			// 如果是非严格模式,需要关闭端口占用
+			JSONObject startSendRtpStreamResult = null;
+			if (sendRtpItem.getLocalPort() != 0) {
+				if (zlmrtpServerFactory.releasePort(mediaInfo, sendRtpItem.getSsrc())) {
+					startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
+				}
+			}else {
+				startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
+			}
+			if (startSendRtpStreamResult != null) {
+				startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, startSendRtpStreamResult, param, callIdHeader);
+			}
 		}
 	}
 	private void startSendRtpStreamHand(RequestEvent evt, SendRtpItem sendRtpItem, ParentPlatform parentPlatform,
@@ -141,7 +156,7 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
 			logger.info("调用ZLM推流接口, 结果: {}",  jsonObject);
 			logger.info("RTP推流成功[ {}/{} ],{}->{}:{}, " ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"), param.get("dst_url"), param.get("dst_port"));
 		} else {
-			logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"),JSONObject.toJSON(param));
+			logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"), JSON.toJSONString(param));
 			if (sendRtpItem.isOnlyAudio()) {
 				// TODO 可能是语音对讲
 			}else {

+ 3 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java

@@ -18,6 +18,7 @@ import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.InitializingBean;
@@ -82,7 +83,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 	public void process(RequestEvent evt) {
 
 		try {
-			responseAck(getServerTransaction(evt), Response.OK);
+			responseAck((SIPRequest) evt.getRequest(), Response.OK);
 		} catch (SipException | InvalidArgumentException | ParseException e) {
 			logger.error("[回复BYE信息失败],{}", e.getMessage());
 		}
@@ -106,7 +107,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 				if (totalReaderCount <= 0) {
 					logger.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId);
 					if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) {
-						Device device = deviceService.queryDevice(sendRtpItem.getDeviceId());
+						Device device = deviceService.getDevice(sendRtpItem.getDeviceId());
 						if (device == null) {
 							logger.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId);
 						}

+ 294 - 202
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java

@@ -1,22 +1,23 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.GBEventSubscribe;
 import com.genersoft.iot.vmp.gb28181.GB_Event;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
+import com.genersoft.iot.vmp.gb28181.session.SsrcConfig;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
-import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
+import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
-import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
 import com.genersoft.iot.vmp.media.zlm.ZLMMediaListManager;
 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
+import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
 import com.genersoft.iot.vmp.media.zlm.dto.*;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.IPlayService;
@@ -40,12 +41,14 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import javax.sdp.*;
-import javax.sip.*;
+import javax.sip.InvalidArgumentException;
+import javax.sip.RequestEvent;
+import javax.sip.SipException;
 import javax.sip.header.CallIdHeader;
-import javax.sip.message.Request;
 import javax.sip.message.Response;
 import java.text.ParseException;
 import java.time.Instant;
+import java.util.Random;
 import java.util.Vector;
 
 /**
@@ -85,7 +88,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
     private IPlayService playService;
 
     @Autowired
-    private ISIPCommander commander;
+    private SIPSender sipSender;
 
     @Autowired
     private ZLMRTPServerFactory zlmrtpServerFactory;
@@ -128,15 +131,18 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
     public void process(RequestEvent evt) {
         //  Invite Request消息实现,此消息一般为级联消息,上级给下级发送请求视频指令
         try {
-            Request request = evt.getRequest();
+            SIPRequest request = (SIPRequest)evt.getRequest();
             String channelId = SipUtils.getChannelIdFromRequest(request);
             String requesterId = SipUtils.getUserIdFromFromHeader(request);
             CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
-            ServerTransaction serverTransaction = getServerTransaction(evt);
             if (requesterId == null || channelId == null) {
                 logger.info("无法从FromHeader的Address中获取到平台id,返回400");
                 // 参数不全, 发400,请求错误
-                responseAck(serverTransaction, Response.BAD_REQUEST);
+                try {
+                    responseAck(request, Response.BAD_REQUEST);
+                } catch (SipException | InvalidArgumentException | ParseException e) {
+                    logger.error("[命令发送失败] invite BAD_REQUEST: {}", e.getMessage());
+                }
                 return;
             }
 
@@ -144,7 +150,8 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
             // 查询请求是否来自上级平台\设备
             ParentPlatform platform = storager.queryParentPlatByServerGBId(requesterId);
             if (platform == null) {
-                inviteFromDeviceHandle(serverTransaction, requesterId);
+                inviteFromDeviceHandle(request, requesterId);
+
             } else {
                 // 查询平台下是否有该通道
                 DeviceChannel channel = storager.queryChannelInParentPlatform(requesterId, channelId);
@@ -162,7 +169,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 //                        return;
 //                    }
                     // 通道存在,发100,TRYING
-                    responseAck(serverTransaction, Response.TRYING);
+                    try {
+                        responseAck(request, Response.TRYING);
+                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                        logger.error("[命令发送失败] invite TRYING: {}", e.getMessage());
+                    }
                 } else if (channel == null && gbStream != null) {
 
                     String mediaServerId = gbStream.getMediaServerId();
@@ -170,13 +181,21 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                     if (mediaServerItem == null) {
                         if ("proxy".equals(gbStream.getStreamType())) {
                             logger.info("[ app={}, stream={} ]找不到zlm {},返回410", gbStream.getApp(), gbStream.getStream(), mediaServerId);
-                            responseAck(serverTransaction, Response.GONE);
+                            try {
+                                responseAck(request, Response.GONE);
+                            } catch (SipException | InvalidArgumentException | ParseException e) {
+                                logger.error("[命令发送失败] invite GONE: {}", e.getMessage());
+                            }
                             return;
                         } else {
                             streamPushItem = streamPushService.getPush(gbStream.getApp(), gbStream.getStream());
                             if (streamPushItem == null || streamPushItem.getServerId().equals(userSetting.getServerId())) {
                                 logger.info("[ app={}, stream={} ]找不到zlm {},返回410", gbStream.getApp(), gbStream.getStream(), mediaServerId);
-                                responseAck(serverTransaction, Response.GONE);
+                                try {
+                                    responseAck(request, Response.GONE);
+                                } catch (SipException | InvalidArgumentException | ParseException e) {
+                                    logger.error("[命令发送失败] invite GONE: {}", e.getMessage());
+                                }
                                 return;
                             }
                         }
@@ -185,25 +204,47 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                             streamPushItem = streamPushService.getPush(gbStream.getApp(), gbStream.getStream());
                             if (streamPushItem == null) {
                                 logger.info("[ app={}, stream={} ]找不到zlm {},返回410", gbStream.getApp(), gbStream.getStream(), mediaServerId);
-                                responseAck(serverTransaction, Response.GONE);
+                                try {
+                                    responseAck(request, Response.GONE);
+                                } catch (SipException | InvalidArgumentException | ParseException e) {
+                                    logger.error("[命令发送失败] invite GONE: {}", e.getMessage());
+                                }
                                 return;
                             }
                         }else if("proxy".equals(gbStream.getStreamType())){
                             proxyByAppAndStream = streamProxyService.getStreamProxyByAppAndStream(gbStream.getApp(), gbStream.getStream());
                             if (proxyByAppAndStream == null) {
                                 logger.info("[ app={}, stream={} ]找不到zlm {},返回410", gbStream.getApp(), gbStream.getStream(), mediaServerId);
-                                responseAck(serverTransaction, Response.GONE);
+                                try {
+                                    responseAck(request, Response.GONE);
+                                } catch (SipException | InvalidArgumentException | ParseException e) {
+                                    logger.error("[命令发送失败] invite GONE: {}", e.getMessage());
+                                }
                                 return;
                             }
                         }
                     }
-                    responseAck(serverTransaction, Response.CALL_IS_BEING_FORWARDED); // 通道存在,发181,呼叫转接中
+                    try {
+                        responseAck(request, Response.CALL_IS_BEING_FORWARDED);
+                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                        logger.error("[命令发送失败] invite CALL_IS_BEING_FORWARDED: {}", e.getMessage());
+                    }
                 } else if (catalog != null) {
-                    responseAck(serverTransaction, Response.BAD_REQUEST, "catalog channel can not play"); // 目录不支持点播
+                    try {
+                        // 目录不支持点播
+                        responseAck(request, Response.BAD_REQUEST, "catalog channel can not play");
+                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                        logger.error("[命令发送失败] invite 目录不支持点播: {}", e.getMessage());
+                    }
                     return;
                 } else {
-                    logger.info("通道不存在,返回404");
-                    responseAck(serverTransaction, Response.NOT_FOUND); // 通道不存在,发404,资源不存在
+                    logger.info("通道不存在,返回404: {}", channelId);
+                    try {
+                        // 通道不存在,发404,资源不存在
+                        responseAck(request, Response.NOT_FOUND);
+                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                        logger.error("[命令发送失败] invite 通道不存在: {}", e.getMessage());
+                    }
                     return;
                 }
                 // 解析sdp消息, 使用jainsip 自带的sdp解析方式
@@ -274,11 +315,16 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                 if (port == -1) {
                     logger.info("不支持的媒体格式,返回415");
                     // 回复不支持的格式
-                    responseAck(serverTransaction, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415
+                    try {
+                        // 不支持的格式,发415
+                        responseAck(request, Response.UNSUPPORTED_MEDIA_TYPE);
+                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                        logger.error("[命令发送失败] invite 不支持的格式: {}", e.getMessage());
+                    }
                     return;
                 }
                 String username = sdp.getOrigin().getUsername();
-                String addressStr = sdp.getOrigin().getAddress();
+                String addressStr = sdp.getConnection().getAddress();
 
                 logger.info("[上级点播]用户:{}, 通道:{}, 地址:{}:{}, ssrc:{}", username, channelId, addressStr, port, ssrc);
                 Device device = null;
@@ -287,25 +333,36 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                     device = storager.queryVideoDeviceByPlatformIdAndChannelId(requesterId, channelId);
                     if (device == null) {
                         logger.warn("点播平台{}的通道{}时未找到设备信息", requesterId, channel);
-                        responseAck(serverTransaction, Response.SERVER_INTERNAL_ERROR);
+                        try {
+                            responseAck(request, Response.SERVER_INTERNAL_ERROR);
+                        } catch (SipException | InvalidArgumentException | ParseException e) {
+                            logger.error("[命令发送失败] invite 未找到设备信息: {}", e.getMessage());
+                        }
                         return;
                     }
                     mediaServerItem = playService.getNewMediaServerItem(device);
                     if (mediaServerItem == null) {
                         logger.warn("未找到可用的zlm");
-                        responseAck(serverTransaction, Response.BUSY_HERE);
+                        try {
+                            responseAck(request, Response.BUSY_HERE);
+                        } catch (SipException | InvalidArgumentException | ParseException e) {
+                            logger.error("[命令发送失败] invite BUSY_HERE: {}", e.getMessage());
+                        }
                         return;
                     }
                     SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
-                            device.getDeviceId(), channelId,
-                            mediaTransmissionTCP);
+                            device.getDeviceId(), channelId, mediaTransmissionTCP, platform.isRtcp());
 
                     if (tcpActive != null) {
                         sendRtpItem.setTcpActive(tcpActive);
                     }
                     if (sendRtpItem == null) {
                         logger.warn("服务器端口资源不足");
-                        responseAck(serverTransaction, Response.BUSY_HERE);
+                        try {
+                            responseAck(request, Response.BUSY_HERE);
+                        } catch (SipException | InvalidArgumentException | ParseException e) {
+                            logger.error("[命令发送失败] invite 服务器端口资源不足: {}", e.getMessage());
+                        }
                         return;
                     }
                     sendRtpItem.setCallId(callIdHeader.getCallId());
@@ -333,7 +390,12 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                         } else {
                             content.append("t=0 0\r\n");
                         }
-                        content.append("m=video " + sendRtpItem.getLocalPort() + " RTP/AVP 96\r\n");
+                        int localPort = sendRtpItem.getLocalPort();
+                        if (localPort == 0) {
+                            // 非严格模式端口不统一, 增加兼容性,修改为一个不为0的端口
+                            localPort = new Random().nextInt(65535) + 1;
+                        }
+                        content.append("m=video " + localPort + " RTP/AVP 96\r\n");
                         content.append("a=sendonly\r\n");
                         content.append("a=rtpmap:96 PS/90000\r\n");
                         content.append("y=" + sendRtpItem.getSsrc() + "\r\n");
@@ -351,34 +413,25 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                                     logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
                                 }
                             }, 60 * 1000);
-                            responseSdpAck(serverTransaction, content.toString(), platform);
+                            responseSdpAck(request, content.toString(), platform);
 
-                        } catch (SipException e) {
-                            e.printStackTrace();
-                        } catch (InvalidArgumentException e) {
-                            e.printStackTrace();
-                        } catch (ParseException e) {
-                            e.printStackTrace();
+                        } catch (SipException | InvalidArgumentException | ParseException e) {
+                            logger.error("[命令发送失败] 国标级联 回复SdpAck", e);
                         }
                     };
                     SipSubscribe.Event errorEvent = ((event) -> {
                         // 未知错误。直接转发设备点播的错误
-                        Response response = null;
                         try {
-                            response = getMessageFactory().createResponse(event.statusCode, evt.getRequest());
-                            serverTransaction.sendResponse(response);
-                            System.out.println("未知错误。直接转发设备点播的错误");
-                            if (serverTransaction.getDialog() != null) {
-                                serverTransaction.getDialog().delete();
-                            }
-                        } catch (ParseException | SipException | InvalidArgumentException e) {
-                            e.printStackTrace();
+                            Response response = getMessageFactory().createResponse(event.statusCode, evt.getRequest());
+                            sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
+                        } catch (ParseException | SipException  e) {
+                            logger.error("未处理的异常 ", e);
                         }
                     });
                     sendRtpItem.setApp("rtp");
                     if ("Playback".equalsIgnoreCase(sessionName)) {
                         sendRtpItem.setPlayType(InviteStreamType.PLAYBACK);
-                        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, true, true);
+                        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, device.isSsrcCheck(), true);
                         sendRtpItem.setStreamId(ssrcInfo.getStream());
                         // 写入redis, 超时时回复
                         redisCatchStorage.updateSendRTPSever(sendRtpItem);
@@ -391,13 +444,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                                         }
                                         redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null);
                                         try {
-                                            responseAck(serverTransaction, Response.REQUEST_TIMEOUT);
-                                        } catch (SipException e) {
-                                            e.printStackTrace();
-                                        } catch (InvalidArgumentException e) {
-                                            e.printStackTrace();
-                                        } catch (ParseException e) {
-                                            e.printStackTrace();
+                                            responseAck(request, Response.REQUEST_TIMEOUT);
+                                        } catch (SipException | InvalidArgumentException | ParseException e) {
+                                            logger.error("[命令发送失败] 国标级联 录像回放 发送REQUEST_TIMEOUT: {}", e.getMessage());
                                         }
                                     } else {
                                         if (result.getMediaServerItem() != null) {
@@ -433,10 +482,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 
                             // 写入redis, 超时时回复
                             redisCatchStorage.updateSendRTPSever(sendRtpItem);
-                            playService.play(mediaServerItem, ssrcInfo, device, channelId,1, hookEvent, errorEvent, (code, msg) -> {
+                            MediaServerItem finalMediaServerItem = mediaServerItem;
+                            playService.play(mediaServerItem, ssrcInfo, device, channelId, hookEvent, errorEvent, (code, msg) -> {
                                 logger.info("[上级点播]超时, 用户:{}, 通道:{}", username, channelId);
                                 redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null);
-                            }, null);
+                            });
                         } else {
                             sendRtpItem.setStreamId(playTransaction.getStream());
                             // 写入redis, 超时时回复
@@ -448,65 +498,68 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                         }
                     }
                 } else if (gbStream != null) {
+                    if(ssrc.equals(ssrcDefault))
+                    {
+                        SsrcConfig ssrcConfig = mediaServerItem.getSsrcConfig();
+                        if(ssrcConfig != null)
+                        {
+                            ssrc = ssrcConfig.getPlaySsrc();
+                            ssrcConfig.releaseSsrc(ssrc);
+                        }
+                    }
                     if("push".equals(gbStream.getStreamType())) {
                         if (streamPushItem != null && streamPushItem.isPushIng()) {
                             // 推流状态
-                            pushStream(evt, serverTransaction, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
+                            pushStream(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
                                     mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
                         } else {
                             // 未推流 拉起
-                            notifyStreamOnline(evt, serverTransaction,gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
+                            notifyStreamOnline(evt, request,gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
                                     mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
                         }
                     }else if ("proxy".equals(gbStream.getStreamType())){
-                        if(null != proxyByAppAndStream &&proxyByAppAndStream.isStatus()){
-                            pushProxyStream(evt, serverTransaction, gbStream,  platform, callIdHeader, mediaServerItem, port, tcpActive,
-                                    mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
-                        }else{
-                            //开启代理拉流
-                            boolean start1 = streamProxyService.start(gbStream.getApp(), gbStream.getStream());
-                            if(start1) {
-                                pushProxyStream(evt, serverTransaction, gbStream,  platform, callIdHeader, mediaServerItem, port, tcpActive,
+                        if (null != proxyByAppAndStream) {
+                            if(proxyByAppAndStream.isStatus()){
+                                pushProxyStream(evt, request, gbStream,  platform, callIdHeader, mediaServerItem, port, tcpActive,
                                         mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
                             }else{
-                                //失败后通知
-                                notifyStreamOnline(evt, serverTransaction,gbStream, null, platform, callIdHeader, mediaServerItem, port, tcpActive,
+                                //开启代理拉流
+                                notifyStreamOnline(evt, request,gbStream, null, platform, callIdHeader, mediaServerItem, port, tcpActive,
                                         mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
                             }
                         }
 
+
                     }
                 }
             }
-
-        } catch (SipException | InvalidArgumentException | ParseException e) {
-            e.printStackTrace();
-            logger.warn("sdp解析错误");
-            e.printStackTrace();
         } catch (SdpParseException e) {
-            e.printStackTrace();
+            logger.error("sdp解析错误", e);
         } catch (SdpException e) {
-            e.printStackTrace();
+            logger.error("未处理的异常 ", e);
         }
     }
 
     /**
      * 安排推流
      */
-    private void pushProxyStream(RequestEvent evt, ServerTransaction serverTransaction, GbStream gbStream, ParentPlatform platform,
+    private void pushProxyStream(RequestEvent evt, SIPRequest request, GbStream gbStream, ParentPlatform platform,
                             CallIdHeader callIdHeader, MediaServerItem mediaServerItem,
                             int port, Boolean tcpActive, boolean mediaTransmissionTCP,
-                            String channelId, String addressStr, String ssrc, String requesterId) throws InvalidArgumentException, ParseException, SipException {
+                            String channelId, String addressStr, String ssrc, String requesterId) {
             Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream());
             if (streamReady) {
                 // 自平台内容
                 SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
-                        gbStream.getApp(), gbStream.getStream(), channelId,
-                        mediaTransmissionTCP);
+                        gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp());
 
                 if (sendRtpItem == null) {
                     logger.warn("服务器端口资源不足");
-                    responseAck(serverTransaction, Response.BUSY_HERE);
+                    try {
+                        responseAck(request, Response.BUSY_HERE);
+                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                        logger.error("[命令发送失败] invite 服务器端口资源不足: {}", e.getMessage());
+                    }
                     return;
                 }
                 if (tcpActive != null) {
@@ -516,10 +569,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                 // 写入redis, 超时时回复
                 sendRtpItem.setStatus(1);
                 sendRtpItem.setCallId(callIdHeader.getCallId());
-                SIPRequest request = (SIPRequest) evt.getRequest();
                 sendRtpItem.setFromTag(request.getFromTag());
 
-                SIPResponse response = sendStreamAck(mediaServerItem, serverTransaction, sendRtpItem, platform, evt);
+                SIPResponse response = sendStreamAck(mediaServerItem, request, sendRtpItem, platform, evt);
                 if (response != null) {
                     sendRtpItem.setToTag(response.getToTag());
                 }
@@ -528,22 +580,25 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
         }
 
     }
-    private void pushStream(RequestEvent evt, ServerTransaction serverTransaction, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform,
+    private void pushStream(RequestEvent evt, SIPRequest request, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform,
                             CallIdHeader callIdHeader, MediaServerItem mediaServerItem,
                             int port, Boolean tcpActive, boolean mediaTransmissionTCP,
-                            String channelId, String addressStr, String ssrc, String requesterId) throws InvalidArgumentException, ParseException, SipException {
+                            String channelId, String addressStr, String ssrc, String requesterId) {
         // 推流
         if (streamPushItem.isSelf()) {
             Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream());
             if (streamReady) {
                 // 自平台内容
                 SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
-                        gbStream.getApp(), gbStream.getStream(), channelId,
-                        mediaTransmissionTCP);
+                        gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp());
 
                 if (sendRtpItem == null) {
                     logger.warn("服务器端口资源不足");
-                    responseAck(serverTransaction, Response.BUSY_HERE);
+                    try {
+                        responseAck(request, Response.BUSY_HERE);
+                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                        logger.error("[命令发送失败] invite 服务器端口资源不足: {}", e.getMessage());
+                    }
                     return;
                 }
                 if (tcpActive != null) {
@@ -554,9 +609,8 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                 sendRtpItem.setStatus(1);
                 sendRtpItem.setCallId(callIdHeader.getCallId());
 
-                SIPRequest request = (SIPRequest) evt.getRequest();
                 sendRtpItem.setFromTag(request.getFromTag());
-                SIPResponse response = sendStreamAck(mediaServerItem, serverTransaction, sendRtpItem, platform, evt);
+                SIPResponse response = sendStreamAck(mediaServerItem, request, sendRtpItem, platform, evt);
                 if (response != null) {
                     sendRtpItem.setToTag(response.getToTag());
                 }
@@ -565,31 +619,62 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 
             } else {
                 // 不在线 拉起
-                notifyStreamOnline(evt, serverTransaction,gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
+                notifyStreamOnline(evt, request,gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
                         mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
             }
 
         } else {
             // 其他平台内容
-            otherWvpPushStream(evt, serverTransaction, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
+            otherWvpPushStream(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
                     mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
         }
     }
     /**
      * 通知流上线
      */
-    private void notifyStreamOnline(RequestEvent evt, ServerTransaction serverTransaction, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform,
+    private void notifyStreamOnline(RequestEvent evt, SIPRequest request, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform,
                                     CallIdHeader callIdHeader, MediaServerItem mediaServerItem,
                                     int port, Boolean tcpActive, boolean mediaTransmissionTCP,
-                                    String channelId, String addressStr, String ssrc, String requesterId) throws InvalidArgumentException, ParseException, SipException {
+                                    String channelId, String addressStr, String ssrc, String requesterId) {
         if ("proxy".equals(gbStream.getStreamType())) {
             // TODO 控制启用以使设备上线
             logger.info("[ app={}, stream={} ]通道未推流,启用流后开始推流", gbStream.getApp(), gbStream.getStream());
-            responseAck(serverTransaction, Response.BAD_REQUEST, "channel [" + gbStream.getGbId() + "] offline");
+            // 监听流上线
+            HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed(gbStream.getApp(), gbStream.getStream(), true, "rtsp", mediaServerItem.getId());
+            zlmHttpHookSubscribe.addSubscribe(hookSubscribe, (mediaServerItemInUSe, responseJSON) -> {
+                String app = responseJSON.getString("app");
+                String stream = responseJSON.getString("stream");
+                logger.info("[上级点播]拉流代理已经就绪, {}/{}", app, stream);
+                dynamicTask.stop(callIdHeader.getCallId());
+                pushProxyStream(evt, request, gbStream,  platform, callIdHeader, mediaServerItem, port, tcpActive,
+                        mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
+            });
+            dynamicTask.startDelay(callIdHeader.getCallId(), () -> {
+                logger.info("[ app={}, stream={} ] 等待拉流代理流超时", gbStream.getApp(), gbStream.getStream());
+                zlmHttpHookSubscribe.removeSubscribe(hookSubscribe);
+            }, userSetting.getPlatformPlayTimeout());
+            boolean start = streamProxyService.start(gbStream.getApp(), gbStream.getStream());
+            if (!start) {
+                try {
+                    responseAck(request, Response.BUSY_HERE, "channel [" + gbStream.getGbId() + "] offline");
+                } catch (SipException | InvalidArgumentException | ParseException e) {
+                    logger.error("[命令发送失败] invite 通道未推流: {}", e.getMessage());
+                }
+                zlmHttpHookSubscribe.removeSubscribe(hookSubscribe);
+                dynamicTask.stop(callIdHeader.getCallId());
+            }
+
+
+
         } else if ("push".equals(gbStream.getStreamType())) {
             if (!platform.isStartOfflinePush()) {
                 // 平台设置中关闭了拉起离线的推流则直接回复
-                responseAck(serverTransaction, Response.TEMPORARILY_UNAVAILABLE, "channel stream not pushing");
+                try {
+                    logger.info("[上级点播] 失败,推流设备未推流,channel: {}, app: {}, stream: {}", gbStream.getGbId(), gbStream.getApp(), gbStream.getStream());
+                    responseAck(request, Response.TEMPORARILY_UNAVAILABLE, "channel stream not pushing");
+                } catch (SipException | InvalidArgumentException | ParseException e) {
+                    logger.error("[命令发送失败] invite 通道未推流: {}", e.getMessage());
+                }
                 return;
             }
             // 发送redis消息以使设备上线
@@ -604,13 +689,13 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                 logger.info("[ app={}, stream={} ] 等待设备开始推流超时", gbStream.getApp(), gbStream.getStream());
                 try {
                     mediaListManager.removedChannelOnlineEventLister(gbStream.getApp(), gbStream.getStream());
-                    responseAck(serverTransaction, Response.REQUEST_TIMEOUT); // 超时
+                    responseAck(request, Response.REQUEST_TIMEOUT); // 超时
                 } catch (SipException e) {
-                    e.printStackTrace();
+                    logger.error("未处理的异常 ", e);
                 } catch (InvalidArgumentException e) {
-                    e.printStackTrace();
+                    logger.error("未处理的异常 ", e);
                 } catch (ParseException e) {
-                    e.printStackTrace();
+                    logger.error("未处理的异常 ", e);
                 }
             }, userSetting.getPlatformPlayTimeout());
             // 添加监听
@@ -622,18 +707,18 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                 dynamicTask.stop(callIdHeader.getCallId());
                 if (serverId.equals(userSetting.getServerId())) {
                     SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, finalPort, ssrc, requesterId,
-                            app, stream, channelId, mediaTransmissionTCP);
+                            app, stream, channelId, mediaTransmissionTCP, platform.isRtcp());
 
                     if (sendRtpItem == null) {
                         logger.warn("上级点时创建sendRTPItem失败,可能是服务器端口资源不足");
                         try {
-                            responseAck(serverTransaction, Response.BUSY_HERE);
+                            responseAck(request, Response.BUSY_HERE);
                         } catch (SipException e) {
-                            e.printStackTrace();
+                            logger.error("未处理的异常 ", e);
                         } catch (InvalidArgumentException e) {
-                            e.printStackTrace();
+                            logger.error("未处理的异常 ", e);
                         } catch (ParseException e) {
-                            e.printStackTrace();
+                            logger.error("未处理的异常 ", e);
                         }
                         return;
                     }
@@ -645,16 +730,15 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                     sendRtpItem.setStatus(1);
                     sendRtpItem.setCallId(callIdHeader.getCallId());
 
-                    SIPRequest request = (SIPRequest) evt.getRequest();
                     sendRtpItem.setFromTag(request.getFromTag());
-                    SIPResponse response = sendStreamAck(mediaServerItem, serverTransaction, sendRtpItem, platform, evt);
+                    SIPResponse response = sendStreamAck(mediaServerItem, request, sendRtpItem, platform, evt);
                     if (response != null) {
                         sendRtpItem.setToTag(response.getToTag());
                     }
                     redisCatchStorage.updateSendRTPSever(sendRtpItem);
                 } else {
                     // 其他平台内容
-                    otherWvpPushStream(evt, serverTransaction, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
+                    otherWvpPushStream(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
                             mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
                 }
             });
@@ -665,7 +749,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                     dynamicTask.stop(callIdHeader.getCallId());
                     mediaListManager.removedChannelOnlineEventLister(gbStream.getApp(), gbStream.getStream());
                     try {
-                        responseAck(serverTransaction, Response.TEMPORARILY_UNAVAILABLE, response.getMsg());
+                        responseAck(request, Response.TEMPORARILY_UNAVAILABLE, response.getMsg());
                     } catch (SipException | InvalidArgumentException | ParseException e) {
                         logger.error("[命令发送失败] 国标级联 点播回复: {}", e.getMessage());
                     }
@@ -677,7 +761,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
     /**
      * 来自其他wvp的推流
      */
-    private void otherWvpPushStream(RequestEvent evt, ServerTransaction serverTransaction, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform,
+    private void otherWvpPushStream(RequestEvent evt, SIPRequest request, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform,
                                     CallIdHeader callIdHeader, MediaServerItem mediaServerItem,
                                     int port, Boolean tcpActive, boolean mediaTransmissionTCP,
                                     String channelId, String addressStr, String ssrc, String requesterId) {
@@ -685,18 +769,18 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
         // 发送redis消息
         redisGbPlayMsgListener.sendMsg(streamPushItem.getServerId(), streamPushItem.getMediaServerId(),
                 streamPushItem.getApp(), streamPushItem.getStream(), addressStr, port, ssrc, requesterId,
-                channelId, mediaTransmissionTCP, null, responseSendItemMsg -> {
+                channelId, mediaTransmissionTCP, platform.isRtcp(),null, responseSendItemMsg -> {
                     SendRtpItem sendRtpItem = responseSendItemMsg.getSendRtpItem();
                     if (sendRtpItem == null || responseSendItemMsg.getMediaServerItem() == null) {
                         logger.warn("服务器端口资源不足");
                         try {
-                            responseAck(serverTransaction, Response.BUSY_HERE);
+                            responseAck(request, Response.BUSY_HERE);
                         } catch (SipException e) {
-                            e.printStackTrace();
+                            logger.error("未处理的异常 ", e);
                         } catch (InvalidArgumentException e) {
-                            e.printStackTrace();
+                            logger.error("未处理的异常 ", e);
                         } catch (ParseException e) {
-                            e.printStackTrace();
+                            logger.error("未处理的异常 ", e);
                         }
                         return;
                     }
@@ -709,50 +793,39 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                     sendRtpItem.setStatus(1);
                     sendRtpItem.setCallId(callIdHeader.getCallId());
 
-                    SIPRequest request = (SIPRequest) evt.getRequest();
                     sendRtpItem.setFromTag(request.getFromTag());
-                    SIPResponse response = sendStreamAck(responseSendItemMsg.getMediaServerItem(), serverTransaction,sendRtpItem, platform, evt);
+                    SIPResponse response = sendStreamAck(responseSendItemMsg.getMediaServerItem(), request,sendRtpItem, platform, evt);
                     if (response != null) {
                         sendRtpItem.setToTag(response.getToTag());
                     }
                     redisCatchStorage.updateSendRTPSever(sendRtpItem);
                 }, (wvpResult) -> {
-                    try {
-                        // 错误
-                        if (wvpResult.getCode() == RedisGbPlayMsgListener.ERROR_CODE_OFFLINE) {
-                            // 离线
-                            // 查询是否在本机上线了
-                            StreamPushItem currentStreamPushItem = streamPushService.getPush(streamPushItem.getApp(), streamPushItem.getStream());
-                            if (currentStreamPushItem.isPushIng()) {
-                                // 在线状态
-                                pushStream(evt, serverTransaction, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
-                                        mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
 
-                            } else {
-                                // 不在线 拉起
-                                notifyStreamOnline(evt, serverTransaction, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
-                                        mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
-                            }
+                    // 错误
+                    if (wvpResult.getCode() == RedisGbPlayMsgListener.ERROR_CODE_OFFLINE) {
+                        // 离线
+                        // 查询是否在本机上线了
+                        StreamPushItem currentStreamPushItem = streamPushService.getPush(streamPushItem.getApp(), streamPushItem.getStream());
+                        if (currentStreamPushItem.isPushIng()) {
+                            // 在线状态
+                            pushStream(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
+                                    mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
+
+                        } else {
+                            // 不在线 拉起
+                            notifyStreamOnline(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
+                                    mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
                         }
-                    } catch (InvalidArgumentException | ParseException | SipException e) {
-                        logger.error("[命令发送失败] 国标级联 点播回复: {}", e.getMessage());
                     }
-
-
                     try {
-                        responseAck(serverTransaction, Response.BUSY_HERE);
-                    } catch (SipException e) {
-                        e.printStackTrace();
-                    } catch (InvalidArgumentException e) {
-                        e.printStackTrace();
-                    } catch (ParseException e) {
-                        e.printStackTrace();
+                        responseAck(request, Response.BUSY_HERE);
+                    } catch (InvalidArgumentException | ParseException | SipException e) {
+                        logger.error("[命令发送失败] 国标级联 点播回复 BUSY_HERE: {}", e.getMessage());
                     }
-                    return;
                 });
     }
 
-    public SIPResponse sendStreamAck(MediaServerItem mediaServerItem, ServerTransaction serverTransaction, SendRtpItem sendRtpItem, ParentPlatform platform, RequestEvent evt) {
+    public SIPResponse sendStreamAck(MediaServerItem mediaServerItem, SIPRequest request, SendRtpItem sendRtpItem, ParentPlatform platform, RequestEvent evt) {
 
         StringBuffer content = new StringBuffer(200);
         content.append("v=0\r\n");
@@ -760,7 +833,13 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
         content.append("s=Play\r\n");
         content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
         content.append("t=0 0\r\n");
-        content.append("m=video " + sendRtpItem.getLocalPort() + " RTP/AVP 96\r\n");
+        // 非严格模式端口不统一, 增加兼容性,修改为一个不为0的端口
+        int localPort = sendRtpItem.getLocalPort();
+        if(localPort == 0)
+        {
+            localPort = new Random().nextInt(65535) + 1;
+        }
+        content.append("m=video " + localPort + " RTP/AVP 96\r\n");
         content.append("a=sendonly\r\n");
         content.append("a=rtpmap:96 PS/90000\r\n");
         if (sendRtpItem.isTcp()) {
@@ -775,29 +854,29 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
         content.append("f=\r\n");
 
         try {
-            return responseSdpAck(serverTransaction, content.toString(), platform);
+            return responseSdpAck(request, content.toString(), platform);
         } catch (SipException e) {
-            e.printStackTrace();
+            logger.error("未处理的异常 ", e);
         } catch (InvalidArgumentException e) {
-            e.printStackTrace();
+            logger.error("未处理的异常 ", e);
         } catch (ParseException e) {
-            e.printStackTrace();
+            logger.error("未处理的异常 ", e);
         }
         return null;
     }
 
-
-    public void inviteFromDeviceHandle(ServerTransaction serverTransaction, String requesterId) throws InvalidArgumentException, ParseException, SipException, SdpException {
+    public void inviteFromDeviceHandle(SIPRequest request, String requesterId) {
 
         // 非上级平台请求,查询是否设备请求(通常为接收语音广播的设备)
         Device device = redisCatchStorage.getDevice(requesterId);
         if (device != null) {
             logger.info("收到设备" + requesterId + "的语音广播Invite请求");
-
-            responseAck(serverTransaction, Response.TRYING);
-
-            String contentString = new String(serverTransaction.getRequest().getRawContent());
-            logger.info("[received invite] dev:{} --> server:\n{}\n",device.getDeviceId(),contentString);
+            try {
+                responseAck(request, Response.TRYING);
+            } catch (SipException | InvalidArgumentException | ParseException e) {
+                logger.error("[命令发送失败] invite BAD_REQUEST: {}", e.getMessage());
+            }
+            String contentString = new String(request.getRawContent());
             // jainSip不支持y=字段, 移除移除以解析。
             String substring = contentString;
             String ssrc = "0000000404";
@@ -810,61 +889,74 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
             if (ssrcIndex > 0) {
                 substring = contentString.substring(0, ssrcIndex);
             }
-            SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
-
-            //  获取支持的格式
-            Vector mediaDescriptions = sdp.getMediaDescriptions(true);
-            // 查看是否支持PS 负载96
-            int port = -1;
-            //boolean recvonly = false;
-            boolean mediaTransmissionTCP = false;
-            Boolean tcpActive = null;
-            for (int i = 0; i < mediaDescriptions.size(); i++) {
-                MediaDescription mediaDescription = (MediaDescription) mediaDescriptions.get(i);
-                Media media = mediaDescription.getMedia();
-
-                Vector mediaFormats = media.getMediaFormats(false);
-                if (mediaFormats.contains("8")) {
-                    port = media.getMediaPort();
-                    String protocol = media.getProtocol();
-                    // 区分TCP发流还是udp, 当前默认udp
-                    if ("TCP/RTP/AVP".equals(protocol)) {
-                        String setup = mediaDescription.getAttribute("setup");
-                        if (setup != null) {
-                            mediaTransmissionTCP = true;
-                            if ("active".equals(setup)) {
-                                tcpActive = true;
-                            } else if ("passive".equals(setup)) {
-                                tcpActive = false;
+            SessionDescription sdp = null;
+            try {
+                sdp = SdpFactory.getInstance().createSessionDescription(substring);
+                //  获取支持的格式
+                Vector mediaDescriptions = sdp.getMediaDescriptions(true);
+                // 查看是否支持PS 负载96
+                int port = -1;
+                //boolean recvonly = false;
+                boolean mediaTransmissionTCP = false;
+                Boolean tcpActive = null;
+                for (int i = 0; i < mediaDescriptions.size(); i++) {
+                    MediaDescription mediaDescription = (MediaDescription) mediaDescriptions.get(i);
+                    Media media = mediaDescription.getMedia();
+
+                    Vector mediaFormats = media.getMediaFormats(false);
+                    if (mediaFormats.contains("8")) {
+                        port = media.getMediaPort();
+                        String protocol = media.getProtocol();
+                        // 区分TCP发流还是udp, 当前默认udp
+                        if ("TCP/RTP/AVP".equals(protocol)) {
+                            String setup = mediaDescription.getAttribute("setup");
+                            if (setup != null) {
+                                mediaTransmissionTCP = true;
+                                if ("active".equals(setup)) {
+                                    tcpActive = true;
+                                } else if ("passive".equals(setup)) {
+                                    tcpActive = false;
+                                }
                             }
                         }
+                        break;
                     }
-                    break;
                 }
+                if (port == -1) {
+                    logger.info("不支持的媒体格式,返回415");
+                    // 回复不支持的格式
+                    try {
+                        responseAck(request, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415
+                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                        logger.error("[命令发送失败] invite 不支持的媒体格式,返回415, {}", e.getMessage());
+                    }
+                    return;
+                }
+                String username = sdp.getOrigin().getUsername();
+                String addressStr = sdp.getConnection().getAddress();
+                logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", username, addressStr, port, ssrc);
+			
+	            // todo 向zlm服务器申请语音推流转发通道
+	            JSONObject subscribeKey = new JSONObject();
+	            subscribeKey.put("deviceId", username);
+	            subscribeKey.put("addr", addressStr);
+	            subscribeKey.put("port", port);
+	            subscribeKey.put("ssrc", ssrc);
+
+	            GBEventSubscribe.InviteEvent subscribe = GBHookSubscribe.sendStreamNotify(GB_Event.HOOK_BROADCAST_INVITE,subscribeKey);
+	            if (subscribe != null ) {
+	                subscribe.response(0, subscribeKey,request);
+	            }
+			 } catch (SdpException e) {
+                logger.error("[SDP解析异常]", e);
             }
-            if (port == -1) {
-                logger.info("不支持的媒体格式,返回415");
-                // 回复不支持的格式
-                responseAck(serverTransaction, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415
-                return;
-            }
-            String username = sdp.getOrigin().getUsername();
-            String addressStr = sdp.getOrigin().getAddress();
-            logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", username, addressStr, port, ssrc);
-            // todo 向zlm服务器申请语音推流转发通道
-            JSONObject subscribeKey = new JSONObject();
-            subscribeKey.put("deviceId", username);
-            subscribeKey.put("addr", addressStr);
-            subscribeKey.put("port", port);
-            subscribeKey.put("ssrc", ssrc);
-
-            GBEventSubscribe.InviteEvent subscribe = GBHookSubscribe.sendStreamNotify(GB_Event.HOOK_BROADCAST_INVITE,subscribeKey);
-            if (subscribe != null ) {
-                subscribe.response(0, subscribeKey,serverTransaction);
-            }
-        } else {
+    } else {
             logger.warn("来自无效设备/平台的请求");
-            responseAck(serverTransaction, Response.BAD_REQUEST);
+            try {
+                responseAck(request, Response.BAD_REQUEST);; // 不支持的格式,发415
+            } catch (SipException | InvalidArgumentException | ParseException e) {
+                logger.error("[命令发送失败] invite 来自无效设备/平台的请求, {}", e.getMessage());
+            }
         }
     }
 }

+ 60 - 48
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java

@@ -1,6 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.*;
@@ -11,7 +11,6 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
-import com.genersoft.iot.vmp.gb28181.utils.Coordtransform;
 import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
 import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
@@ -20,6 +19,7 @@ import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.DateUtil;
 import com.genersoft.iot.vmp.utils.redis.RedisUtil;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.DocumentException;
 import org.dom4j.Element;
 import org.slf4j.Logger;
@@ -30,16 +30,15 @@ import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
-import javax.sip.ServerTransaction;
 import javax.sip.SipException;
 import javax.sip.header.FromHeader;
 import javax.sip.message.Response;
 import java.text.ParseException;
 import java.util.Iterator;
+import java.util.List;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 /**
@@ -77,8 +76,6 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 	@Autowired
 	private IDeviceChannelService deviceChannelService;
 
-	private boolean taskQueueHandlerRun = false;
-
 	private ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>();
 
 	@Qualifier("taskExecutor")
@@ -94,45 +91,41 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 	@Override
 	public void process(RequestEvent evt) {
 		try {
-			taskQueue.offer(new HandlerCatchData(evt, null, null));
-			ServerTransaction serverTransaction = getServerTransaction(evt);
-			responseAck(serverTransaction, Response.OK);
-			if (!taskQueueHandlerRun) {
-				taskQueueHandlerRun = true;
-				taskExecutor.execute(()-> {
-					while (!taskQueue.isEmpty()) {
-						try {
-							HandlerCatchData take = taskQueue.poll();
-							Element rootElement = getRootElement(take.getEvt());
-							if (rootElement == null) {
-								logger.error("处理NOTIFY消息时未获取到消息体,{}", take.getEvt().getRequest());
-								continue;
-							}
-							String cmd = XmlUtil.getText(rootElement, "CmdType");
-
-							if (CmdType.CATALOG.equals(cmd)) {
-								logger.info("接收到Catalog通知");
-								processNotifyCatalogList(take.getEvt());
-							} else if (CmdType.ALARM.equals(cmd)) {
-								logger.info("接收到Alarm通知");
-								processNotifyAlarm(take.getEvt());
-							} else if (CmdType.MOBILE_POSITION.equals(cmd)) {
-								logger.info("接收到MobilePosition通知");
-								processNotifyMobilePosition(take.getEvt());
-							} else {
-								logger.info("接收到消息:" + cmd);
-							}
-						} catch (DocumentException e) {
-							logger.error("处理NOTIFY消息时错误", e);
+			responseAck((SIPRequest) evt.getRequest(), Response.OK, null, null);
+		}catch (SipException | InvalidArgumentException | ParseException e) {
+			logger.error("未处理的异常 ", e);
+		}
+		boolean runed = !taskQueue.isEmpty();
+		taskQueue.offer(new HandlerCatchData(evt, null, null));
+		if (!runed) {
+			taskExecutor.execute(()-> {
+				while (!taskQueue.isEmpty()) {
+					try {
+						HandlerCatchData take = taskQueue.poll();
+						Element rootElement = getRootElement(take.getEvt());
+						if (rootElement == null) {
+							logger.error("处理NOTIFY消息时未获取到消息体,{}", take.getEvt().getRequest());
+							continue;
 						}
+						String cmd = XmlUtil.getText(rootElement, "CmdType");
+
+						if (CmdType.CATALOG.equals(cmd)) {
+							logger.info("接收到Catalog通知");
+							processNotifyCatalogList(take.getEvt());
+						} else if (CmdType.ALARM.equals(cmd)) {
+							logger.info("接收到Alarm通知");
+							processNotifyAlarm(take.getEvt());
+						} else if (CmdType.MOBILE_POSITION.equals(cmd)) {
+							logger.info("接收到MobilePosition通知");
+							processNotifyMobilePosition(take.getEvt());
+						} else {
+							logger.info("接收到消息:" + cmd);
+						}
+					} catch (DocumentException e) {
+						logger.error("处理NOTIFY消息时错误", e);
 					}
-					taskQueueHandlerRun = false;
-				});
-			}
-		} catch (SipException | InvalidArgumentException | ParseException e) {
-			e.printStackTrace();
-		} finally {
-			taskQueueHandlerRun = false;
+				}
+			});
 		}
 	}
 
@@ -158,6 +151,17 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 			Element deviceIdElement = rootElement.element("DeviceID");
 			String channelId = deviceIdElement.getTextTrim().toString();
 			Device device = redisCatchStorage.getDevice(deviceId);
+
+			if (device == null) {
+				// 根据通道id查询设备Id
+				List<Device> deviceList = deviceChannelService.getDeviceByChannelId(channelId);
+				if (deviceList.size() > 0) {
+					device = deviceList.get(0);
+				}else {
+					logger.warn("[mobilePosition移动位置Notify] 未找到通道{}所属的设备", channelId);
+					return;
+				}
+			}
 			if (device != null) {
 				if (!ObjectUtils.isEmpty(device.getName())) {
 					mobilePosition.setDeviceName(device.getName());
@@ -221,7 +225,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 			jsonObject.put("speed", mobilePosition.getSpeed());
 			redisCatchStorage.sendMobilePositionMsg(jsonObject);
 		} catch (DocumentException  e) {
-			e.printStackTrace();
+			logger.error("未处理的异常 ", e);
 		}
 	}
 
@@ -331,7 +335,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 				publisher.deviceAlarmEventPublish(deviceAlarm);
 			}
 		} catch (DocumentException e) {
-			e.printStackTrace();
+			logger.error("未处理的异常 ", e);
 		}
 	}
 
@@ -389,12 +393,20 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 						case CatalogEvent.OFF :
 							// 离线
 							logger.info("[收到通道离线通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
-							storager.deviceChannelOffline(deviceId, channel.getChannelId());
+							if (userSetting.getRefuseChannelStatusChannelFormNotify()) {
+								storager.deviceChannelOffline(deviceId, channel.getChannelId());
+							}else {
+								logger.info("[收到通道离线通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
+							}
 							break;
 						case CatalogEvent.VLOST:
 							// 视频丢失
 							logger.info("[收到通道视频丢失通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
-							storager.deviceChannelOffline(deviceId, channel.getChannelId());
+							if (userSetting.getRefuseChannelStatusChannelFormNotify()) {
+								storager.deviceChannelOffline(deviceId, channel.getChannelId());
+							}else {
+								logger.info("[收到通道视频丢失通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
+							}
 							break;
 						case CatalogEvent.DEFECT:
 							// 故障
@@ -424,7 +436,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 				}
 			}
 		} catch (DocumentException e) {
-			e.printStackTrace();
+			logger.error("未处理的异常 ", e);
 		}
 	}
 

+ 54 - 57
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java

@@ -1,33 +1,36 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 
 import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.conf.UserSetting;
+import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.RemoteAddressInfo;
 import com.genersoft.iot.vmp.gb28181.bean.WvpSipDate;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
+import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
-import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper;
+import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
 import com.genersoft.iot.vmp.service.IDeviceService;
 import com.genersoft.iot.vmp.utils.DateUtil;
 import gov.nist.javax.sip.RequestEventExt;
 import gov.nist.javax.sip.address.AddressImpl;
 import gov.nist.javax.sip.address.SipUri;
-import gov.nist.javax.sip.header.Expires;
 import gov.nist.javax.sip.header.SIPDateHeader;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 
-import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
-import javax.sip.ServerTransaction;
 import javax.sip.SipException;
-import javax.sip.header.*;
-import javax.sip.message.Request;
+import javax.sip.header.AuthorizationHeader;
+import javax.sip.header.ContactHeader;
+import javax.sip.header.FromHeader;
+import javax.sip.header.ViaHeader;
 import javax.sip.message.Response;
 import java.security.NoSuchAlgorithmException;
 import java.text.ParseException;
@@ -53,6 +56,12 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
     @Autowired
     private IDeviceService deviceService;
 
+    @Autowired
+    private SIPSender sipSender;
+
+    @Autowired
+    private UserSetting userSetting;
+
     @Override
     public void afterPropertiesSet() throws Exception {
         // 添加消息处理的订阅
@@ -70,41 +79,52 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
             RequestEventExt evtExt = (RequestEventExt) evt;
             String requestAddress = evtExt.getRemoteIpAddress() + ":" + evtExt.getRemotePort();
             logger.info("[注册请求] 开始处理: {}", requestAddress);
-            Request request = evt.getRequest();
-            ExpiresHeader expiresHeader = (ExpiresHeader) request.getHeader(Expires.NAME);
+//            MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
+//            QueryExp protocol = Query.match(Query.attr("protocol"), Query.value("HTTP/1.1"));
+////            ObjectName name = new ObjectName("*:type=Connector,*");
+//            ObjectName name = new ObjectName("*:*");
+//            Set<ObjectName> objectNames = beanServer.queryNames(name, protocol);
+//            for (ObjectName objectName : objectNames) {
+//                String catalina = objectName.getDomain();
+//                if ("Catalina".equals(catalina)) {
+//                    System.out.println(objectName.getKeyProperty("port"));
+//                }
+//            }
+
+//            System.out.println(ServiceInfo.getServerPort());
+            SIPRequest request = (SIPRequest)evt.getRequest();
             Response response = null;
             boolean passwordCorrect = false;
             // 注册标志
-            boolean registerFlag = false;
+            boolean registerFlag;
             FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME);
             AddressImpl address = (AddressImpl) fromHeader.getAddress();
             SipUri uri = (SipUri) address.getURI();
             String deviceId = uri.getUser();
-
+            Device device = deviceService.getDevice(deviceId);
+            String password = (device != null && !ObjectUtils.isEmpty(device.getPassword()))? device.getPassword() : sipConfig.getPassword();
             AuthorizationHeader authHead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
-            if (authHead == null && !ObjectUtils.isEmpty(sipConfig.getPassword())) {
-                logger.info("[注册请求] 未携带授权头 回复401: {}", requestAddress);
+            if (authHead == null && !ObjectUtils.isEmpty(password)) {
+                logger.info("[注册请求] 回复401: {}", requestAddress);
                 response = getMessageFactory().createResponse(Response.UNAUTHORIZED, request);
                 new DigestServerAuthenticationHelper().generateChallenge(getHeaderFactory(), response, sipConfig.getDomain());
-                sendResponse(evt, response);
+                sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
                 return;
             }
 
             // 校验密码是否正确
-            passwordCorrect = ObjectUtils.isEmpty(sipConfig.getPassword()) ||
-                    new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request, sipConfig.getPassword());
+            passwordCorrect = ObjectUtils.isEmpty(password) ||
+                    new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request, password);
 
             if (!passwordCorrect) {
                 // 注册失败
                 response = getMessageFactory().createResponse(Response.FORBIDDEN, request);
                 response.setReasonPhrase("wrong password");
                 logger.info("[注册请求] 密码/SIP服务器ID错误, 回复403: {}", requestAddress);
-                sendResponse(evt, response);
+                sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
                 return;
             }
 
-            Device device = deviceService.queryDevice(deviceId);
-
             // 携带授权头并且密码正确
             response = getMessageFactory().createResponse(Response.OK, request);
             // 添加date头
@@ -114,13 +134,9 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
             dateHeader.setDate(wvpSipDate);
             response.addHeader(dateHeader);
 
-            if (expiresHeader == null) {
+            if (request.getExpires() == null) {
                 response = getMessageFactory().createResponse(Response.BAD_REQUEST, request);
-                ServerTransaction serverTransaction = getServerTransaction(evt);
-                serverTransaction.sendResponse(response);
-                if (serverTransaction.getDialog() != null) {
-                    serverTransaction.getDialog().delete();
-                }
+                sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
                 return;
             }
             // 添加Contact头
@@ -128,15 +144,9 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
             // 添加Expires头
             response.addHeader(request.getExpires());
 
-            // 获取到通信地址等信息
-            ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
-            String received = viaHeader.getReceived();
-            int rPort = viaHeader.getRPort();
-            // 解析本地地址替代
-            if (ObjectUtils.isEmpty(received) || rPort == -1) {
-                received = viaHeader.getHost();
-                rPort = viaHeader.getPort();
-            }
+            RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request,
+                    userSetting.getSipUseSourceIpAsRemoteAddress());
+
             if (device == null) {
                 device = new Device();
                 device.setStreamMode("UDP");
@@ -146,15 +156,16 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
                 device.setDeviceId(deviceId);
                 device.setOnline(0);
             }
-            device.setIp(received);
-            device.setPort(rPort);
-            device.setHostAddress(received.concat(":").concat(String.valueOf(rPort)));
-            if (expiresHeader.getExpires() == 0) {
+            device.setIp(remoteAddressInfo.getIp());
+            device.setPort(remoteAddressInfo.getPort());
+            device.setHostAddress(remoteAddressInfo.getIp().concat(":").concat(String.valueOf(remoteAddressInfo.getPort())));
+            device.setLocalIp(request.getLocalAddress().getHostAddress());
+            if (request.getExpires().getExpires() == 0) {
                 // 注销成功
                 registerFlag = false;
             } else {
                 // 注册成功
-                device.setExpires(expiresHeader.getExpires());
+                device.setExpires(request.getExpires().getExpires());
                 registerFlag = true;
                 // 判断TCP还是UDP
                 ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
@@ -162,7 +173,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
                 device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP");
             }
 
-            sendResponse(evt, response);
+            sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
             // 注册成功
             // 保存到redis
             if (registerFlag) {
@@ -171,24 +182,10 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
                 deviceService.online(device);
             } else {
                 logger.info("[注销成功] deviceId: {}->{}" ,deviceId, requestAddress);
-                deviceService.offline(deviceId);
+                deviceService.offline(deviceId, "主动注销");
             }
-        } catch (SipException | InvalidArgumentException | NoSuchAlgorithmException | ParseException e) {
-            e.printStackTrace();
-        }
-
-    }
-
-    private void sendResponse(RequestEvent evt, Response response) throws InvalidArgumentException, SipException {
-        ServerTransaction serverTransaction = getServerTransaction(evt);
-        if (serverTransaction == null) {
-            logger.warn("[回复失败]:{}", response);
-            return;
-        }
-        serverTransaction.sendResponse(response);
-        if (serverTransaction.getDialog() != null) {
-            serverTransaction.getDialog().delete();
+        } catch (SipException | NoSuchAlgorithmException | ParseException e) {
+            logger.error("未处理的异常 ", e);
         }
     }
-
 }

+ 24 - 39
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java

@@ -1,41 +1,30 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 
-import com.genersoft.iot.vmp.common.VideoManagerConstants;
-import com.genersoft.iot.vmp.conf.DynamicTask;
-import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.CmdType;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
 import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo;
-import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeHandlerTask;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
-import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
+import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
 import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
-import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
-import gov.nist.javax.sip.SipProviderImpl;
 import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.message.SIPResponse;
-import gov.nist.javax.sip.stack.SIPClientTransaction;
-import gov.nist.javax.sip.stack.SIPDialog;
-import gov.nist.javax.sip.stack.SIPServerTransaction;
-import gov.nist.javax.sip.stack.SIPServerTransactionImpl;
 import org.dom4j.DocumentException;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Component;
 
-import javax.sip.*;
+import javax.sip.InvalidArgumentException;
+import javax.sip.RequestEvent;
+import javax.sip.SipException;
 import javax.sip.header.ExpiresHeader;
-import javax.sip.message.Request;
 import javax.sip.message.Response;
 import java.text.ParseException;
 
@@ -58,6 +47,9 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 	@Autowired
 	private SubscribeHolder subscribeHolder;
 
+	@Autowired
+	private SIPSender sipSender;
+
 	@Override
 	public void afterPropertiesSet() throws Exception {
 		// 添加消息处理的订阅
@@ -71,8 +63,7 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 	 */
 	@Override
 	public void process(RequestEvent evt) {
-		ServerTransaction serverTransaction = getServerTransaction(evt);
-		Request request = evt.getRequest();
+		SIPRequest request = (SIPRequest) evt.getRequest();
 		try {
 			Element rootElement = getRootElement(evt);
 			if (rootElement == null) {
@@ -81,12 +72,12 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 			}
 			String cmd = XmlUtil.getText(rootElement, "CmdType");
 			if (CmdType.MOBILE_POSITION.equals(cmd)) {
-				processNotifyMobilePosition(serverTransaction, rootElement);
+				processNotifyMobilePosition(request, rootElement);
 //			} else if (CmdType.ALARM.equals(cmd)) {
 //				logger.info("接收到Alarm订阅");
 //				processNotifyAlarm(serverTransaction, rootElement);
 			} else if (CmdType.CATALOG.equals(cmd)) {
-				processNotifyCatalogList(serverTransaction, rootElement);
+				processNotifyCatalogList(request, rootElement);
 			} else {
 				logger.info("接收到消息:" + cmd);
 
@@ -96,16 +87,10 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 					response.setExpires(expireHeader);
 				}
 				logger.info("response : " + response);
-				ServerTransaction transaction = getServerTransaction(evt);
-				if (transaction != null) {
-					transaction.sendResponse(response);
-					transaction.terminate();
-				} else {
-					logger.info("processRequest serverTransactionId is null.");
-				}
+				sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
 			}
 		} catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
-			e.printStackTrace();
+			logger.error("未处理的异常 ", e);
 		}
 
 	}
@@ -113,14 +98,14 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 	/**
 	 * 处理移动位置订阅消息
 	 */
-	private void processNotifyMobilePosition(ServerTransaction serverTransaction, Element rootElement) throws SipException {
-		if (serverTransaction == null) {
+	private void processNotifyMobilePosition(SIPRequest request, Element rootElement) throws SipException {
+		if (request == null) {
 			return;
 		}
-		String platformId = SipUtils.getUserIdFromFromHeader(serverTransaction.getRequest());
+		String platformId = SipUtils.getUserIdFromFromHeader(request);
 		String deviceId = XmlUtil.getText(rootElement, "DeviceID");
 		ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);
-		SubscribeInfo subscribeInfo = new SubscribeInfo(serverTransaction, platformId);
+		SubscribeInfo subscribeInfo = new SubscribeInfo(request, platformId);
 		if (platform == null) {
 			return;
 		}
@@ -149,7 +134,7 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 
 		try {
 			ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(platformId);
-			SIPResponse response = responseXmlAck(serverTransaction, resultXml.toString(), parentPlatform, subscribeInfo.getExpires());
+			SIPResponse response = responseXmlAck(request, resultXml.toString(), parentPlatform, subscribeInfo.getExpires());
 			if (subscribeInfo.getExpires() == 0) {
 				subscribeHolder.removeMobilePositionSubscribe(platformId);
 			}else {
@@ -158,7 +143,7 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 			}
 
 		} catch (SipException | InvalidArgumentException | ParseException e) {
-			e.printStackTrace();
+			logger.error("未处理的异常 ", e);
 		}
 	}
 
@@ -166,17 +151,17 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 
 	}
 
-	private void processNotifyCatalogList(ServerTransaction serverTransaction, Element rootElement) throws SipException {
-		if (serverTransaction == null) {
+	private void processNotifyCatalogList(SIPRequest request, Element rootElement) throws SipException {
+		if (request == null) {
 			return;
 		}
-		String platformId = SipUtils.getUserIdFromFromHeader(serverTransaction.getRequest());
+		String platformId = SipUtils.getUserIdFromFromHeader(request);
 		String deviceId = XmlUtil.getText(rootElement, "DeviceID");
 		ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);
 		if (platform == null){
 			return;
 		}
-		SubscribeInfo subscribeInfo = new SubscribeInfo(serverTransaction, platformId);
+		SubscribeInfo subscribeInfo = new SubscribeInfo(request, platformId);
 
 		String sn = XmlUtil.getText(rootElement, "SN");
 		logger.info("[回复上级的目录订阅请求]: {}/{}", platformId, deviceId);
@@ -196,7 +181,7 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 		}
 		try {
 			ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(platformId);
-			SIPResponse response = responseXmlAck(serverTransaction, resultXml.toString(), parentPlatform, subscribeInfo.getExpires());
+			SIPResponse response = responseXmlAck(request, resultXml.toString(), parentPlatform, subscribeInfo.getExpires());
 			if (subscribeInfo.getExpires() == 0) {
 				subscribeHolder.removeCatalogSubscribe(platformId);
 			}else {
@@ -204,7 +189,7 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 				subscribeHolder.putCatalogSubscribe(platformId, subscribeInfo);
 			}
 		} catch (SipException | InvalidArgumentException | ParseException e) {
-			e.printStackTrace();
+			logger.error("未处理的异常 ", e);
 		}
 	}
 }

+ 3 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/TestInviteRequestProcessor.java

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
+import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.message.SIPResponse;
 import org.springframework.stereotype.Component;
 
@@ -11,8 +12,8 @@ import java.text.ParseException;
 
 @Component
 public class TestInviteRequestProcessor extends SIPRequestProcessorParent {
-    public SIPResponse responseBroadcastSdpACK(ServerTransaction serverTransaction, String sdp, String gbId, String addr, int port)  throws SipException, InvalidArgumentException, ParseException {
-        return responseSdpACK(serverTransaction,
+    public SIPResponse responseBroadcastSdpACK(SIPRequest request, String sdp, String gbId, String addr, int port)  throws SipException, InvalidArgumentException, ParseException {
+        return responseSdpACK(request,
                 sdp,
                 gbId,
                 addr,

+ 7 - 9
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java

@@ -19,7 +19,6 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
-import javax.sip.ServerTransaction;
 import javax.sip.SipException;
 import javax.sip.header.*;
 import javax.sip.message.Response;
@@ -62,8 +61,9 @@ public class InfoRequestProcessor extends SIPRequestProcessorParent implements I
     @Override
     public void process(RequestEvent evt) {
         logger.debug("接收到消息:" + evt.getRequest());
-        String deviceId = SipUtils.getUserIdFromFromHeader(evt.getRequest());
-        CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
+        SIPRequest request = (SIPRequest) evt.getRequest();
+        String deviceId = SipUtils.getUserIdFromFromHeader(request);
+        CallIdHeader callIdHeader = request.getCallIdHeader();
         // 先从会话内查找
         SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null);
 
@@ -71,7 +71,6 @@ public class InfoRequestProcessor extends SIPRequestProcessorParent implements I
         if (ssrcTransaction != null) {
             deviceId = ssrcTransaction.getDeviceId();
         }
-        ServerTransaction serverTransaction = getServerTransaction(evt);
         // 查询设备是否存在
         Device device = redisCatchStorage.getDevice(deviceId);
         // 查询上级平台是否存在
@@ -79,7 +78,6 @@ public class InfoRequestProcessor extends SIPRequestProcessorParent implements I
         try {
             if (device != null && parentPlatform != null) {
                 logger.warn("[重复]平台与设备编号重复:{}", deviceId);
-                SIPRequest request = (SIPRequest) evt.getRequest();
                 String hostAddress = request.getRemoteAddress().getHostAddress();
                 int remotePort = request.getRemotePort();
                 if (device.getHostAddress().equals(hostAddress + ":" + remotePort)) {
@@ -90,7 +88,7 @@ public class InfoRequestProcessor extends SIPRequestProcessorParent implements I
             }
             if (device == null && parentPlatform == null) {
                 // 不存在则回复404
-                responseAck(serverTransaction, Response.NOT_FOUND, "device "+ deviceId +" not found");
+                responseAck(request, Response.NOT_FOUND, "device "+ deviceId +" not found");
                 logger.warn("[设备未找到 ]: {}", deviceId);
                 if (sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()) != null){
                     DeviceNotFoundEvent deviceNotFoundEvent = new DeviceNotFoundEvent(evt.getDialog());
@@ -107,21 +105,21 @@ public class InfoRequestProcessor extends SIPRequestProcessorParent implements I
                     String streamId = sendRtpItem.getStreamId();
                     StreamInfo streamInfo = redisCatchStorage.queryPlayback(null, null, streamId, null);
                     if (null == streamInfo) {
-                        responseAck(serverTransaction, Response.NOT_FOUND, "stream " + streamId + " not found");
+                        responseAck(request, Response.NOT_FOUND, "stream " + streamId + " not found");
                         return;
                     }
                     Device device1 = storager.queryVideoDevice(streamInfo.getDeviceID());
                     cmder.playbackControlCmd(device1,streamInfo,new String(evt.getRequest().getRawContent()),eventResult -> {
                         // 失败的回复
                         try {
-                            responseAck(serverTransaction, eventResult.statusCode, eventResult.msg);
+                            responseAck(request, eventResult.statusCode, eventResult.msg);
                         } catch (SipException | InvalidArgumentException | ParseException e) {
                             logger.error("[命令发送失败] 国标级联 录像控制: {}", e.getMessage());
                         }
                     }, eventResult -> {
                         // 成功的回复
                         try {
-                            responseAck(serverTransaction, eventResult.statusCode);
+                            responseAck(request, eventResult.statusCode);
                         } catch (SipException | InvalidArgumentException | ParseException e) {
                             logger.error("[命令发送失败] 国标级联 录像控制: {}", e.getMessage());
                         }

+ 6 - 13
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageRequestProcessor.java

@@ -23,12 +23,8 @@ import org.springframework.stereotype.Component;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
-import javax.sip.ServerTransaction;
 import javax.sip.SipException;
-import javax.sip.address.SipURI;
-import javax.sip.header.CSeqHeader;
 import javax.sip.header.CallIdHeader;
-import javax.sip.message.Request;
 import javax.sip.message.Response;
 import java.text.ParseException;
 import java.util.Map;
@@ -80,16 +76,13 @@ public class MessageRequestProcessor extends SIPRequestProcessorParent implement
         if (ssrcTransaction != null) {
             deviceId = ssrcTransaction.getDeviceId();
         }
-
-        ServerTransaction serverTransaction = getServerTransaction(evt);
-
+        SIPRequest request = (SIPRequest) evt.getRequest();
         // 查询设备是否存在
         Device device = redisCatchStorage.getDevice(deviceId);
         // 查询上级平台是否存在
         ParentPlatform parentPlatform = storage.queryParentPlatByServerGBId(deviceId);
         try {
             if (device != null && parentPlatform != null) {
-                SIPRequest request = (SIPRequest) evt.getRequest();
                 String hostAddress = request.getRemoteAddress().getHostAddress();
                 int remotePort = request.getRemotePort();
                 if (device.getHostAddress().equals(hostAddress + ":" + remotePort)) {
@@ -100,8 +93,8 @@ public class MessageRequestProcessor extends SIPRequestProcessorParent implement
             }
             if (device == null && parentPlatform == null) {
                 // 不存在则回复404
-                responseAck(serverTransaction, Response.NOT_FOUND, "device "+ deviceId +" not found");
-                logger.warn("[设备未找到 ]: {}", deviceId);
+                responseAck(request, Response.NOT_FOUND, "device "+ deviceId +" not found");
+                logger.warn("[设备未找到 ]deviceId: {}, callId: {}", deviceId, callIdHeader.getCallId());
                 if (sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()) != null){
                     DeviceNotFoundEvent deviceNotFoundEvent = new DeviceNotFoundEvent(evt.getDialog());
                     deviceNotFoundEvent.setCallId(callIdHeader.getCallId());
@@ -114,13 +107,13 @@ public class MessageRequestProcessor extends SIPRequestProcessorParent implement
                     rootElement = getRootElement(evt);
                     if (rootElement == null) {
                         logger.error("处理MESSAGE请求  未获取到消息体{}", evt.getRequest());
-                        responseAck(serverTransaction, Response.BAD_REQUEST, "content is null");
+                        responseAck(request, Response.BAD_REQUEST, "content is null");
                         return;
                     }
                 } catch (DocumentException e) {
                     logger.warn("解析XML消息内容异常", e);
                     // 不存在则回复404
-                    responseAck(serverTransaction, Response.BAD_REQUEST, e.getMessage());
+                    responseAck(request, Response.BAD_REQUEST, e.getMessage());
                 }
                 String name = rootElement.getName();
                 IMessageHandler messageHandler = messageHandlerMap.get(name);
@@ -133,7 +126,7 @@ public class MessageRequestProcessor extends SIPRequestProcessorParent implement
                 }else {
                     // 不支持的message
                     // 不存在则回复415
-                    responseAck(serverTransaction, Response.UNSUPPORTED_MEDIA_TYPE, "Unsupported message type, must Control/Notify/Query/Response");
+                    responseAck(request, Response.UNSUPPORTED_MEDIA_TYPE, "Unsupported message type, must Control/Notify/Query/Response");
                 }
             }
         } catch (SipException e) {

+ 267 - 53
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java

@@ -1,16 +1,18 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.control.cmd;
 
-import com.genersoft.iot.vmp.VManageBootstrap;
+import com.genersoft.iot.vmp.common.enums.DeviceControlType;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.DragZoomRequest;
+import com.genersoft.iot.vmp.gb28181.bean.HomePositionRequest;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
+import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.control.ControlMessageHandler;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
-import com.genersoft.iot.vmp.utils.SpringBeanFactory;
-import gov.nist.javax.sip.SipStackImpl;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -20,17 +22,14 @@ import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 
 import javax.sip.*;
 import javax.sip.address.SipURI;
-import javax.sip.header.HeaderAddress;
-import javax.sip.header.ToHeader;
 import javax.sip.message.Response;
 import java.text.ParseException;
-import java.util.Iterator;
+import java.util.List;
 
-import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
+import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.*;
 
 @Component
 public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
@@ -67,10 +66,10 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent
     @Override
     public void handForPlatform(RequestEvent evt, ParentPlatform parentPlatform, Element rootElement) {
 
-        ServerTransaction serverTransaction = getServerTransaction(evt);
+        SIPRequest request = (SIPRequest) evt.getRequest();
 
         // 此处是上级发出的DeviceControl指令
-        String targetGBId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
+        String targetGBId = ((SipURI) request.getToHeader().getAddress().getURI()).getUser();
         String channelId = getText(rootElement, "DeviceID");
         // 远程启动功能
         if (!ObjectUtils.isEmpty(getText(rootElement, "TeleBoot"))) {
@@ -82,60 +81,275 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent
                 } catch (InvalidArgumentException | ParseException | SipException e) {
                     logger.error("[命令发送失败] 国标级联 注销: {}", e.getMessage());
                 }
-                taskExecutor.execute(()->{
-                    try {
-                        Thread.sleep(3000);
-                        SipProvider up = (SipProvider) SpringBeanFactory.getBean("udpSipProvider");
-                        SipStackImpl stack = (SipStackImpl)up.getSipStack();
-                        stack.stop();
-                        Iterator listener = stack.getListeningPoints();
-                        while (listener.hasNext()) {
-                            stack.deleteListeningPoint((ListeningPoint) listener.next());
-                        }
-                        Iterator providers = stack.getSipProviders();
-                        while (providers.hasNext()) {
-                            stack.deleteSipProvider((SipProvider) providers.next());
-                        }
-                        VManageBootstrap.restart();
-                    } catch (InterruptedException | ObjectInUseException e) {
-                        logger.error("[任务执行失败] 服务重启: {}", e.getMessage());
-                    }
+                taskExecutor.execute(() -> {
+                    // 远程启动
+//                    try {
+//                        Thread.sleep(3000);
+//                        SipProvider up = (SipProvider) SpringBeanFactory.getBean("udpSipProvider");
+//                        SipStackImpl stack = (SipStackImpl)up.getSipStack();
+//                        stack.stop();
+//                        Iterator listener = stack.getListeningPoints();
+//                        while (listener.hasNext()) {
+//                            stack.deleteListeningPoint((ListeningPoint) listener.next());
+//                        }
+//                        Iterator providers = stack.getSipProviders();
+//                        while (providers.hasNext()) {
+//                            stack.deleteSipProvider((SipProvider) providers.next());
+//                        }
+//                        VManageBootstrap.restart();
+//                    } catch (InterruptedException | ObjectInUseException e) {
+//                        logger.error("[任务执行失败] 服务重启: {}", e.getMessage());
+//                    }
                 });
-            } else {
-                // 远程启动指定设备
             }
         }
-        // 云台/前端控制命令
-        if (!ObjectUtils.isEmpty(getText(rootElement,"PTZCmd")) && !parentPlatform.getServerGBId().equals(targetGBId)) {
-            String cmdString = getText(rootElement,"PTZCmd");
+        DeviceControlType deviceControlType = DeviceControlType.typeOf(rootElement);
+        logger.info("[接受deviceControl命令] 命令: {}", deviceControlType);
+        if (!ObjectUtils.isEmpty(deviceControlType) && !parentPlatform.getServerGBId().equals(targetGBId)) {
+            //判断是否存在该通道
             Device deviceForPlatform = storager.queryVideoDeviceByPlatformIdAndChannelId(parentPlatform.getServerGBId(), channelId);
             if (deviceForPlatform == null) {
                 try {
-                    responseAck(serverTransaction, Response.NOT_FOUND);
-                    return;
+                    responseAck(request, Response.NOT_FOUND);
                 } catch (SipException | InvalidArgumentException | ParseException e) {
                     logger.error("[命令发送失败] 错误信息: {}", e.getMessage());
                 }
+                return;
             }
-            try {
-                cmder.fronEndCmd(deviceForPlatform, channelId, cmdString, eventResult -> {
-                    // 失败的回复
-                    try {
-                        responseAck(serverTransaction, eventResult.statusCode, eventResult.msg);
-                    } catch (SipException | InvalidArgumentException | ParseException e) {
-                        logger.error("[命令发送失败] 云台/前端回复: {}", e.getMessage());
-                    }
-                }, eventResult -> {
-                    // 成功的回复
-                    try {
-                        responseAck(serverTransaction, eventResult.statusCode);
-                    } catch (SipException | InvalidArgumentException | ParseException e) {
-                        logger.error("[命令发送失败] 云台/前端回复: {}", e.getMessage());
-                    }
-                });
-            } catch (InvalidArgumentException | SipException | ParseException e) {
-                logger.error("[命令发送失败] 云台/前端: {}", e.getMessage());
+            switch (deviceControlType) {
+                case PTZ:
+                    handlePtzCmd(deviceForPlatform, channelId, rootElement, request, DeviceControlType.PTZ);
+                    break;
+                case ALARM:
+                    handleAlarmCmd(deviceForPlatform, rootElement, request);
+                    break;
+                case GUARD:
+                    handleGuardCmd(deviceForPlatform, rootElement, request, DeviceControlType.GUARD);
+                    break;
+                case RECORD:
+                    handleRecordCmd(deviceForPlatform, channelId, rootElement, request, DeviceControlType.RECORD);
+                    break;
+                case I_FRAME:
+                    handleIFameCmd(deviceForPlatform, request, channelId);
+                    break;
+                case TELE_BOOT:
+                    handleTeleBootCmd(deviceForPlatform, request);
+                    break;
+                case DRAG_ZOOM_IN:
+                    handleDragZoom(deviceForPlatform, channelId, rootElement, request, DeviceControlType.DRAG_ZOOM_IN);
+                    break;
+                case DRAG_ZOOM_OUT:
+                    handleDragZoom(deviceForPlatform, channelId, rootElement, request, DeviceControlType.DRAG_ZOOM_OUT);
+                    break;
+                case HOME_POSITION:
+                    handleHomePositionCmd(deviceForPlatform, channelId, rootElement, request, DeviceControlType.HOME_POSITION);
+                    break;
+                default:
+                    break;
             }
         }
     }
+
+    /**
+     * 处理云台指令
+     *
+     * @param device      设备
+     * @param channelId   通道id
+     * @param rootElement
+     * @param request
+     */
+    private void handlePtzCmd(Device device, String channelId, Element rootElement, SIPRequest request, DeviceControlType type) {
+        String cmdString = getText(rootElement, type.getVal());
+        try {
+            cmder.fronEndCmd(device, channelId, cmdString,
+                    errorResult -> onError(request, errorResult),
+                    okResult -> onOk(request, okResult));
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 云台/前端: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 处理强制关键帧
+     *
+     * @param device    设备
+     * @param channelId 通道id
+     */
+    private void handleIFameCmd(Device device, SIPRequest request, String channelId) {
+        try {
+            cmder.iFrameCmd(device, channelId);
+            responseAck(request, Response.OK);
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 强制关键帧: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 处理重启命令
+     *
+     * @param device 设备信息
+     */
+    private void handleTeleBootCmd(Device device, SIPRequest request) {
+        try {
+            cmder.teleBootCmd(device);
+            responseAck(request, Response.OK);
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 重启: {}", e.getMessage());
+        }
+
+    }
+
+    /**
+     * 处理拉框控制***
+     *
+     * @param device      设备信息
+     * @param channelId   通道id
+     * @param rootElement 根节点
+     * @param type        消息类型
+     */
+    private void handleDragZoom(Device device, String channelId, Element rootElement, SIPRequest request, DeviceControlType type) {
+        try {
+            DragZoomRequest dragZoomRequest = loadElement(rootElement, DragZoomRequest.class);
+            DragZoomRequest.DragZoom dragZoom = dragZoomRequest.getDragZoomIn();
+            if (dragZoom == null) {
+                dragZoom = dragZoomRequest.getDragZoomOut();
+            }
+            StringBuffer cmdXml = new StringBuffer(200);
+            cmdXml.append("<" + type.getVal() + ">\r\n");
+            cmdXml.append("<Length>" + dragZoom.getLength() + "</Length>\r\n");
+            cmdXml.append("<Width>" + dragZoom.getWidth() + "</Width>\r\n");
+            cmdXml.append("<MidPointX>" + dragZoom.getMidPointX() + "</MidPointX>\r\n");
+            cmdXml.append("<MidPointY>" + dragZoom.getMidPointY() + "</MidPointY>\r\n");
+            cmdXml.append("<LengthX>" + dragZoom.getLengthX() + "</LengthX>\r\n");
+            cmdXml.append("<LengthY>" + dragZoom.getLengthY() + "</LengthY>\r\n");
+            cmdXml.append("</" + type.getVal() + ">\r\n");
+            cmder.dragZoomCmd(device, channelId, cmdXml.toString());
+            responseAck(request, Response.OK);
+        } catch (Exception e) {
+            logger.error("[命令发送失败] 拉框控制: {}", e.getMessage());
+        }
+
+    }
+
+    /**
+     * 处理看守位命令***
+     *
+     * @param device      设备信息
+     * @param channelId   通道id
+     * @param rootElement 根节点
+     * @param request     请求信息
+     * @param type        消息类型
+     */
+    private void handleHomePositionCmd(Device device, String channelId, Element rootElement, SIPRequest request, DeviceControlType type) {
+        try {
+            HomePositionRequest homePosition = loadElement(rootElement, HomePositionRequest.class);
+            //获取整个消息主体,我们只需要修改请求头即可
+            HomePositionRequest.HomePosition info = homePosition.getHomePosition();
+            cmder.homePositionCmd(device, channelId, info.getEnabled(), info.getResetTime(), info.getPresetIndex(),
+                    errorResult -> onError(request, errorResult),
+                    okResult -> onOk(request, okResult));
+        } catch (Exception e) {
+            logger.error("[命令发送失败] 看守位设置: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 处理告警消息***
+     *
+     * @param device      设备信息
+     * @param rootElement 根节点
+     * @param request     请求信息
+     */
+    private void handleAlarmCmd(Device device, Element rootElement, SIPRequest request) {
+        //告警方法
+        String alarmMethod = "";
+        //告警类型
+        String alarmType = "";
+        List<Element> info = rootElement.elements("Info");
+        if (info != null) {
+            for (Element element : info) {
+                alarmMethod = getText(element, "AlarmMethod");
+                alarmType = getText(element, "AlarmType");
+            }
+        }
+        try {
+            cmder.alarmCmd(device, alarmMethod, alarmType,
+                    errorResult -> onError(request, errorResult),
+                    okResult -> onOk(request, okResult));
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 告警消息: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 处理录像控制
+     *
+     * @param device      设备信息
+     * @param channelId   通道id
+     * @param rootElement 根节点
+     * @param request     请求信息
+     * @param type        消息类型
+     */
+    private void handleRecordCmd(Device device, String channelId, Element rootElement, SIPRequest request, DeviceControlType type) {
+        //获取整个消息主体,我们只需要修改请求头即可
+        String cmdString = getText(rootElement, type.getVal());
+        try {
+            cmder.recordCmd(device, channelId, cmdString,
+                    errorResult -> onError(request, errorResult),
+                    okResult -> onOk(request, okResult));
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 录像控制: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 处理报警布防/撤防命令
+     *
+     * @param device      设备信息
+     * @param rootElement 根节点
+     * @param request     请求信息
+     * @param type        消息类型
+     */
+    private void handleGuardCmd(Device device, Element rootElement, SIPRequest request, DeviceControlType type) {
+        //获取整个消息主体,我们只需要修改请求头即可
+        String cmdString = getText(rootElement, type.getVal());
+        try {
+            cmder.guardCmd(device, cmdString,
+                    errorResult -> onError(request, errorResult),
+                    okResult -> onOk(request, okResult));
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 布防/撤防命令: {}", e.getMessage());
+        }
+    }
+
+
+    /**
+     * 错误响应处理
+     *
+     * @param request     请求
+     * @param eventResult 响应结构
+     */
+    private void onError(SIPRequest request, SipSubscribe.EventResult eventResult) {
+        // 失败的回复
+        try {
+            responseAck(request, eventResult.statusCode, eventResult.msg);
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.error("[命令发送失败] 回复: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 成功响应处理
+     *
+     * @param request     请求
+     * @param eventResult 响应结构
+     */
+    private void onOk(SIPRequest request, SipSubscribe.EventResult eventResult) {
+        // 成功的回复
+        try {
+            responseAck(request, eventResult.statusCode);
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.error("[命令发送失败] 回复: {}", e.getMessage());
+        }
+    }
 }

+ 126 - 117
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.*;
@@ -8,7 +9,6 @@ import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler;
-import com.genersoft.iot.vmp.gb28181.utils.Coordtransform;
 import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
 import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
 import com.genersoft.iot.vmp.service.IDeviceAlarmService;
@@ -16,6 +16,7 @@ import com.genersoft.iot.vmp.service.IDeviceChannelService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.DateUtil;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -25,17 +26,15 @@ import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
 import javax.sip.SipException;
 import javax.sip.message.Response;
-
 import java.text.ParseException;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
-import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.*;
+import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
 
 /**
  * 报警事件的处理,参考:9.4
@@ -70,8 +69,6 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
     @Autowired
     private IDeviceChannelService deviceChannelService;
 
-    private boolean taskQueueHandlerRun = false;
-
     private ConcurrentLinkedQueue<SipMsgInfo> taskQueue = new ConcurrentLinkedQueue<>();
 
     @Qualifier("taskExecutor")
@@ -87,128 +84,136 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
     @Override
     public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
         logger.info("[收到报警通知]设备:{}", device.getDeviceId());
-
+        boolean isEmpty = taskQueue.isEmpty();
         taskQueue.offer(new SipMsgInfo(evt, device, rootElement));
-        if (!taskQueueHandlerRun) {
-            taskQueueHandlerRun = true;
+        // 回复200 OK
+        try {
+            responseAck((SIPRequest) evt.getRequest(), Response.OK);
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.error("[命令发送失败] 报警通知回复: {}", e.getMessage());
+        }
+        if (isEmpty) {
             taskExecutor.execute(() -> {
                 logger.info("[处理报警通知]待处理数量:{}", taskQueue.size() );
                 while (!taskQueue.isEmpty()) {
-                    SipMsgInfo sipMsgInfo = taskQueue.poll();
-                    // 回复200 OK
                     try {
-                        responseAck(getServerTransaction(sipMsgInfo.getEvt()), Response.OK);
-                    } catch (SipException | InvalidArgumentException | ParseException e) {
-                        logger.error("[处理报警通知], 回复200OK失败", e);
-                    }
-
-                    Element deviceIdElement = sipMsgInfo.getRootElement().element("DeviceID");
-                    String channelId = deviceIdElement.getText().toString();
-
-                    DeviceAlarm deviceAlarm = new DeviceAlarm();
-                    deviceAlarm.setCreateTime(DateUtil.getNow());
-                    deviceAlarm.setDeviceId(sipMsgInfo.getDevice().getDeviceId());
-                    deviceAlarm.setChannelId(channelId);
-                    deviceAlarm.setAlarmPriority(getText(sipMsgInfo.getRootElement(), "AlarmPriority"));
-                    deviceAlarm.setAlarmMethod(getText(sipMsgInfo.getRootElement(), "AlarmMethod"));
-                    String alarmTime = XmlUtil.getText(sipMsgInfo.getRootElement(), "AlarmTime");
-                    if (alarmTime == null) {
-                        continue;
-                    }
-                    deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime));
-                    String alarmDescription = getText(sipMsgInfo.getRootElement(), "AlarmDescription");
-                    if (alarmDescription == null) {
-                        deviceAlarm.setAlarmDescription("");
-                    } else {
-                        deviceAlarm.setAlarmDescription(alarmDescription);
-                    }
-                    String longitude = getText(sipMsgInfo.getRootElement(), "Longitude");
-                    if (longitude != null && NumericUtil.isDouble(longitude)) {
-                        deviceAlarm.setLongitude(Double.parseDouble(longitude));
-                    } else {
-                        deviceAlarm.setLongitude(0.00);
-                    }
-                    String latitude = getText(sipMsgInfo.getRootElement(), "Latitude");
-                    if (latitude != null && NumericUtil.isDouble(latitude)) {
-                        deviceAlarm.setLatitude(Double.parseDouble(latitude));
-                    } else {
-                        deviceAlarm.setLatitude(0.00);
-                    }
+                        SipMsgInfo sipMsgInfo = taskQueue.poll();
+
+                        Element deviceIdElement = sipMsgInfo.getRootElement().element("DeviceID");
+                        String channelId = deviceIdElement.getText().toString();
+
+                        DeviceAlarm deviceAlarm = new DeviceAlarm();
+                        deviceAlarm.setCreateTime(DateUtil.getNow());
+                        deviceAlarm.setDeviceId(sipMsgInfo.getDevice().getDeviceId());
+                        deviceAlarm.setChannelId(channelId);
+                        deviceAlarm.setAlarmPriority(getText(sipMsgInfo.getRootElement(), "AlarmPriority"));
+                        deviceAlarm.setAlarmMethod(getText(sipMsgInfo.getRootElement(), "AlarmMethod"));
+                        String alarmTime = XmlUtil.getText(sipMsgInfo.getRootElement(), "AlarmTime");
+                        if (alarmTime == null) {
+                            continue;
+                        }
+                        deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime));
+                        String alarmDescription = getText(sipMsgInfo.getRootElement(), "AlarmDescription");
+                        if (alarmDescription == null) {
+                            deviceAlarm.setAlarmDescription("");
+                        } else {
+                            deviceAlarm.setAlarmDescription(alarmDescription);
+                        }
+                        String longitude = getText(sipMsgInfo.getRootElement(), "Longitude");
+                        if (longitude != null && NumericUtil.isDouble(longitude)) {
+                            deviceAlarm.setLongitude(Double.parseDouble(longitude));
+                        } else {
+                            deviceAlarm.setLongitude(0.00);
+                        }
+                        String latitude = getText(sipMsgInfo.getRootElement(), "Latitude");
+                        if (latitude != null && NumericUtil.isDouble(latitude)) {
+                            deviceAlarm.setLatitude(Double.parseDouble(latitude));
+                        } else {
+                            deviceAlarm.setLatitude(0.00);
+                        }
 
-                    if (!ObjectUtils.isEmpty(deviceAlarm.getAlarmMethod())) {
-                        if ( deviceAlarm.getAlarmMethod().contains(DeviceAlarmMethod.GPS.getVal() + "")) {
-                            MobilePosition mobilePosition = new MobilePosition();
-                            mobilePosition.setCreateTime(DateUtil.getNow());
-                            mobilePosition.setDeviceId(deviceAlarm.getDeviceId());
-                            mobilePosition.setTime(deviceAlarm.getAlarmTime());
-                            mobilePosition.setLongitude(deviceAlarm.getLongitude());
-                            mobilePosition.setLatitude(deviceAlarm.getLatitude());
-                            mobilePosition.setReportSource("GPS Alarm");
-
-                            // 更新device channel 的经纬度
-                            DeviceChannel deviceChannel = new DeviceChannel();
-                            deviceChannel.setDeviceId(sipMsgInfo.getDevice().getDeviceId());
-                            deviceChannel.setChannelId(channelId);
-                            deviceChannel.setLongitude(mobilePosition.getLongitude());
-                            deviceChannel.setLatitude(mobilePosition.getLatitude());
-                            deviceChannel.setGpsTime(mobilePosition.getTime());
-
-                            deviceChannel = deviceChannelService.updateGps(deviceChannel, sipMsgInfo.getDevice());
-
-                            mobilePosition.setLongitudeWgs84(deviceChannel.getLongitudeWgs84());
-                            mobilePosition.setLatitudeWgs84(deviceChannel.getLatitudeWgs84());
-                            mobilePosition.setLongitudeGcj02(deviceChannel.getLongitudeGcj02());
-                            mobilePosition.setLatitudeGcj02(deviceChannel.getLatitudeGcj02());
-
-                            if (userSetting.getSavePositionHistory()) {
-                                storager.insertMobilePosition(mobilePosition);
+                        if (!ObjectUtils.isEmpty(deviceAlarm.getAlarmMethod())) {
+                            if ( deviceAlarm.getAlarmMethod().contains(DeviceAlarmMethod.GPS.getVal() + "")) {
+                                MobilePosition mobilePosition = new MobilePosition();
+                                mobilePosition.setCreateTime(DateUtil.getNow());
+                                mobilePosition.setDeviceId(deviceAlarm.getDeviceId());
+                                mobilePosition.setTime(deviceAlarm.getAlarmTime());
+                                mobilePosition.setLongitude(deviceAlarm.getLongitude());
+                                mobilePosition.setLatitude(deviceAlarm.getLatitude());
+                                mobilePosition.setReportSource("GPS Alarm");
+
+                                // 更新device channel 的经纬度
+                                DeviceChannel deviceChannel = new DeviceChannel();
+                                deviceChannel.setDeviceId(sipMsgInfo.getDevice().getDeviceId());
+                                deviceChannel.setChannelId(channelId);
+                                deviceChannel.setLongitude(mobilePosition.getLongitude());
+                                deviceChannel.setLatitude(mobilePosition.getLatitude());
+                                deviceChannel.setGpsTime(mobilePosition.getTime());
+
+                                deviceChannel = deviceChannelService.updateGps(deviceChannel, sipMsgInfo.getDevice());
+
+                                mobilePosition.setLongitudeWgs84(deviceChannel.getLongitudeWgs84());
+                                mobilePosition.setLatitudeWgs84(deviceChannel.getLatitudeWgs84());
+                                mobilePosition.setLongitudeGcj02(deviceChannel.getLongitudeGcj02());
+                                mobilePosition.setLatitudeGcj02(deviceChannel.getLatitudeGcj02());
+
+                                if (userSetting.getSavePositionHistory()) {
+                                    storager.insertMobilePosition(mobilePosition);
+                                }
+                                storager.updateChannelPosition(deviceChannel);
+
+                                // 发送redis消息。 通知位置信息的变化
+                                JSONObject jsonObject = new JSONObject();
+                                jsonObject.put("time", mobilePosition.getTime());
+                                jsonObject.put("serial", deviceChannel.getDeviceId());
+                                jsonObject.put("code", deviceChannel.getChannelId());
+                                jsonObject.put("longitude", mobilePosition.getLongitude());
+                                jsonObject.put("latitude", mobilePosition.getLatitude());
+                                jsonObject.put("altitude", mobilePosition.getAltitude());
+                                jsonObject.put("direction", mobilePosition.getDirection());
+                                jsonObject.put("speed", mobilePosition.getSpeed());
+                                redisCatchStorage.sendMobilePositionMsg(jsonObject);
                             }
-                            storager.updateChannelPosition(deviceChannel);
-
-                            // 发送redis消息。 通知位置信息的变化
-                            JSONObject jsonObject = new JSONObject();
-                            jsonObject.put("time", mobilePosition.getTime());
-                            jsonObject.put("serial", deviceChannel.getDeviceId());
-                            jsonObject.put("code", deviceChannel.getChannelId());
-                            jsonObject.put("longitude", mobilePosition.getLongitude());
-                            jsonObject.put("latitude", mobilePosition.getLatitude());
-                            jsonObject.put("altitude", mobilePosition.getAltitude());
-                            jsonObject.put("direction", mobilePosition.getDirection());
-                            jsonObject.put("speed", mobilePosition.getSpeed());
-                            redisCatchStorage.sendMobilePositionMsg(jsonObject);
                         }
-                    }
-                    if (!ObjectUtils.isEmpty(deviceAlarm.getDeviceId())) {
-                        if (deviceAlarm.getAlarmMethod().contains(DeviceAlarmMethod.Video.getVal() + "")) {
-                            deviceAlarm.setAlarmType(getText(sipMsgInfo.getRootElement().element("Info"), "AlarmType"));
+                        if (!ObjectUtils.isEmpty(deviceAlarm.getDeviceId())) {
+                            if (deviceAlarm.getAlarmMethod().contains(DeviceAlarmMethod.Video.getVal() + "")) {
+                                deviceAlarm.setAlarmType(getText(sipMsgInfo.getRootElement().element("Info"), "AlarmType"));
+                            }
+                        }
+                        logger.info("[收到报警通知]内容:{}", JSON.toJSONString(deviceAlarm));
+                        // todo 作者自用判断,其他小伙伴需要此消息可以自行修改,但是不要提在pr里
+                        if (DeviceAlarmMethod.Other.getVal() == Integer.parseInt(deviceAlarm.getAlarmMethod())) {
+                            // 发送给平台的报警信息。 发送redis通知
+                            logger.info("[发送给平台的报警信息]内容:{}", JSONObject.toJSONString(deviceAlarm));
+                            AlarmChannelMessage alarmChannelMessage = new AlarmChannelMessage();
+                            if (deviceAlarm.getAlarmMethod() != null) {
+                                alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod()));
+                            }
+                            alarmChannelMessage.setAlarmDescription(deviceAlarm.getAlarmDescription());
+                            if (deviceAlarm.getAlarmType() != null) {
+                                alarmChannelMessage.setAlarmType(Integer.parseInt(deviceAlarm.getAlarmType()));
+                            }
+                            alarmChannelMessage.setGbId(channelId);
+                            redisCatchStorage.sendAlarmMsg(alarmChannelMessage);
+                            continue;
                         }
-                    }
-                    logger.info("[收到报警通知]内容:{}", JSONObject.toJSON(deviceAlarm));
-                    if ("7".equals(deviceAlarm.getAlarmMethod()) ) {
-                        // 发送给平台的报警信息。 发送redis通知
-                        AlarmChannelMessage alarmChannelMessage = new AlarmChannelMessage();
-                        alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod()));
-                        alarmChannelMessage.setAlarmDescription(deviceAlarm.getAlarmDescription());
-                        alarmChannelMessage.setGbId(channelId);
-                        redisCatchStorage.sendAlarmMsg(alarmChannelMessage);
-                        continue;
-                    }
 
-                    logger.debug("存储报警信息、报警分类");
-                    // 存储报警信息、报警分类
-                    if (sipConfig.isAlarm()) {
-                        deviceAlarmService.add(deviceAlarm);
-                    }
+                        logger.debug("存储报警信息、报警分类");
+                        // 存储报警信息、报警分类
+                        if (sipConfig.isAlarm()) {
+                            deviceAlarmService.add(deviceAlarm);
+                        }
 
-                    if (redisCatchStorage.deviceIsOnline(sipMsgInfo.getDevice().getDeviceId())) {
-                        publisher.deviceAlarmEventPublish(deviceAlarm);
+                        if (redisCatchStorage.deviceIsOnline(sipMsgInfo.getDevice().getDeviceId())) {
+                            publisher.deviceAlarmEventPublish(deviceAlarm);
+                        }
+                    }catch (Exception e) {
+                        logger.error("未处理的异常 ", e);
+                        logger.warn("[收到报警通知] 发现未处理的异常, {}\r\n{}",e.getMessage(), evt.getRequest());
                     }
                 }
-                taskQueueHandlerRun = false;
             });
         }
-
-
     }
 
     @Override
@@ -216,7 +221,7 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
         logger.info("收到来自平台[{}]的报警通知", parentPlatform.getServerGBId());
         // 回复200 OK
         try {
-            responseAck(getServerTransaction(evt), Response.OK);
+            responseAck((SIPRequest) evt.getRequest(), Response.OK);
         } catch (SipException | InvalidArgumentException | ParseException e) {
             logger.error("[命令发送失败] 国标级联 报警通知回复: {}", e.getMessage());
         }
@@ -264,11 +269,15 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
         if (channelId.equals(parentPlatform.getDeviceGBId())) {
             // 发送给平台的报警信息。 发送redis通知
             AlarmChannelMessage alarmChannelMessage = new AlarmChannelMessage();
-            alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod()));
+            if (deviceAlarm.getAlarmMethod() != null) {
+                alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod()));
+            }
             alarmChannelMessage.setAlarmDescription(deviceAlarm.getAlarmDescription());
             alarmChannelMessage.setGbId(channelId);
+            if (deviceAlarm.getAlarmType() != null) {
+                alarmChannelMessage.setAlarmType(Integer.parseInt(deviceAlarm.getAlarmType()));
+            }
             redisCatchStorage.sendAlarmMsg(alarmChannelMessage);
-            return;
         }
     }
 }

+ 47 - 33
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java

@@ -1,28 +1,28 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd;
 
+import com.genersoft.iot.vmp.common.VideoManagerConstants;
+import com.genersoft.iot.vmp.conf.DynamicTask;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
-import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
+import com.genersoft.iot.vmp.gb28181.bean.RemoteAddressInfo;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler;
+import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
 import com.genersoft.iot.vmp.service.IDeviceService;
-import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.DateUtil;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
-import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
 import javax.sip.SipException;
-import javax.sip.header.ViaHeader;
 import javax.sip.message.Response;
 import java.text.ParseException;
 
@@ -32,6 +32,7 @@ import java.text.ParseException;
 @Component
 public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
 
+
     private Logger logger = LoggerFactory.getLogger(KeepaliveNotifyMessageHandler.class);
     private final static String cmdType = "Keepalive";
 
@@ -41,6 +42,12 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
     @Autowired
     private IDeviceService deviceService;
 
+    @Autowired
+    private UserSetting userSetting;
+
+    @Autowired
+    private DynamicTask dynamicTask;
+
     @Override
     public void afterPropertiesSet() throws Exception {
         notifyMessageHandler.addHandler(cmdType, this);
@@ -52,36 +59,43 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
             // 未注册的设备不做处理
             return;
         }
-
+        SIPRequest request = (SIPRequest) evt.getRequest();
+        // 回复200 OK
         try {
-            // 判断RPort是否改变,改变则说明路由nat信息变化,修改设备信息
-            // 获取到通信地址等信息
-            ViaHeader viaHeader = (ViaHeader) evt.getRequest().getHeader(ViaHeader.NAME);
-            String received = viaHeader.getReceived();
-            int rPort = viaHeader.getRPort();
-            // 解析本地地址替代
-            if (ObjectUtils.isEmpty(received) || rPort == -1) {
-                received = viaHeader.getHost();
-                rPort = viaHeader.getPort();
-            }
-            if (device.getPort() != rPort) {
-                device.setPort(rPort);
-                device.setHostAddress(received.concat(":").concat(String.valueOf(rPort)));
-            }
-            device.setKeepaliveTime(DateUtil.getNow());
-            // 回复200 OK
-            responseAck(getServerTransaction(evt), Response.OK);
-            if (device.getOnline() == 1) {
-                deviceService.updateDevice(device);
-            }else {
-                // 对于已经离线的设备判断他的注册是否已经过期
-                if (!deviceService.expire(device)){
-                    deviceService.online(device);
-                }
-            }
+            responseAck(request, Response.OK);
         } catch (SipException | InvalidArgumentException | ParseException e) {
-            logger.error("[命令发送失败] 国标级联 心跳回复: {}", e.getMessage());
+            logger.error("[命令发送失败] 心跳回复: {}", e.getMessage());
         }
+
+        RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request, userSetting.getSipUseSourceIpAsRemoteAddress());
+        if (!device.getIp().equalsIgnoreCase(remoteAddressInfo.getIp()) || device.getPort() != remoteAddressInfo.getPort()) {
+            device.setPort(remoteAddressInfo.getPort());
+            device.setHostAddress(remoteAddressInfo.getIp().concat(":").concat(String.valueOf(remoteAddressInfo.getPort())));
+            device.setIp(remoteAddressInfo.getIp());
+        }
+        if (device.getKeepaliveTime() == null) {
+            device.setKeepaliveIntervalTime(60);
+        }else {
+            long lastTime = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(device.getKeepaliveTime());
+            device.setKeepaliveIntervalTime(new Long(System.currentTimeMillis()/1000-lastTime).intValue());
+        }
+
+        device.setKeepaliveTime(DateUtil.getNow());
+
+        if (device.getOnline() == 1) {
+            deviceService.updateDevice(device);
+        }else {
+            // 对于已经离线的设备判断他的注册是否已经过期
+            if (!deviceService.expire(device)){
+                device.setOnline(0);
+                deviceService.online(device);
+            }
+        }
+        // 刷新过期任务
+        String registerExpireTaskKey = VideoManagerConstants.REGISTER_EXPIRE_TASK_KEY_PREFIX + device.getDeviceId();
+        // 如果三次心跳失败,则设置设备离线
+        dynamicTask.startDelay(registerExpireTaskKey, ()-> deviceService.offline(device.getDeviceId(), "三次心跳失败"), device.getKeepaliveIntervalTime()*1000*3);
+
     }
 
     @Override

+ 2 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java

@@ -14,6 +14,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessag
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -67,7 +68,7 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i
 
         // 回复200 OK
         try {
-            responseAck(getServerTransaction(evt), Response.OK);
+             responseAck((SIPRequest) evt.getRequest(), Response.OK);
         } catch (SipException | InvalidArgumentException | ParseException e) {
             logger.error("[命令发送失败] 国标级联 录像流推送完毕,回复200OK: {}", e.getMessage());
         }

+ 16 - 16
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MobilePositionNotifyMessageHandler.java

@@ -1,6 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
@@ -11,6 +11,7 @@ import com.genersoft.iot.vmp.service.IDeviceChannelService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.DateUtil;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.DocumentException;
 import org.dom4j.Element;
 import org.slf4j.Logger;
@@ -21,7 +22,6 @@ import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
@@ -56,8 +56,6 @@ public class MobilePositionNotifyMessageHandler extends SIPRequestProcessorParen
     @Autowired
     private IDeviceChannelService deviceChannelService;
 
-    private boolean taskQueueHandlerRun = false;
-
     private ConcurrentLinkedQueue<SipMsgInfo> taskQueue = new ConcurrentLinkedQueue<>();
 
     @Qualifier("taskExecutor")
@@ -72,17 +70,22 @@ public class MobilePositionNotifyMessageHandler extends SIPRequestProcessorParen
     @Override
     public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
 
+        boolean isEmpty = taskQueue.isEmpty();
         taskQueue.offer(new SipMsgInfo(evt, device, rootElement));
-        if (!taskQueueHandlerRun) {
-            taskQueueHandlerRun = true;
+        // 回复200 OK
+        try {
+            responseAck((SIPRequest) evt.getRequest(), Response.OK);
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.error("[命令发送失败] 移动位置通知回复: {}", e.getMessage());
+        }
+        if (isEmpty) {
             taskExecutor.execute(() -> {
                 while (!taskQueue.isEmpty()) {
                     SipMsgInfo sipMsgInfo = taskQueue.poll();
                     try {
                         Element rootElementAfterCharset = getRootElement(sipMsgInfo.getEvt(), sipMsgInfo.getDevice().getCharset());
                         if (rootElementAfterCharset == null) {
-                            logger.warn("[ 移动设备位置数据通知 ] content cannot be null, {}", sipMsgInfo.getEvt().getRequest());
-                            responseAck(getServerTransaction(sipMsgInfo.getEvt()), Response.BAD_REQUEST);
+                            logger.warn("[移动位置通知] {}处理失败,未识别到信息体", device.getDeviceId());
                             continue;
                         }
                         MobilePosition mobilePosition = new MobilePosition();
@@ -132,8 +135,6 @@ public class MobilePositionNotifyMessageHandler extends SIPRequestProcessorParen
                             storager.insertMobilePosition(mobilePosition);
                         }
                         storager.updateChannelPosition(deviceChannel);
-                        //回复 200 OK
-                        responseAck(getServerTransaction(sipMsgInfo.getEvt()), Response.OK);
 
                         // 发送redis消息。 通知位置信息的变化
                         JSONObject jsonObject = new JSONObject();
@@ -147,16 +148,15 @@ public class MobilePositionNotifyMessageHandler extends SIPRequestProcessorParen
                         jsonObject.put("speed", mobilePosition.getSpeed());
                         redisCatchStorage.sendMobilePositionMsg(jsonObject);
 
-                    } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
-                        e.printStackTrace();
+                    } catch (DocumentException e) {
+                        logger.error("未处理的异常 ", e);
+                    } catch (Exception e) {
+                        logger.warn("[移动位置通知] 发现未处理的异常, \r\n{}", evt.getRequest());
+                        logger.error("[移动位置通知] 异常内容: ", e);
                     }
-
                 }
-                taskQueueHandlerRun = false;
             });
         }
-
-
     }
 
     @Override

+ 2 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/AlarmQueryMessageHandler.java

@@ -9,6 +9,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorP
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.QueryMessageHandler;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -58,7 +59,7 @@ public class AlarmQueryMessageHandler extends SIPRequestProcessorParent implemen
 
         logger.info("不支持alarm查询");
         try {
-            responseAck(getServerTransaction(evt), Response.NOT_FOUND, "not support alarm query");
+             responseAck((SIPRequest) evt.getRequest(), Response.NOT_FOUND, "not support alarm query");
         } catch (SipException | InvalidArgumentException | ParseException e) {
             logger.error("[命令发送失败] 国标级联 alarm查询回复200OK: {}", e.getMessage());
         }

+ 36 - 29
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java

@@ -1,14 +1,16 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.cmd;
 
 import com.genersoft.iot.vmp.conf.SipConfig;
-import com.genersoft.iot.vmp.gb28181.bean.*;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
+import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
-import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.QueryMessageHandler;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -62,38 +64,41 @@ public class CatalogQueryMessageHandler extends SIPRequestProcessorParent implem
     @Override
     public void handForPlatform(RequestEvent evt, ParentPlatform parentPlatform, Element rootElement) {
 
-        String key = DeferredResultHolder.CALLBACK_CMD_CATALOG + parentPlatform.getServerGBId();
         FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME);
         try {
             // 回复200 OK
-            responseAck(getServerTransaction(evt), Response.OK);
-            Element snElement = rootElement.element("SN");
-            String sn = snElement.getText();
-            // 准备回复通道信息
-            List<DeviceChannel> deviceChannelInPlatforms = storager.queryChannelWithCatalog(parentPlatform.getServerGBId());
-            // 查询关联的直播通道
-            List<DeviceChannel> gbStreams = storager.queryGbStreamListInPlatform(parentPlatform.getServerGBId());
-            // 回复目录信息
-            List<DeviceChannel> catalogs =  storager.queryCatalogInPlatform(parentPlatform.getServerGBId());
-
-            List<DeviceChannel> allChannels = new ArrayList<>();
-
-            // 回复平台
+             responseAck((SIPRequest) evt.getRequest(), Response.OK);
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.error("[命令发送失败] 国标级联 目录查询回复200OK: {}", e.getMessage());
+        }
+        Element snElement = rootElement.element("SN");
+        String sn = snElement.getText();
+        // 准备回复通道信息
+        List<DeviceChannel> deviceChannelInPlatforms = storager.queryChannelWithCatalog(parentPlatform.getServerGBId());
+        // 查询关联的直播通道
+        List<DeviceChannel> gbStreams = storager.queryGbStreamListInPlatform(parentPlatform.getServerGBId());
+        // 回复目录信息
+        List<DeviceChannel> catalogs =  storager.queryCatalogInPlatform(parentPlatform.getServerGBId());
+
+        List<DeviceChannel> allChannels = new ArrayList<>();
+
+        // 回复平台
 //            DeviceChannel deviceChannel = getChannelForPlatform(parentPlatform);
 //            allChannels.add(deviceChannel);
 
-            // 回复目录
-            if (catalogs.size() > 0) {
-                allChannels.addAll(catalogs);
-            }
-            // 回复级联的通道
-            if (deviceChannelInPlatforms.size() > 0) {
-                allChannels.addAll(deviceChannelInPlatforms);
-            }
-            // 回复直播的通道
-            if (gbStreams.size() > 0) {
-                allChannels.addAll(gbStreams);
-            }
+        // 回复目录
+        if (catalogs.size() > 0) {
+            allChannels.addAll(catalogs);
+        }
+        // 回复级联的通道
+        if (deviceChannelInPlatforms.size() > 0) {
+            allChannels.addAll(deviceChannelInPlatforms);
+        }
+        // 回复直播的通道
+        if (gbStreams.size() > 0) {
+            allChannels.addAll(gbStreams);
+        }
+        try {
             if (allChannels.size() > 0) {
                 cmderFroPlatform.catalogQuery(allChannels, parentPlatform, sn, fromHeader.getTag());
             }else {
@@ -101,9 +106,11 @@ public class CatalogQueryMessageHandler extends SIPRequestProcessorParent implem
                 cmderFroPlatform.catalogQuery(null, parentPlatform, sn, fromHeader.getTag(), 0);
             }
         } catch (SipException | InvalidArgumentException | ParseException e) {
-            logger.error("[命令发送失败] 国标级联 目录查询: {}", e.getMessage());
+            logger.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage());
         }
 
+
+
     }
 
     private DeviceChannel getChannelForPlatform(ParentPlatform platform) {

+ 25 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceInfoQueryMessageHandler.java

@@ -6,6 +6,8 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.QueryMessageHandler;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -20,6 +22,8 @@ import javax.sip.header.FromHeader;
 import javax.sip.message.Response;
 import java.text.ParseException;
 
+import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
+
 @Component
 public class DeviceInfoQueryMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
 
@@ -31,6 +35,8 @@ public class DeviceInfoQueryMessageHandler extends SIPRequestProcessorParent imp
 
     @Autowired
     private SIPCommanderFroPlatform cmderFroPlatform;
+    @Autowired
+    private IVideoManagerStorage storager;
 
     @Override
     public void afterPropertiesSet() throws Exception {
@@ -48,13 +54,30 @@ public class DeviceInfoQueryMessageHandler extends SIPRequestProcessorParent imp
         FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME);
         try {
             // 回复200 OK
-            responseAck(getServerTransaction(evt), Response.OK);
+            responseAck((SIPRequest) evt.getRequest(), Response.OK);
         } catch (SipException | InvalidArgumentException | ParseException e) {
             logger.error("[命令发送失败] DeviceInfo查询回复: {}", e.getMessage());
+            return;
         }
         String sn = rootElement.element("SN").getText();
+
+        /*根据WVP原有的数据结构,设备和通道是分开放置,设备信息都是存放在设备表里,通道表里的设备信息不可作为真实信息处理
+        大部分NVR/IPC设备对他的通道信息实现都是返回默认的值没有什么参考价值。NVR/IPC通道我们统一使用设备表的设备信息来作为返回。
+        我们这里使用查询数据库的方式来实现这个设备信息查询的功能,在其他地方对设备信息更新达到正确的目的。*/
+
+        String channelId = getText(rootElement, "DeviceID");
+        // 查询这是通道id还是设备id
+        Device device = null;
+        // 如果id指向平台的国标编号,那么就是查询平台的信息
+        if (!parentPlatform.getDeviceGBId().equals(channelId)) {
+            device = storager.queryDeviceInfoByPlatformIdAndChannelId(parentPlatform.getServerGBId(), channelId);
+            if (device ==null){
+                logger.error("[平台没有该通道的使用权限]:platformId"+parentPlatform.getServerGBId()+"  deviceID:"+channelId);
+                return;
+            }
+        }
         try {
-            cmderFroPlatform.deviceInfoResponse(parentPlatform, sn, fromHeader.getTag());
+            cmderFroPlatform.deviceInfoResponse(parentPlatform, device, sn, fromHeader.getTag());
         } catch (SipException | InvalidArgumentException | ParseException e) {
             logger.error("[命令发送失败] 国标级联 DeviceInfo查询回复: {}", e.getMessage());
         }

+ 12 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceStatusQueryMessageHandler.java

@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.
 
 import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
@@ -9,6 +10,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorP
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.QueryMessageHandler;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -23,6 +25,8 @@ import javax.sip.header.FromHeader;
 import javax.sip.message.Response;
 import java.text.ParseException;
 
+import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
+
 @Component
 public class DeviceStatusQueryMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
 
@@ -61,13 +65,19 @@ public class DeviceStatusQueryMessageHandler extends SIPRequestProcessorParent i
         FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME);
         // 回复200 OK
         try {
-            responseAck(getServerTransaction(evt), Response.OK);
+            responseAck((SIPRequest) evt.getRequest(), Response.OK);
         } catch (SipException | InvalidArgumentException | ParseException e) {
             logger.error("[命令发送失败] 国标级联 DeviceStatus查询回复200OK: {}", e.getMessage());
         }
         String sn = rootElement.element("SN").getText();
+        String channelId = getText(rootElement, "DeviceID");
+        DeviceChannel deviceChannel = storager.queryChannelInParentPlatform(parentPlatform.getServerGBId(), channelId);
+        if (deviceChannel ==null){
+            logger.error("[平台没有该通道的使用权限]:platformId"+parentPlatform.getServerGBId()+"  deviceID:"+channelId);
+            return;
+        }
         try {
-            cmderFroPlatform.deviceStatusResponse(parentPlatform, sn, fromHeader.getTag());
+            cmderFroPlatform.deviceStatusResponse(parentPlatform,channelId, sn, fromHeader.getTag(),deviceChannel.getStatus());
         } catch (SipException | InvalidArgumentException | ParseException e) {
             logger.error("[命令发送失败] 国标级联 DeviceStatus查询回复: {}", e.getMessage());
         }

+ 7 - 9
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/RecordInfoQueryMessageHandler.java

@@ -12,6 +12,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.Q
 import com.genersoft.iot.vmp.utils.DateUtil;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.storager.dao.dto.ChannelSourceInfo;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -21,9 +22,7 @@ import org.springframework.stereotype.Component;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
-import javax.sip.ServerTransaction;
 import javax.sip.SipException;
-import javax.sip.header.FromHeader;
 import javax.sip.message.Response;
 import java.text.ParseException;
 import java.util.List;
@@ -68,8 +67,7 @@ public class RecordInfoQueryMessageHandler extends SIPRequestProcessorParent imp
     @Override
     public void handForPlatform(RequestEvent evt, ParentPlatform parentPlatform, Element rootElement) {
 
-        FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME);
-        ServerTransaction serverTransaction = getServerTransaction(evt);
+        SIPRequest request = (SIPRequest) evt.getRequest();
         Element snElement = rootElement.element("SN");
         int sn = Integer.parseInt(snElement.getText());
         Element deviceIDElement = rootElement.element("DeviceID");
@@ -104,7 +102,7 @@ public class RecordInfoQueryMessageHandler extends SIPRequestProcessorParent imp
             // 接收录像数据
             recordEndEventListener.addEndEventHandler(deviceChannel.getDeviceId(), channelId, (recordInfo)->{
                 try {
-                    cmderFroPlatform.recordInfo(deviceChannel, parentPlatform, fromHeader.getTag(), recordInfo);
+                    cmderFroPlatform.recordInfo(deviceChannel, parentPlatform, request.getFromTag(), recordInfo);
                 } catch (SipException | InvalidArgumentException | ParseException e) {
                     logger.error("[命令发送失败] 国标级联 回复录像数据: {}", e.getMessage());
                 }
@@ -114,14 +112,14 @@ public class RecordInfoQueryMessageHandler extends SIPRequestProcessorParent imp
                         DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTime), sn, secrecy, type, (eventResult -> {
                             // 回复200 OK
                             try {
-                                responseAck(serverTransaction, Response.OK);
+                                responseAck(request, Response.OK);
                             } catch (SipException | InvalidArgumentException | ParseException e) {
                                 logger.error("[命令发送失败] 录像查询回复: {}", e.getMessage());
                             }
                         }),(eventResult -> {
                             // 查询失败
                             try {
-                                responseAck(serverTransaction, eventResult.statusCode, eventResult.msg);
+                                responseAck(request, eventResult.statusCode, eventResult.msg);
                             } catch (SipException | InvalidArgumentException | ParseException e) {
                                 logger.error("[命令发送失败] 录像查询回复: {}", e.getMessage());
                             }
@@ -133,13 +131,13 @@ public class RecordInfoQueryMessageHandler extends SIPRequestProcessorParent imp
         }else if (channelSources.get(1).getCount() > 0) { // 直播流
             // TODO
             try {
-                responseAck(serverTransaction, Response.NOT_IMPLEMENTED); // 回复未实现
+                responseAck(request, Response.NOT_IMPLEMENTED); // 回复未实现
             } catch (SipException | InvalidArgumentException | ParseException e) {
                 logger.error("[命令发送失败] 录像查询: {}", e.getMessage());
             }
         }else { // 错误的请求
             try {
-                responseAck(serverTransaction, Response.BAD_REQUEST);
+                responseAck(request, Response.BAD_REQUEST);
             } catch (SipException | InvalidArgumentException | ParseException e) {
                 logger.error("[命令发送失败] 录像查询: {}", e.getMessage());
             }

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/AlarmResponseMessageHandler.java

@@ -1,6 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;

+ 9 - 8
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java

@@ -1,6 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.gb28181.GBEventSubscribe;
 import com.genersoft.iot.vmp.gb28181.GBHookSubscribeFactory;
 import com.genersoft.iot.vmp.gb28181.GB_Event;
@@ -13,7 +13,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorP
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
 import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
-import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -51,13 +51,10 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i
     @Override
     public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
         try {
-
-            logger.info("[sip received] broadcast response from {}",device.getDeviceId());
-            // invite 信息完成
-
-            ServerTransaction serverTransaction = getServerTransaction(evt);
+            String channelId = getText(rootElement, "DeviceID");
+            String key = DeferredResultHolder.CALLBACK_CMD_BROADCAST + device.getDeviceId() + channelId;
             // 回复200 OK
-            responseAck(serverTransaction, Response.OK);
+            responseAck((SIPRequest) evt.getRequest(), Response.OK);
             // 此处是对本平台发出Broadcast指令的应答
             JSONObject json = new JSONObject();
             XmlUtil.node2Json(rootElement, json);
@@ -66,6 +63,10 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i
                 logger.debug(json.toJSONString());
             }
 //            HookSubscribeForKey broadcastForInviteHook = GBHookSubscribeFactory.on_broadcast_invite(deviceId);
+			RequestMessage msg = new RequestMessage();
+            msg.setKey(key);
+            msg.setData(json);
+            deferredResultHolder.invokeAllResult(msg);
 
         } catch (ParseException | SipException | InvalidArgumentException e) {
             logger.error("[命令发送失败] 国标级联 语音喊话: {}", e.getMessage());

+ 63 - 66
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java

@@ -1,19 +1,14 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
 
-import com.genersoft.iot.vmp.conf.SipConfig;
-import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.*;
-import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 import com.genersoft.iot.vmp.gb28181.session.CatalogDataCatch;
-import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
-import com.genersoft.iot.vmp.gb28181.utils.Coordtransform;
-import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
+import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
 import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
-import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.DocumentException;
 import org.dom4j.Element;
 import org.slf4j.Logger;
@@ -23,11 +18,9 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
-import org.springframework.util.StringUtils;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
-import javax.sip.ServerTransaction;
 import javax.sip.SipException;
 import javax.sip.message.Response;
 import java.text.ParseException;
@@ -45,12 +38,10 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
     private Logger logger = LoggerFactory.getLogger(CatalogResponseMessageHandler.class);
     private final String cmdType = "Catalog";
 
-    private boolean taskQueueHandlerRun = false;
-
     @Autowired
     private ResponseMessageHandler responseMessageHandler;
 
-    private ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>();
+    private final ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>();
 
     @Autowired
     private IVideoManagerStorage storager;
@@ -69,75 +60,81 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
 
     @Override
     public void handForDevice(RequestEvent evt, Device device, Element element) {
+        boolean isEmpty = taskQueue.isEmpty();
         taskQueue.offer(new HandlerCatchData(evt, device, element));
         // 回复200 OK
-        ServerTransaction serverTransaction = getServerTransaction(evt);
         try {
-            responseAck(serverTransaction, Response.OK);
+            responseAck((SIPRequest) evt.getRequest(), Response.OK);
         } catch (SipException | InvalidArgumentException | ParseException e) {
             logger.error("[命令发送失败] 目录查询回复: {}", e.getMessage());
         }
-        if (!taskQueueHandlerRun) {
-            taskQueueHandlerRun = true;
+        // 如果不为空则说明已经开启消息处理
+        if (isEmpty) {
             taskExecutor.execute(() -> {
                 while (!taskQueue.isEmpty()) {
-                    HandlerCatchData take = taskQueue.poll();
-                    Element rootElement = null;
+                    // 全局异常捕获,保证下一条可以得到处理
                     try {
-                        rootElement = getRootElement(take.getEvt(), take.getDevice().getCharset());
-                    } catch (DocumentException e) {
-                        logger.error("[xml解析] 失败: ", e);
-                        continue;
-                    }
-                    if (rootElement == null) {
-                        logger.warn("[ 收到通道 ] content cannot be null, {}", evt.getRequest());
-                        continue;
-                    }
-                    Element deviceListElement = rootElement.element("DeviceList");
-                    Element sumNumElement = rootElement.element("SumNum");
-                    Element snElement = rootElement.element("SN");
-                    int sumNum = Integer.parseInt(sumNumElement.getText());
-
-                    if (sumNum == 0) {
-                        logger.info("[收到通道]设备:{}的: 0个", take.getDevice().getDeviceId());
-                        // 数据已经完整接收
-                        storager.cleanChannelsForDevice(take.getDevice().getDeviceId());
-                        catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), null);
-                    } else {
-                        Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
-                        if (deviceListIterator != null) {
-                            List<DeviceChannel> channelList = new ArrayList<>();
-                            // 遍历DeviceList
-                            while (deviceListIterator.hasNext()) {
-                                Element itemDevice = deviceListIterator.next();
-                                Element channelDeviceElement = itemDevice.element("DeviceID");
-                                if (channelDeviceElement == null) {
-                                    continue;
+                        HandlerCatchData take = taskQueue.poll();
+                        Element rootElement = null;
+                        try {
+                            rootElement = getRootElement(take.getEvt(), take.getDevice().getCharset());
+                        } catch (DocumentException e) {
+                            logger.error("[xml解析] 失败: ", e);
+                            continue;
+                        }
+                        if (rootElement == null) {
+                            logger.warn("[ 收到通道 ] content cannot be null, {}", evt.getRequest());
+                            continue;
+                        }
+                        Element deviceListElement = rootElement.element("DeviceList");
+                        Element sumNumElement = rootElement.element("SumNum");
+                        Element snElement = rootElement.element("SN");
+                        int sumNum = Integer.parseInt(sumNumElement.getText());
+
+                        if (sumNum == 0) {
+                            logger.info("[收到通道]设备:{}的: 0个", take.getDevice().getDeviceId());
+                            // 数据已经完整接收
+                            storager.cleanChannelsForDevice(take.getDevice().getDeviceId());
+                            catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), null);
+                        } else {
+                            Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
+                            if (deviceListIterator != null) {
+                                List<DeviceChannel> channelList = new ArrayList<>();
+                                // 遍历DeviceList
+                                while (deviceListIterator.hasNext()) {
+                                    Element itemDevice = deviceListIterator.next();
+                                    Element channelDeviceElement = itemDevice.element("DeviceID");
+                                    if (channelDeviceElement == null) {
+                                        continue;
+                                    }
+                                    DeviceChannel deviceChannel = XmlUtil.channelContentHander(itemDevice, device, null);
+                                    deviceChannel = SipUtils.updateGps(deviceChannel, device.getGeoCoordSys());
+                                    deviceChannel.setDeviceId(take.getDevice().getDeviceId());
+
+                                    channelList.add(deviceChannel);
                                 }
-                                DeviceChannel deviceChannel = XmlUtil.channelContentHander(itemDevice, device, null);
-                                deviceChannel.setDeviceId(take.getDevice().getDeviceId());
-
-                                channelList.add(deviceChannel);
-                            }
-                            int sn = Integer.parseInt(snElement.getText());
-                            catalogDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, take.getDevice(), channelList);
-                            logger.info("[收到通道]设备: {} -> {}个,{}/{}", take.getDevice().getDeviceId(), channelList.size(), catalogDataCatch.get(take.getDevice().getDeviceId()) == null ? 0 : catalogDataCatch.get(take.getDevice().getDeviceId()).size(), sumNum);
-                            if (catalogDataCatch.get(take.getDevice().getDeviceId()).size() == sumNum) {
-                                // 数据已经完整接收, 此时可能存在某个设备离线变上线的情况,但是考虑到性能,此处不做处理,
-                                // 目前支持设备通道上线通知时和设备上线时向上级通知
-                                boolean resetChannelsResult = storager.resetChannels(take.getDevice().getDeviceId(), catalogDataCatch.get(take.getDevice().getDeviceId()));
-                                if (!resetChannelsResult) {
-                                    String errorMsg = "接收成功,写入失败,共" + sumNum + "条,已接收" + catalogDataCatch.get(take.getDevice().getDeviceId()).size() + "条";
-                                    catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), errorMsg);
-                                } else {
-                                    catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), null);
+                                int sn = Integer.parseInt(snElement.getText());
+                                catalogDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, take.getDevice(), channelList);
+                                logger.info("[收到通道]设备: {} -> {}个,{}/{}", take.getDevice().getDeviceId(), channelList.size(), catalogDataCatch.get(take.getDevice().getDeviceId()) == null ? 0 : catalogDataCatch.get(take.getDevice().getDeviceId()).size(), sumNum);
+                                if (catalogDataCatch.get(take.getDevice().getDeviceId()).size() == sumNum) {
+                                    // 数据已经完整接收, 此时可能存在某个设备离线变上线的情况,但是考虑到性能,此处不做处理,
+                                    // 目前支持设备通道上线通知时和设备上线时向上级通知
+                                    boolean resetChannelsResult = storager.resetChannels(take.getDevice().getDeviceId(), catalogDataCatch.get(take.getDevice().getDeviceId()));
+                                    if (!resetChannelsResult) {
+                                        String errorMsg = "接收成功,写入失败,共" + sumNum + "条,已接收" + catalogDataCatch.get(take.getDevice().getDeviceId()).size() + "条";
+                                        catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), errorMsg);
+                                    } else {
+                                        catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), null);
+                                    }
                                 }
                             }
-                        }
 
+                        }
+                    }catch (Exception e) {
+                        logger.warn("[收到通道] 发现未处理的异常, \r\n{}", evt.getRequest());
+                        logger.error("[收到通道] 异常内容: ", e);
                     }
                 }
-                taskQueueHandlerRun = false;
             });
         }
 

+ 15 - 13
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/ConfigDownloadResponseMessageHandler.java

@@ -1,6 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
@@ -10,6 +10,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorP
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
 import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -52,20 +53,21 @@ public class ConfigDownloadResponseMessageHandler extends SIPRequestProcessorPar
         String key = DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + device.getDeviceId() + channelId;
         try {
             // 回复200 OK
-            responseAck(getServerTransaction(evt), Response.OK);
-            // 此处是对本平台发出DeviceControl指令的应答
-            JSONObject json = new JSONObject();
-            XmlUtil.node2Json(element, json);
-            if (logger.isDebugEnabled()) {
-                logger.debug(json.toJSONString());
-            }
-            RequestMessage msg = new RequestMessage();
-            msg.setKey(key);
-            msg.setData(json);
-            deferredResultHolder.invokeAllResult(msg);
+            responseAck((SIPRequest) evt.getRequest(), Response.OK);
         } catch (SipException | InvalidArgumentException | ParseException e) {
-            logger.error("[命令发送失败] 国标级联 设备配置查询: {}", e.getMessage());
+            logger.error("[命令发送失败] 设备配置查询: {}", e.getMessage());
         }
+        // 此处是对本平台发出DeviceControl指令的应答
+        JSONObject json = new JSONObject();
+        XmlUtil.node2Json(element, json);
+        if (logger.isDebugEnabled()) {
+            logger.debug(json.toJSONString());
+        }
+        RequestMessage msg = new RequestMessage();
+        msg.setKey(key);
+        msg.setData(json);
+        deferredResultHolder.invokeAllResult(msg);
+
 
     }
 

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceConfigResponseMessageHandler.java

@@ -1,6 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;

+ 15 - 13
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceControlResponseMessageHandler.java

@@ -1,6 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
@@ -9,6 +9,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorP
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
 import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -46,21 +47,22 @@ public class DeviceControlResponseMessageHandler extends SIPRequestProcessorPare
     public void handForDevice(RequestEvent evt, Device device, Element element) {
         // 此处是对本平台发出DeviceControl指令的应答
         try {
-            responseAck(getServerTransaction(evt), Response.OK);
-            JSONObject json = new JSONObject();
-            String channelId = getText(element, "DeviceID");
-            XmlUtil.node2Json(element, json);
-            if (logger.isDebugEnabled()) {
-                logger.debug(json.toJSONString());
-            }
-            RequestMessage msg = new RequestMessage();
-            String key = DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL +  device.getDeviceId() + channelId;
-            msg.setKey(key);
-            msg.setData(json);
-            deferredResultHolder.invokeAllResult(msg);
+             responseAck((SIPRequest) evt.getRequest(), Response.OK);
         } catch (SipException | InvalidArgumentException | ParseException e) {
             logger.error("[命令发送失败] 国标级联 设备控制: {}", e.getMessage());
         }
+        JSONObject json = new JSONObject();
+        String channelId = getText(element, "DeviceID");
+        XmlUtil.node2Json(element, json);
+        if (logger.isDebugEnabled()) {
+            logger.debug(json.toJSONString());
+        }
+        RequestMessage msg = new RequestMessage();
+        String key = DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL +  device.getDeviceId() + channelId;
+        msg.setKey(key);
+        msg.setData(json);
+        deferredResultHolder.invokeAllResult(msg);
+
     }
 
     @Override

+ 15 - 24
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceInfoResponseMessageHandler.java

@@ -13,6 +13,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.respons
 import com.genersoft.iot.vmp.service.IDeviceService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.DocumentException;
 import org.dom4j.Element;
 import org.slf4j.Logger;
@@ -21,11 +22,9 @@ import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
-import javax.sip.ServerTransaction;
 import javax.sip.SipException;
 import javax.sip.message.Response;
 import java.text.ParseException;
@@ -44,20 +43,9 @@ public class DeviceInfoResponseMessageHandler extends SIPRequestProcessorParent
     @Autowired
     private ResponseMessageHandler responseMessageHandler;
 
-    @Autowired
-    private IVideoManagerStorage storager;
-
-    @Autowired
-    private IRedisCatchStorage redisCatchStorage;
-
     @Autowired
     private DeferredResultHolder deferredResultHolder;
 
-    @Autowired
-    private SipConfig config;
-
-    @Autowired
-    private EventPublisher publisher;
 
     @Autowired
     private IDeviceService deviceService;
@@ -75,12 +63,17 @@ public class DeviceInfoResponseMessageHandler extends SIPRequestProcessorParent
             logger.warn("[接收到DeviceInfo应答消息,但是设备已经离线]:" + (device != null ? device.getDeviceId():"" ));
             return;
         }
-        ServerTransaction serverTransaction = getServerTransaction(evt);
+        SIPRequest request = (SIPRequest) evt.getRequest();
         try {
             rootElement = getRootElement(evt, device.getCharset());
+
             if (rootElement == null) {
                 logger.warn("[ 接收到DeviceInfo应答消息 ] content cannot be null, {}", evt.getRequest());
-                responseAck(serverTransaction, Response.BAD_REQUEST);
+                try {
+                    responseAck((SIPRequest) evt.getRequest(), Response.BAD_REQUEST);
+                } catch (SipException | InvalidArgumentException | ParseException e) {
+                    logger.error("[命令发送失败] DeviceInfo应答消息 BAD_REQUEST: {}", e.getMessage());
+                }
                 return;
             }
             Element deviceIdElement = rootElement.element("DeviceID");
@@ -100,16 +93,14 @@ public class DeviceInfoResponseMessageHandler extends SIPRequestProcessorParent
             msg.setKey(key);
             msg.setData(device);
             deferredResultHolder.invokeAllResult(msg);
-            // 回复200 OK
-            responseAck(serverTransaction, Response.OK);
         } catch (DocumentException e) {
-            e.printStackTrace();
-        } catch (InvalidArgumentException e) {
-            e.printStackTrace();
-        } catch (ParseException e) {
-            e.printStackTrace();
-        } catch (SipException e) {
-            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
+        try {
+            // 回复200 OK
+            responseAck(request, Response.OK);
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.error("[命令发送失败] DeviceInfo应答消息 200: {}", e.getMessage());
         }
     }
 

+ 4 - 6
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceStatusResponseMessageHandler.java

@@ -1,10 +1,8 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
 
-import com.alibaba.fastjson.JSONObject;
-import com.genersoft.iot.vmp.common.VideoManagerConstants;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
-import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
@@ -13,6 +11,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.respons
 import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
 import com.genersoft.iot.vmp.service.IDeviceService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -25,7 +24,6 @@ import javax.sip.RequestEvent;
 import javax.sip.SipException;
 import javax.sip.message.Response;
 import java.text.ParseException;
-import java.util.Objects;
 
 @Component
 public class DeviceStatusResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
@@ -59,7 +57,7 @@ public class DeviceStatusResponseMessageHandler extends SIPRequestProcessorParen
         }
         // 回复200 OK
         try {
-            responseAck(getServerTransaction(evt), Response.OK);
+             responseAck((SIPRequest) evt.getRequest(), Response.OK);
         } catch (SipException | InvalidArgumentException | ParseException e) {
             logger.error("[命令发送失败] 国标级联 设备状态应答回复200OK: {}", e.getMessage());
         }
@@ -75,7 +73,7 @@ public class DeviceStatusResponseMessageHandler extends SIPRequestProcessorParen
         if ("ONLINE".equalsIgnoreCase(text.trim())) {
             deviceService.online(device);
         }else {
-            deviceService.offline(device.getDeviceId());
+            deviceService.offline(device.getDeviceId(), "设备状态查询结果:" + text.trim());
         }
         RequestMessage msg = new RequestMessage();
         msg.setKey(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + device.getDeviceId());

+ 16 - 9
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/MobilePositionResponseMessageHandler.java

@@ -1,6 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
@@ -13,6 +13,7 @@ import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.DateUtil;
 import com.genersoft.iot.vmp.utils.GpsUtil;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.DocumentException;
 import org.dom4j.Element;
 import org.slf4j.Logger;
@@ -21,11 +22,9 @@ import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
-import javax.sip.ServerTransaction;
 import javax.sip.SipException;
 import javax.sip.message.Response;
 import java.text.ParseException;
@@ -64,14 +63,17 @@ public class MobilePositionResponseMessageHandler extends SIPRequestProcessorPar
 
     @Override
     public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
-
-        ServerTransaction serverTransaction = getServerTransaction(evt);
+        SIPRequest request = (SIPRequest) evt.getRequest();
 
         try {
             rootElement = getRootElement(evt, device.getCharset());
             if (rootElement == null) {
                 logger.warn("[ 移动设备位置数据查询回复 ] content cannot be null, {}", evt.getRequest());
-                responseAck(serverTransaction, Response.BAD_REQUEST);
+                try {
+                    responseAck(request, Response.BAD_REQUEST);
+                } catch (SipException | InvalidArgumentException | ParseException e) {
+                    logger.error("[命令发送失败] 移动设备位置数据查询 BAD_REQUEST: {}", e.getMessage());
+                }
                 return;
             }
             MobilePosition mobilePosition = new MobilePosition();
@@ -133,9 +135,14 @@ public class MobilePositionResponseMessageHandler extends SIPRequestProcessorPar
             jsonObject.put("speed", mobilePosition.getSpeed());
             redisCatchStorage.sendMobilePositionMsg(jsonObject);
             //回复 200 OK
-            responseAck(serverTransaction, Response.OK);
-        } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
-            e.printStackTrace();
+            try {
+                responseAck(request, Response.OK);
+            } catch (SipException | InvalidArgumentException | ParseException e) {
+                logger.error("[命令发送失败] 移动设备位置数据查询 200: {}", e.getMessage());
+            }
+
+        } catch (DocumentException e) {
+            logger.error("未处理的异常 ", e);
         }
     }
 

+ 17 - 7
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/PresetQueryResponseMessageHandler.java

@@ -7,6 +7,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.DocumentException;
 import org.dom4j.Element;
 import org.slf4j.Logger;
@@ -17,7 +18,6 @@ import org.springframework.stereotype.Component;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
-import javax.sip.ServerTransaction;
 import javax.sip.SipException;
 import javax.sip.message.Response;
 import java.text.ParseException;
@@ -51,14 +51,18 @@ public class PresetQueryResponseMessageHandler extends SIPRequestProcessorParent
     @Override
     public void handForDevice(RequestEvent evt, Device device, Element element) {
 
-        ServerTransaction serverTransaction = getServerTransaction(evt);
+        SIPRequest request = (SIPRequest) evt.getRequest();
 
         try {
              Element rootElement = getRootElement(evt, device.getCharset());
 
             if (rootElement == null) {
                 logger.warn("[ 设备预置位查询应答 ] content cannot be null, {}", evt.getRequest());
-                responseAck(serverTransaction, Response.BAD_REQUEST);
+                try {
+                    responseAck(request, Response.BAD_REQUEST);
+                } catch (InvalidArgumentException | ParseException | SipException e) {
+                    logger.error("[命令发送失败] 设备预置位查询应答处理: {}", e.getMessage());
+                }
                 return;
             }
             Element presetListNumElement = rootElement.element("PresetList");
@@ -67,7 +71,11 @@ public class PresetQueryResponseMessageHandler extends SIPRequestProcessorParent
             String deviceId = getText(rootElement, "DeviceID");
             String key = DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + deviceId;
             if (snElement == null || presetListNumElement == null) {
-                responseAck(serverTransaction, Response.BAD_REQUEST, "xml error");
+                try {
+                    responseAck(request, Response.BAD_REQUEST, "xml error");
+                } catch (InvalidArgumentException | ParseException | SipException e) {
+                    logger.error("[命令发送失败] 设备预置位查询应答处理: {}", e.getMessage());
+                }
                 return;
             }
             int sumNum = Integer.parseInt(presetListNumElement.attributeValue("Num"));
@@ -94,11 +102,13 @@ public class PresetQueryResponseMessageHandler extends SIPRequestProcessorParent
             requestMessage.setKey(key);
             requestMessage.setData(presetQuerySipReqList);
             deferredResultHolder.invokeAllResult(requestMessage);
-            responseAck(serverTransaction, Response.OK);
+            try {
+                responseAck(request, Response.OK);
+            } catch (InvalidArgumentException | ParseException | SipException e) {
+                logger.error("[命令发送失败] 设备预置位查询应答处理: {}", e.getMessage());
+            }
         } catch (DocumentException e) {
             logger.error("[解析xml]失败: ", e);
-        } catch (InvalidArgumentException | ParseException | SipException e) {
-            logger.error("[命令发送失败] 设备预置位查询应答处理: {}", e.getMessage());
         }
     }
 

+ 96 - 98
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java

@@ -1,16 +1,17 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
 
+import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
-import com.genersoft.iot.vmp.gb28181.session.RecordDataCatch;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
 import com.genersoft.iot.vmp.utils.DateUtil;
-import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
-import org.dom4j.DocumentException;
+import com.genersoft.iot.vmp.utils.UJson;
+import com.genersoft.iot.vmp.utils.redis.RedisUtil;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -20,7 +21,6 @@ import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
@@ -28,9 +28,8 @@ import javax.sip.SipException;
 import javax.sip.message.Response;
 import java.text.ParseException;
 import java.util.*;
-import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.util.stream.Collectors;
 
 import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
 
@@ -45,13 +44,9 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
 
     private ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>();
 
-    private boolean taskQueueHandlerRun = false;
     @Autowired
     private ResponseMessageHandler responseMessageHandler;
 
-    @Autowired
-    private RecordDataCatch recordDataCatch;
-
     @Autowired
     private DeferredResultHolder deferredResultHolder;
 
@@ -62,6 +57,8 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
     @Autowired
     private ThreadPoolTaskExecutor taskExecutor;
 
+    private Long recordInfoTtl = 1800L;
+
     @Override
     public void afterPropertiesSet() throws Exception {
         responseMessageHandler.addHandler(cmdType, this);
@@ -69,96 +66,94 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
 
     @Override
     public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
-
-        // 回复200 OK
         try {
-            responseAck(getServerTransaction(evt), Response.OK);
-            taskQueue.offer(new HandlerCatchData(evt, device, rootElement));
-            if (!taskQueueHandlerRun) {
-                taskQueueHandlerRun = true;
-                taskExecutor.execute(()->{
-                    while (!taskQueue.isEmpty()) {
-                        try {
-                            HandlerCatchData take = taskQueue.poll();
-                            Element rootElementForCharset = getRootElement(take.getEvt(), take.getDevice().getCharset());
-                            if (rootElement == null) {
-                                logger.warn("[ 国标录像 ] content cannot be null, {}", evt.getRequest());
+            // 回复200 OK
+             responseAck((SIPRequest) evt.getRequest(), Response.OK);
+        }catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.error("[命令发送失败] 国标级联 国标录像: {}", e.getMessage());
+        }
+        taskExecutor.execute(()->{
+            try {
+
+                String sn = getText(rootElement, "SN");
+                String channelId = getText(rootElement, "DeviceID");
+                RecordInfo recordInfo = new RecordInfo();
+                recordInfo.setChannelId(channelId);
+                recordInfo.setDeviceId(device.getDeviceId());
+                recordInfo.setSn(sn);
+                recordInfo.setName(getText(rootElement, "Name"));
+                String sumNumStr = getText(rootElement, "SumNum");
+                int sumNum = 0;
+                if (!ObjectUtils.isEmpty(sumNumStr)) {
+                    sumNum = Integer.parseInt(sumNumStr);
+                }
+                recordInfo.setSumNum(sumNum);
+                Element recordListElement = rootElement.element("RecordList");
+                if (recordListElement == null || sumNum == 0) {
+                    logger.info("无录像数据");
+                    recordInfo.setCount(sumNum);
+                    eventPublisher.recordEndEventPush(recordInfo);
+                    releaseRequest(device.getDeviceId(), sn,recordInfo);
+                } else {
+                    Iterator<Element> recordListIterator = recordListElement.elementIterator();
+                    if (recordListIterator != null) {
+                        List<RecordItem> recordList = new ArrayList<>();
+                        // 遍历DeviceList
+                        while (recordListIterator.hasNext()) {
+                            Element itemRecord = recordListIterator.next();
+                            Element recordElement = itemRecord.element("DeviceID");
+                            if (recordElement == null) {
+                                logger.info("记录为空,下一个...");
                                 continue;
                             }
-                            String sn = getText(rootElementForCharset, "SN");
-                            String channelId = getText(rootElementForCharset, "DeviceID");
-                            RecordInfo recordInfo = new RecordInfo();
-                            recordInfo.setChannelId(channelId);
-                            recordInfo.setDeviceId(take.getDevice().getDeviceId());
-                            recordInfo.setSn(sn);
-                            recordInfo.setName(getText(rootElementForCharset, "Name"));
-                            String sumNumStr = getText(rootElementForCharset, "SumNum");
-                            int sumNum = 0;
-                            if (!ObjectUtils.isEmpty(sumNumStr)) {
-                                sumNum = Integer.parseInt(sumNumStr);
-                            }
-                            recordInfo.setSumNum(sumNum);
-                            Element recordListElement = rootElementForCharset.element("RecordList");
-                            if (recordListElement == null || sumNum == 0) {
-                                logger.info("无录像数据");
-                                eventPublisher.recordEndEventPush(recordInfo);
-                                recordDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, new ArrayList<>());
-                                releaseRequest(take.getDevice().getDeviceId(), sn);
-                            } else {
-                                Iterator<Element> recordListIterator = recordListElement.elementIterator();
-                                if (recordListIterator != null) {
-                                    List<RecordItem> recordList = new ArrayList<>();
-                                    // 遍历DeviceList
-                                    while (recordListIterator.hasNext()) {
-                                        Element itemRecord = recordListIterator.next();
-                                        Element recordElement = itemRecord.element("DeviceID");
-                                        if (recordElement == null) {
-                                            logger.info("记录为空,下一个...");
-                                            continue;
-                                        }
-                                        RecordItem record = new RecordItem();
-                                        record.setDeviceId(getText(itemRecord, "DeviceID"));
-                                        record.setName(getText(itemRecord, "Name"));
-                                        record.setFilePath(getText(itemRecord, "FilePath"));
-                                        record.setFileSize(getText(itemRecord, "FileSize"));
-                                        record.setAddress(getText(itemRecord, "Address"));
-
-                                        String startTimeStr = getText(itemRecord, "StartTime");
-                                        record.setStartTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTimeStr));
-
-                                        String endTimeStr = getText(itemRecord, "EndTime");
-                                        record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTimeStr));
-
-                                        record.setSecrecy(itemRecord.element("Secrecy") == null ? 0
-                                                : Integer.parseInt(getText(itemRecord, "Secrecy")));
-                                        record.setType(getText(itemRecord, "Type"));
-                                        record.setRecorderId(getText(itemRecord, "RecorderID"));
-                                        recordList.add(record);
-                                    }
-                                    recordInfo.setRecordList(recordList);
-                                    // 发送消息,如果是上级查询此录像,则会通过这里通知给上级
-                                    eventPublisher.recordEndEventPush(recordInfo);
-                                    int count = recordDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, recordList);
-                                    logger.info("[国标录像], {}->{}: {}/{}", take.getDevice().getDeviceId(), sn, count, sumNum);
-                                }
-
-                                if (recordDataCatch.isComplete(take.getDevice().getDeviceId(), sn)){
-                                    releaseRequest(take.getDevice().getDeviceId(), sn);
-                                }
-                            }
-                        } catch (DocumentException e) {
-                            logger.error("xml解析异常: ", e);
+                            RecordItem record = new RecordItem();
+                            record.setDeviceId(getText(itemRecord, "DeviceID"));
+                            record.setName(getText(itemRecord, "Name"));
+                            record.setFilePath(getText(itemRecord, "FilePath"));
+                            record.setFileSize(getText(itemRecord, "FileSize"));
+                            record.setAddress(getText(itemRecord, "Address"));
+
+                            String startTimeStr = getText(itemRecord, "StartTime");
+                            record.setStartTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTimeStr));
+
+                            String endTimeStr = getText(itemRecord, "EndTime");
+                            record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTimeStr));
+
+                            record.setSecrecy(itemRecord.element("Secrecy") == null ? 0
+                                    : Integer.parseInt(getText(itemRecord, "Secrecy")));
+                            record.setType(getText(itemRecord, "Type"));
+                            record.setRecorderId(getText(itemRecord, "RecorderID"));
+                            recordList.add(record);
+                        }
+                        Map<String, String> map = recordList.stream()
+                                .filter(record -> record.getDeviceId() != null)
+                                .collect(Collectors.toMap(record -> record.getStartTime()+ record.getEndTime(), UJson::writeJson));
+                        // 获取任务结果数据
+                        String resKey = VideoManagerConstants.REDIS_RECORD_INFO_RES_PRE + channelId + sn;
+                        RedisUtil.hmset(resKey, map, recordInfoTtl);
+                        String resCountKey = VideoManagerConstants.REDIS_RECORD_INFO_RES_COUNT_PRE + channelId + sn;
+                        long incr = RedisUtil.incr(resCountKey, map.size());
+                        RedisUtil.expire(resCountKey, recordInfoTtl);
+                        recordInfo.setRecordList(recordList);
+                        recordInfo.setCount(Math.toIntExact(incr));
+                        eventPublisher.recordEndEventPush(recordInfo);
+                        if (incr < sumNum) {
+                            return;
                         }
+                        // 已接收完成
+                        List<RecordItem> resList = RedisUtil.hmget(resKey).values().stream().map(e -> UJson.readJson(e.toString(), RecordItem.class)).collect(Collectors.toList());
+                        if (resList.size() < sumNum) {
+                            return;
+                        }
+                        recordInfo.setRecordList(resList);
+                        releaseRequest(device.getDeviceId(), sn,recordInfo);
                     }
-                    taskQueueHandlerRun = false;
-                });
+                }
+            } catch (Exception e) {
+                logger.error("[国标录像] 发现未处理的异常, \r\n{}", evt.getRequest());
+                logger.error("[国标录像] 异常内容: ", e);
             }
-
-        } catch (SipException | InvalidArgumentException | ParseException e) {
-            logger.error("[命令发送失败] 国标级联 国标录像: {}", e.getMessage());
-        } finally {
-            taskQueueHandlerRun = false;
-        }
+        });
     }
 
     @Override
@@ -166,15 +161,18 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
 
     }
 
-    public void releaseRequest(String deviceId, String sn){
+    public void releaseRequest(String deviceId, String sn,RecordInfo recordInfo){
         String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + sn;
         // 对数据进行排序
-        Collections.sort(recordDataCatch.getRecordInfo(deviceId, sn).getRecordList());
+        if(recordInfo!=null && recordInfo.getRecordList()!=null) {
+            Collections.sort(recordInfo.getRecordList());
+        }else{
+            recordInfo.setRecordList(new ArrayList<>());
+        }
 
         RequestMessage msg = new RequestMessage();
         msg.setKey(key);
-        msg.setData(recordDataCatch.getRecordInfo(deviceId, sn));
+        msg.setData(recordInfo);
         deferredResultHolder.invokeAllResult(msg);
-        recordDataCatch.remove(deviceId, sn);
     }
 }

+ 8 - 19
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java

@@ -1,10 +1,12 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.response.impl;
 
 import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.gb28181.SipLayer;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
+import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
 import com.genersoft.iot.vmp.gb28181.transmit.event.response.SIPResponseProcessorAbstract;
@@ -48,9 +50,6 @@ public class InviteResponseProcessor extends SIPResponseProcessorAbstract {
 	private final static Logger logger = LoggerFactory.getLogger(InviteResponseProcessor.class);
 	private final String method = "INVITE";
 
-	@Autowired
-	private VideoStreamSessionManager streamSession;
-
 	@Autowired
 	private SIPProcessorObserver sipProcessorObserver;
 
@@ -58,24 +57,14 @@ public class InviteResponseProcessor extends SIPResponseProcessorAbstract {
 	private SipConfig sipConfig;
 
 	@Autowired
-	private SipFactory sipFactory;
-
-	@Autowired
-	private GitUtil gitUtil;
-
-	@Autowired
-	private ISIPCommander commander;
+	private SipLayer sipLayer;
 
 	@Autowired
-	private IDeviceService deviceService;
+	private SIPSender sipSender;
 
 	@Autowired
 	private SIPRequestHeaderProvider headerProvider;
 
-	@Autowired
-	@Qualifier(value="udpSipProvider")
-	private SipProviderImpl udpSipProvider;
-
 
 	@Override
 	public void afterPropertiesSet() throws Exception {
@@ -117,12 +106,12 @@ public class InviteResponseProcessor extends SIPResponseProcessorAbstract {
 				} else {
 					sdp = SdpFactory.getInstance().createSessionDescription(contentString);
 				}
-				SipURI requestUri = sipFactory.createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), event.getRemoteIpAddress() + ":" + event.getRemotePort());
-				Request reqAck = headerProvider.createAckRequest(requestUri, response);
 
-				logger.info("[回复ack] {}-> {}:{} ", sdp.getOrigin().getUsername(), event.getRemoteIpAddress(), event.getRemotePort());
-				commander.transmitRequest(response.getTopmostViaHeader().getTransport(), reqAck, null, null);
+				SipURI requestUri = sipLayer.getSipFactory().createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), event.getRemoteIpAddress() + ":" + event.getRemotePort());
+				Request reqAck = headerProvider.createAckRequest(response.getLocalAddress().getHostAddress(), requestUri, response);
 
+				logger.info("[回复ack] {}-> {}:{} ", sdp.getOrigin().getUsername(), event.getRemoteIpAddress(), event.getRemotePort());
+				sipSender.transmitRequest( response.getLocalAddress().getHostAddress(), reqAck);
 			}
 		} catch (InvalidArgumentException | ParseException | SipException | SdpParseException e) {
 			logger.info("[点播回复ACK],异常:", e );

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java

@@ -100,7 +100,7 @@ public class RegisterResponseProcessor extends SIPResponseProcessorAbstract {
 			if (platformRegisterInfo.isRegister()) {
 				platformService.online(parentPlatform);
 			}else {
-				platformService.offline(parentPlatform);
+				platformService.offline(parentPlatform, false);
 			}
 
 			// 注册/注销成功移除缓存的信息

+ 70 - 13
src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java

@@ -1,12 +1,14 @@
 package com.genersoft.iot.vmp.gb28181.utils;
 
+import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
+import com.genersoft.iot.vmp.gb28181.bean.RemoteAddressInfo;
 import com.genersoft.iot.vmp.utils.GitUtil;
 import com.genersoft.iot.vmp.vmanager.gb28181.ptz.PtzController;
 import gov.nist.javax.sip.address.AddressImpl;
 import gov.nist.javax.sip.address.SipUri;
 import gov.nist.javax.sip.header.Subject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import gov.nist.javax.sip.message.SIPRequest;
+import org.springframework.util.ObjectUtils;
 
 import javax.sip.PeerUnavailableException;
 import javax.sip.SipFactory;
@@ -18,6 +20,7 @@ import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
+//import java.util.logging.Logger;
 
 
 /**
@@ -27,7 +30,7 @@ import java.util.UUID;
  * @createTime 2021年09月27日 15:12:00
  */
 public class SipUtils {
-    private final static Logger logger = LoggerFactory.getLogger(PtzController.class);
+//    private final static Logger logger = LoggerFactory.getLogger(PtzController.class);
     public static String getUserIdFromFromHeader(Request request) {
         FromHeader fromHeader = (FromHeader)request.getHeader(FromHeader.NAME);
         return getUserIdFromFromHeader(fromHeader);
@@ -87,14 +90,14 @@ public class SipUtils {
     public static String cmdString(int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed) {
         int cmdCode = 0;
         if (leftRight == 2) {
-            cmdCode|=0x01;		// 右移 0 03
+            cmdCode|=0x01;		// 右移
         } else if(leftRight == 1) {
-            cmdCode|=0x02;		// 左移 04
+            cmdCode|=0x02;		// 左移
         }
         if (upDown == 2) {
-            cmdCode|=0x04;		// 下移 05
+            cmdCode|=0x04;		// 下移
         } else if(upDown == 1) {
-            cmdCode|=0x08;		// 上移 06
+            cmdCode|=0x08;		// 上移
         }
         if (inOut == 2) {
             cmdCode |= 0x10;	// 放大
@@ -123,12 +126,66 @@ public class SipUtils {
     }
 
     /**
-     *
-     * @param direction 云台移动方向 1:上 2:下 3:左 4:右
-     * @param speed 移动速度
-     * @return
+     * 从请求中获取设备ip地址和端口号
+     * @param request 请求
+     * @param sipUseSourceIpAsRemoteAddress  false 从via中获取地址, true 直接获取远程地址
+     * @return 地址信息
      */
-    public static String cmdPtzString(int direction,int speed){
+    public static RemoteAddressInfo getRemoteAddressFromRequest(SIPRequest request, boolean sipUseSourceIpAsRemoteAddress) {
+
+        String remoteAddress;
+        int remotePort;
+        if (sipUseSourceIpAsRemoteAddress) {
+            remoteAddress = request.getRemoteAddress().getHostAddress();
+            remotePort = request.getRemotePort();
+        }else {
+            // 判断RPort是否改变,改变则说明路由nat信息变化,修改设备信息
+            // 获取到通信地址等信息
+            remoteAddress = request.getTopmostViaHeader().getReceived();
+            remotePort = request.getTopmostViaHeader().getRPort();
+            // 解析本地地址替代
+            if (ObjectUtils.isEmpty(remoteAddress) || remotePort == -1) {
+                remoteAddress = request.getTopmostViaHeader().getHost();
+                remotePort = request.getTopmostViaHeader().getPort();
+            }
+        }
+
+        return new RemoteAddressInfo(remoteAddress, remotePort);
+    }
+
+    public static DeviceChannel updateGps(DeviceChannel deviceChannel, String geoCoordSys) {
+        if (deviceChannel.getLongitude()*deviceChannel.getLatitude() > 0) {
+
+            if (geoCoordSys == null) {
+                geoCoordSys = "WGS84";
+            }
+            if ("WGS84".equals(geoCoordSys)) {
+                deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude());
+                deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude());
+                Double[] position = Coordtransform.WGS84ToGCJ02(deviceChannel.getLongitude(), deviceChannel.getLatitude());
+                deviceChannel.setLongitudeGcj02(position[0]);
+                deviceChannel.setLatitudeGcj02(position[1]);
+            }else if ("GCJ02".equals(geoCoordSys)) {
+                deviceChannel.setLongitudeGcj02(deviceChannel.getLongitude());
+                deviceChannel.setLatitudeGcj02(deviceChannel.getLatitude());
+                Double[] position = Coordtransform.GCJ02ToWGS84(deviceChannel.getLongitude(), deviceChannel.getLatitude());
+                deviceChannel.setLongitudeWgs84(position[0]);
+                deviceChannel.setLatitudeWgs84(position[1]);
+            }else {
+                deviceChannel.setLongitudeGcj02(0.00);
+                deviceChannel.setLatitudeGcj02(0.00);
+                deviceChannel.setLongitudeWgs84(0.00);
+                deviceChannel.setLatitudeWgs84(0.00);
+            }
+        }else {
+            deviceChannel.setLongitudeGcj02(deviceChannel.getLongitude());
+            deviceChannel.setLatitudeGcj02(deviceChannel.getLatitude());
+            deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude());
+            deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude());
+        }
+        return deviceChannel;
+    }
+	public static String cmdPtzString(int direction,int speed){
         int cmdCode = 0;
         if (direction == 4) {
             cmdCode|=0x01;		// 右移 0 03
@@ -158,7 +215,7 @@ public class SipUtils {
         strTmp = String.format("%02X", checkCode);
         builder.append(strTmp, 0, 2);
 
-        logger.info("cmd code --------{}",strTmp);
+//        logger.info("cmd code --------{}",strTmp);
         return builder.toString();
     }
 }

+ 85 - 4
src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java

@@ -1,10 +1,9 @@
 package com.genersoft.iot.vmp.gb28181.utils;
 
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
-import com.genersoft.iot.vmp.gb28181.bean.TreeType;
 import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
 import com.genersoft.iot.vmp.utils.DateUtil;
 import org.dom4j.Attribute;
@@ -15,12 +14,16 @@ import org.dom4j.io.SAXReader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
+import org.springframework.util.ReflectionUtils;
 
 import javax.sip.RequestEvent;
 import javax.sip.message.Request;
 import java.io.ByteArrayInputStream;
 import java.io.StringReader;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
 import java.util.*;
 
 /**
@@ -298,6 +301,10 @@ public class XmlUtil {
             }else {
                 deviceChannel.setParentId(parentId);
             }
+            // 兼容设备通道信息中自己为自己父节点的情况
+            if (deviceChannel.getParentId().equals(deviceChannel.getChannelId())) {
+                deviceChannel.setParentId(null);
+            }
         }
         deviceChannel.setBusinessGroupId(businessGroupID);
         if (channelType.equals(ChannelType.BusinessGroup) || channelType.equals(ChannelType.VirtualOrganization)) {
@@ -391,6 +398,7 @@ public class XmlUtil {
         } else {
             deviceChannel.setLatitude(0.00);
         }
+
         deviceChannel.setGpsTime(DateUtil.getNow());
 
 
@@ -405,6 +413,79 @@ public class XmlUtil {
         } else {
             deviceChannel.setPTZType(Integer.parseInt(XmlUtil.getText(itemDevice, "PTZType")));
         }
+
         return deviceChannel;
     }
+
+    /**
+     * 新增方法支持内部嵌套
+     *
+     * @param element xmlElement
+     * @param clazz 结果类
+     * @param <T> 泛型
+     * @return 结果对象
+     * @throws NoSuchMethodException
+     * @throws InvocationTargetException
+     * @throws InstantiationException
+     * @throws IllegalAccessException
+     */
+    public static <T> T loadElement(Element element, Class<T> clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
+        Field[] fields = clazz.getDeclaredFields();
+        T t = clazz.getDeclaredConstructor().newInstance();
+        for (Field field : fields) {
+            ReflectionUtils.makeAccessible(field);
+            MessageElement annotation = field.getAnnotation(MessageElement.class);
+            if (annotation == null) {
+                continue;
+            }
+            String value = annotation.value();
+            String subVal = annotation.subVal();
+            Element element1 = element.element(value);
+            if (element1 == null) {
+                continue;
+            }
+            if ("".equals(subVal)) {
+                // 无下级数据
+                Object fieldVal = element1.isTextOnly() ? element1.getText() : loadElement(element1, field.getType());
+                Object o = simpleTypeDeal(field.getType(), fieldVal);
+                ReflectionUtils.setField(field, t,  o);
+            } else {
+                // 存在下级数据
+                ArrayList<Object> list = new ArrayList<>();
+                Type genericType = field.getGenericType();
+                if (!(genericType instanceof ParameterizedType)) {
+                    continue;
+                }
+                Class<?> aClass = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
+                for (Element element2 : element1.elements(subVal)) {
+                    list.add(loadElement(element2, aClass));
+                }
+                ReflectionUtils.setField(field, t, list);
+            }
+        }
+        return t;
+    }
+
+    /**
+     * 简单类型处理
+     *
+     * @param tClass
+     * @param val
+     * @return
+     */
+    private static Object simpleTypeDeal(Class<?> tClass, Object val) {
+        if (tClass.equals(String.class)) {
+            return val.toString();
+        }
+        if (tClass.equals(Integer.class)) {
+            return Integer.valueOf(val.toString());
+        }
+        if (tClass.equals(Double.class)) {
+            return Double.valueOf(val.toString());
+        }
+        if (tClass.equals(Long.class)) {
+            return Long.valueOf(val.toString());
+        }
+        return val;
+    }
 }

+ 2 - 2
src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java

@@ -1,7 +1,7 @@
 package com.genersoft.iot.vmp.media.zlm;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import okhttp3.*;
 import okhttp3.logging.HttpLoggingInterceptor;

+ 657 - 733
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java

@@ -1,764 +1,688 @@
 package com.genersoft.iot.vmp.media.zlm;
 
-import java.text.ParseException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
+import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
+import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
-import com.genersoft.iot.vmp.media.zlm.dto.*;
+import com.genersoft.iot.vmp.media.zlm.dto.HookType;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
+import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
+import com.genersoft.iot.vmp.media.zlm.dto.hook.*;
 import com.genersoft.iot.vmp.service.*;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import com.genersoft.iot.vmp.vmanager.bean.DeferredResultEx;
+import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
+import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
+import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.util.ObjectUtils;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
-import org.springframework.web.bind.annotation.RestController;
-
-import com.alibaba.fastjson.JSONObject;
-import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.context.request.async.DeferredResult;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.sip.InvalidArgumentException;
 import javax.sip.SipException;
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
 
-/**    
+/**
  * @description:针对 ZLMediaServer的hook事件监听
  * @author: swwheihei
- * @date:   2020年5月8日 上午10:46:48     
+ * @date: 2020年5月8日 上午10:46:48
  */
 @RestController
 @RequestMapping("/index/hook")
 public class ZLMHttpHookListener {
 
-	private final static Logger logger = LoggerFactory.getLogger(ZLMHttpHookListener.class);
-
-	@Autowired
-	private SIPCommander cmder;
-
-	@Autowired
-	private SIPCommanderFroPlatform commanderFroPlatform;
-
-	@Autowired
-	private IPlayService playService;
-
-	@Autowired
-	private IVideoManagerStorage storager;
-
-	@Autowired
-	private IRedisCatchStorage redisCatchStorage;
-
-	@Autowired
-	private IDeviceService deviceService;
-
-	@Autowired
-	private IMediaServerService mediaServerService;
-
-	@Autowired
-	private IStreamProxyService streamProxyService;
-
-	@Autowired
-	private IStreamPushService streamPushService;
-
-	@Autowired
-	private IMediaService mediaService;
-
-	@Autowired
-	private EventPublisher eventPublisher;
-
-	 @Autowired
-	 private ZLMMediaListManager zlmMediaListManager;
-
-	@Autowired
-	private ZlmHttpHookSubscribe subscribe;
-
-	@Autowired
-	private UserSetting userSetting;
-
-	@Autowired
-	private IUserService userService;
-
-	@Autowired
-	private VideoStreamSessionManager sessionManager;
-
-	@Autowired
-	private AssistRESTfulUtils assistRESTfulUtils;
-
-	@Qualifier("taskExecutor")
-	@Autowired
-	private ThreadPoolTaskExecutor taskExecutor;
-
-	/**
-	 * 服务器定时上报时间,上报间隔可配置,默认10s上报一次
-	 *
-	 */
-	@ResponseBody
-	@PostMapping(value = "/on_server_keepalive", produces = "application/json;charset=UTF-8")
-	public JSONObject onServerKeepalive(@RequestBody JSONObject json){
-
-		logger.info("[ ZLM HOOK ]on_server_keepalive API调用,参数:" + json.toString());
-		String mediaServerId = json.getString("mediaServerId");
-		List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_keepalive);
-		if (subscribes != null  && subscribes.size() > 0) {
-			for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
-				subscribe.response(null, json);
-			}
-		}
-		mediaServerService.updateMediaServerKeepalive(mediaServerId, json.getJSONObject("data"));
-
-		JSONObject ret = new JSONObject();
-		ret.put("code", 0);
-		ret.put("msg", "success");
-
-		return ret;
-	}
-
-	/**
-	 * 流量统计事件,播放器或推流器断开时并且耗用流量超过特定阈值时会触发此事件,阈值通过配置文件general.flowThreshold配置;此事件对回复不敏感。
-	 *  
-	 */
-	@ResponseBody
-	@PostMapping(value = "/on_flow_report", produces = "application/json;charset=UTF-8")
-	public JSONObject onFlowReport(@RequestBody JSONObject json){
-		
-		if (logger.isDebugEnabled()) {
-			logger.debug("[ ZLM HOOK ]on_flow_report API调用,参数:" + json.toString());
-		}
-		JSONObject ret = new JSONObject();
-		ret.put("code", 0);
-		ret.put("msg", "success");
-		return ret;
-	}
-	
-	/**
-	 * 访问http文件服务器上hls之外的文件时触发。
-	 *  
-	 */
-	@ResponseBody
-	@PostMapping(value = "/on_http_access", produces = "application/json;charset=UTF-8")
-	public JSONObject onHttpAccess(@RequestBody JSONObject json){
-		
-		if (logger.isDebugEnabled()) {
-			logger.debug("[ ZLM HOOK ]on_http_access API 调用,参数:" + json.toString());
-		}
-		String mediaServerId = json.getString("mediaServerId");
-		JSONObject ret = new JSONObject();
-		ret.put("code", 0);
-		ret.put("err", "");
-		ret.put("path", "");
-		ret.put("second", 600);
-		return ret;
-	}
-	
-	/**
-	 * 播放器鉴权事件,rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件。
-	 *  
-	 */
-	@ResponseBody
-	@PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8")
-	public JSONObject onPlay(@RequestBody OnPlayHookParam param){
-
-		JSONObject json = (JSONObject)JSON.toJSON(param);
-
-		if (logger.isDebugEnabled()) {
-			logger.debug("[ ZLM HOOK ]on_play API调用,参数:" + JSON.toJSONString(param));
-		}
-		String mediaServerId = param.getMediaServerId();
-		ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_play, json);
-		if (subscribe != null ) {
-			MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
-			if (mediaInfo != null) {
-				subscribe.response(mediaInfo, json);
-			}
-		}
-		JSONObject ret = new JSONObject();
-		if (!"rtp".equals(param.getApp())) {
-			Map<String, String> paramMap = urlParamToMap(param.getParams());
-			StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
-			if (streamAuthorityInfo != null && streamAuthorityInfo.getCallId() != null && !streamAuthorityInfo.getCallId().equals(paramMap.get("callId"))) {
-				ret.put("code", 401);
-				ret.put("msg", "Unauthorized");
-				return ret;
-			}
-		}
-
-		ret.put("code", 0);
-		ret.put("msg", "success");
-		return ret;
-	}
-	
-	/**
-	 * rtsp/rtmp/rtp推流鉴权事件。
-	 *  
-	 */
-	@ResponseBody
-	@PostMapping(value = "/on_publish", produces = "application/json;charset=UTF-8")
-	public JSONObject onPublish(@RequestBody OnPublishHookParam param) {
-
-		JSONObject json = (JSONObject) JSON.toJSON(param);
-
-		logger.info("[ ZLM HOOK ]on_publish API调用,参数:" + json.toString());
-		JSONObject ret = new JSONObject();
-		String mediaServerId = json.getString("mediaServerId");
-		MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
-		if (!"rtp".equals(param.getApp())) {
-			// 推流鉴权
-			if (param.getParams() == null) {
-				logger.info("推流鉴权失败: 缺少不要参数:sign=md5(user表的pushKey)");
-				ret.put("code", 401);
-				ret.put("msg", "Unauthorized");
-				return ret;
-			}
-			Map<String, String> paramMap = urlParamToMap(param.getParams());
-			String sign = paramMap.get("sign");
-			if (sign == null) {
-				logger.info("推流鉴权失败: 缺少不要参数:sign=md5(user表的pushKey)");
-				ret.put("code", 401);
-				ret.put("msg", "Unauthorized");
-				return ret;
-			}
-			// 推流自定义播放鉴权码
-			String callId = paramMap.get("callId");
-			// 鉴权配置
-			boolean hasAuthority = userService.checkPushAuthority(callId, sign);
-			if (!hasAuthority) {
-				logger.info("推流鉴权失败: sign 无权限: callId={}. sign={}", callId, sign);
-				ret.put("code", 401);
-				ret.put("msg", "Unauthorized");
-				return ret;
-			}
-			StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param);
-			streamAuthorityInfo.setCallId(callId);
-			streamAuthorityInfo.setSign(sign);
-			// 鉴权通过
-			redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo);
-			// 通知assist新的callId
-			if (mediaInfo != null && mediaInfo.getRecordAssistPort() > 0) {
-				taskExecutor.execute(()->{
-					assistRESTfulUtils.addStreamCallInfo(mediaInfo, param.getApp(), param.getStream(), callId, null);
-				});
-			}
-		}else {
-			zlmMediaListManager.sendStreamEvent(param.getApp(),param.getStream(), param.getMediaServerId());
-		}
-
-		ret.put("code", 0);
-		ret.put("msg", "success");
-		ret.put("enable_hls", true);
-		if (!"rtp".equals(param.getApp())) {
-			ret.put("enable_audio", true);
-		}
-
-
-		ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_publish, json);
-		if (subscribe != null) {
-			if (mediaInfo != null) {
-				subscribe.response(mediaInfo, json);
-			}else {
-				ret.put("code", 1);
-				ret.put("msg", "zlm not register");
-			}
-		}
-
-		if ("rtp".equals(param.getApp())) {
-			ret.put("enable_mp4", userSetting.getRecordSip());
-		}else {
-			ret.put("enable_mp4", userSetting.isRecordPushLive());
-		}
-		List<SsrcTransaction> ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, param.getStream());
-		if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) {
-			String deviceId = ssrcTransactionForAll.get(0).getDeviceId();
-			String channelId = ssrcTransactionForAll.get(0).getChannelId();
-			DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
-			if (deviceChannel != null) {
-				ret.put("enable_audio", deviceChannel.isHasAudio());
-			}
-			// 如果是录像下载就设置视频间隔十秒
-			if (ssrcTransactionForAll.get(0).getType() == VideoStreamSessionManager.SessionType.download) {
-				ret.put("mp4_max_second", 10);
-				ret.put("enable_mp4", true);
-				ret.put("enable_audio", true);
-
-			}
-		}
-		return ret;
-	}
-
-
-
-	/**
-	 * 录制mp4完成后通知事件;此事件对回复不敏感。
-	 *  
-	 */
-	@ResponseBody
-	@PostMapping(value = "/on_record_mp4", produces = "application/json;charset=UTF-8")
-	public JSONObject onRecordMp4(@RequestBody JSONObject json){
-		
-		if (logger.isDebugEnabled()) {
-			logger.debug("[ ZLM HOOK ]on_record_mp4 API调用,参数:" + json.toString());
-		}
-		String mediaServerId = json.getString("mediaServerId");
-		JSONObject ret = new JSONObject();
-		ret.put("code", 0);
-		ret.put("msg", "success");
-		return ret;
-	}
-	/**
-	 * 录制hls完成后通知事件;此事件对回复不敏感。
-	 *
-	 */
-	@ResponseBody
-	@PostMapping(value = "/on_record_ts", produces = "application/json;charset=UTF-8")
-	public JSONObject onRecordTs(@RequestBody JSONObject json){
-
-		if (logger.isDebugEnabled()) {
-			logger.debug("[ ZLM HOOK ]on_record_ts API调用,参数:" + json.toString());
-		}
-		String mediaServerId = json.getString("mediaServerId");
-		JSONObject ret = new JSONObject();
-		ret.put("code", 0);
-		ret.put("msg", "success");
-		return ret;
-	}
-	
-	/**
-	 * rtsp专用的鉴权事件,先触发on_rtsp_realm事件然后才会触发on_rtsp_auth事件。
-	 *  
-	 */
-	@ResponseBody
-	@PostMapping(value = "/on_rtsp_realm", produces = "application/json;charset=UTF-8")
-	public JSONObject onRtspRealm(@RequestBody JSONObject json){
-		
-		if (logger.isDebugEnabled()) {
-			logger.debug("[ ZLM HOOK ]on_rtsp_realm API调用,参数:" + json.toString());
-		}
-		String mediaServerId = json.getString("mediaServerId");
-		JSONObject ret = new JSONObject();
-		ret.put("code", 0);
-		ret.put("realm", "");
-		return ret;
-	}
-	
-	
-	/**
-	 * 该rtsp流是否开启rtsp专用方式的鉴权事件,开启后才会触发on_rtsp_auth事件。需要指出的是rtsp也支持url参数鉴权,它支持两种方式鉴权。
-	 *  
-	 */
-	@ResponseBody
-	@PostMapping(value = "/on_rtsp_auth", produces = "application/json;charset=UTF-8")
-	public JSONObject onRtspAuth(@RequestBody JSONObject json){
-		
-		if (logger.isDebugEnabled()) {
-			logger.debug("[ ZLM HOOK ]on_rtsp_auth API调用,参数:" + json.toString());
-		}
-		String mediaServerId = json.getString("mediaServerId");
-		JSONObject ret = new JSONObject();
-		ret.put("code", 0);
-		ret.put("encrypted", false);
-		ret.put("passwd", "test");
-		return ret;
-	}
-	
-	/**
-	 * shell登录鉴权,ZLMediaKit提供简单的telnet调试方式,使用telnet 127.0.0.1 9000能进入MediaServer进程的shell界面。
-	 *  
-	 */
-	@ResponseBody
-	@PostMapping(value = "/on_shell_login", produces = "application/json;charset=UTF-8")
-	public JSONObject onShellLogin(@RequestBody JSONObject json){
-		
-		if (logger.isDebugEnabled()) {
-			logger.debug("[ ZLM HOOK ]on_shell_login API调用,参数:" + json.toString());
-		}
-		String mediaServerId = json.getString("mediaServerId");
-		ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_shell_login, json);
-		if (subscribe != null ) {
-			MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
-			if (mediaInfo != null) {
-				subscribe.response(mediaInfo, json);
-			}
-
-		}
-
-		JSONObject ret = new JSONObject();
-		ret.put("code", 0);
-		ret.put("msg", "success");
-		return ret;
-	}
-	
-	/**
-	 * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。
-	 *  
-	 */
-	@ResponseBody
-	@PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8")
-	public JSONObject onStreamChanged(@RequestBody MediaItem item){
-
-		logger.info("[ ZLM HOOK ]on_stream_changed API调用,参数:" + JSONObject.toJSONString(item));
-		String mediaServerId = item.getMediaServerId();
-		JSONObject json = (JSONObject) JSON.toJSON(item);
-		ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json);
-		if (subscribe != null ) {
-			MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
-			if (mediaInfo != null) {
-				subscribe.response(mediaInfo, json);
-			}
-		}
-		// 流消失移除redis play
-		String app = item.getApp();
-		String stream = item.getStream();
-		String schema = item.getSchema();
-		List<MediaItem.MediaTrack> tracks = item.getTracks();
-		boolean regist = item.isRegist();
-		if (regist) {
-			if (item.getOriginType() == OriginType.RTMP_PUSH.ordinal()
-					|| item.getOriginType() == OriginType.RTSP_PUSH.ordinal()
-					|| item.getOriginType() == OriginType.RTC_PUSH.ordinal()) {
-
-				StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(app, stream);
-				if (streamAuthorityInfo == null) {
-					streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(item);
-				}else {
-					streamAuthorityInfo.setOriginType(item.getOriginType());
-					streamAuthorityInfo.setOriginTypeStr(item.getOriginTypeStr());
-				}
-				redisCatchStorage.updateStreamAuthorityInfo(app, stream, streamAuthorityInfo);
-			}
-		}else {
-			redisCatchStorage.removeStreamAuthorityInfo(app, stream);
-		}
-
-		if ("rtsp".equals(schema)){
-			logger.info("on_stream_changed:注册->{}, app->{}, stream->{}", regist, app, stream);
-			if (regist) {
-				mediaServerService.addCount(mediaServerId);
-			}else {
-				mediaServerService.removeCount(mediaServerId);
-			}
-			if (item.getOriginType() == OriginType.PULL.ordinal()
-					|| item.getOriginType() == OriginType.FFMPEG_PULL.ordinal()) {
-				// 设置拉流代理上线/离线
-				streamProxyService.updateStatus(regist, app, stream);
-			}
-			if ("rtp".equals(app) && !regist ) {
-				StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(stream);
-				if (streamInfo!=null){
-					redisCatchStorage.stopPlay(streamInfo);
-					storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
-				}else{
-					streamInfo = redisCatchStorage.queryPlayback(null, null, stream, null);
-					if (streamInfo != null) {
-						redisCatchStorage.stopPlayback(streamInfo.getDeviceID(), streamInfo.getChannelId(),
-								streamInfo.getStream(), null);
-					}
-				}
-			}else {
-				if (!"rtp".equals(app)){
-					String type = OriginType.values()[item.getOriginType()].getType();
-					MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
-
-					if (mediaServerItem != null){
-						if (regist) {
-							StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(app, stream);
-							String callId = null;
-							if (streamAuthorityInfo != null) {
-								callId = streamAuthorityInfo.getCallId();
-							}
-							StreamInfo streamInfoByAppAndStream = mediaService.getStreamInfoByAppAndStream(mediaServerItem,
-									app, stream, tracks, callId);
-							item.setStreamInfo(streamInfoByAppAndStream);
-							redisCatchStorage.addStream(mediaServerItem, type, app, stream, item);
-							if (item.getOriginType() == OriginType.RTSP_PUSH.ordinal()
-									|| item.getOriginType() == OriginType.RTMP_PUSH.ordinal()
-									|| item.getOriginType() == OriginType.RTC_PUSH.ordinal() ) {
-								item.setSeverId(userSetting.getServerId());
-								zlmMediaListManager.addPush(item);
-							}
-						}else {
-							// 兼容流注销时类型从redis记录获取
-							MediaItem mediaItem = redisCatchStorage.getStreamInfo(app, stream, mediaServerId);
-							if (mediaItem != null) {
-								type = OriginType.values()[mediaItem.getOriginType()].getType();
-								redisCatchStorage.removeStream(mediaServerItem.getId(), type, app, stream);
-							}
-							GbStream gbStream = storager.getGbStream(app, stream);
-							if (gbStream != null) {
-//								eventPublisher.catalogEventPublishForStream(null, gbStream, CatalogEvent.OFF);
-							}
-							zlmMediaListManager.removeMedia(app, stream);
-						}
-						if (type != null) {
-							// 发送流变化redis消息
-							JSONObject jsonObject = new JSONObject();
-							jsonObject.put("serverId", userSetting.getServerId());
-							jsonObject.put("app", app);
-							jsonObject.put("stream", stream);
-							jsonObject.put("register", regist);
-							jsonObject.put("mediaServerId", mediaServerId);
-							redisCatchStorage.sendStreamChangeMsg(type, jsonObject);
-						}
-					}
-				}
-			}
-			if (!regist) {
-				List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(stream);
-				if (sendRtpItems.size() > 0) {
-					for (SendRtpItem sendRtpItem : sendRtpItems) {
-						if (sendRtpItem.getApp().equals(app)) {
-							String platformId = sendRtpItem.getPlatformId();
-							ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);
-							Device device = deviceService.queryDevice(platformId);
-
-							try {
-								if (platform != null) {
-									commanderFroPlatform.streamByeCmd(platform, sendRtpItem);
-								}else {
-									cmder.streamByeCmd(device, sendRtpItem.getChannelId(), stream, sendRtpItem.getCallId());
-								}
-							} catch (SipException | InvalidArgumentException | ParseException | SsrcTransactionNotFoundException e) {
-								logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
-							}
-						}
-					}
-				}
-			}
-		}
-
-		JSONObject ret = new JSONObject();
-		ret.put("code", 0);
-		ret.put("msg", "success");
-		return ret;
-	}
-	
-	/**
-	 * 流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流。
-	 *  
-	 */
-	@ResponseBody
-	@PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8")
-	public JSONObject onStreamNoneReader(@RequestBody JSONObject json){
-
-		logger.info("[ ZLM HOOK ]on_stream_none_reader API调用,参数:" + json.toString());
-		String mediaServerId = json.getString("mediaServerId");
-		String streamId = json.getString("stream");
-		String app = json.getString("app");
-		JSONObject ret = new JSONObject();
-		ret.put("code", 0);
-		if ("rtp".equals(app)){
-			ret.put("close", true);
-			StreamInfo streamInfoForPlayCatch = redisCatchStorage.queryPlayByStreamId(streamId);
-			if (streamInfoForPlayCatch != null) {
-				// 收到无人观看说明流也没有在往上级推送
-				if (redisCatchStorage.isChannelSendingRTP(streamInfoForPlayCatch.getChannelId())) {
-					List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByChnnelId(streamInfoForPlayCatch.getChannelId());
-					if (sendRtpItems.size() > 0) {
-						for (SendRtpItem sendRtpItem : sendRtpItems) {
-							ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
-							try {
-								commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
-							} catch (SipException | InvalidArgumentException | ParseException e) {
-								logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
-							}
-							redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
-									sendRtpItem.getCallId(), sendRtpItem.getStreamId());
-						}
-					}
-				}
-				Device device = deviceService.queryDevice(streamInfoForPlayCatch.getDeviceID());
-				if (device != null) {
-					try {
-						cmder.streamByeCmd(device, streamInfoForPlayCatch.getChannelId(),
-								streamInfoForPlayCatch.getStream(), null);
-					} catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
-						logger.error("[无人观看]点播, 发送BYE失败 {}", e.getMessage());
-					}
-				}
-
-				redisCatchStorage.stopPlay(streamInfoForPlayCatch);
-				storager.stopPlay(streamInfoForPlayCatch.getDeviceID(), streamInfoForPlayCatch.getChannelId());
-			}else{
-				StreamInfo streamInfoForPlayBackCatch = redisCatchStorage.queryPlayback(null, null, streamId, null);
-				if (streamInfoForPlayBackCatch != null ) {
-					if (streamInfoForPlayBackCatch.isPause()) {
-						ret.put("close", false);
-					}else {
-						Device device = deviceService.queryDevice(streamInfoForPlayBackCatch.getDeviceID());
-						if (device != null) {
-							try {
-								cmder.streamByeCmd(device,streamInfoForPlayBackCatch.getChannelId(),
-										streamInfoForPlayBackCatch.getStream(), null);
-							} catch (InvalidArgumentException | ParseException | SipException |
-									 SsrcTransactionNotFoundException e) {
-								logger.error("[无人观看]回放, 发送BYE失败 {}", e.getMessage());
-							}
-						}
-						redisCatchStorage.stopPlayback(streamInfoForPlayBackCatch.getDeviceID(),
-								streamInfoForPlayBackCatch.getChannelId(), streamInfoForPlayBackCatch.getStream(), null);
-					}
-
-				}else {
-					StreamInfo streamInfoForDownload = redisCatchStorage.queryDownload(null, null, streamId, null);
-					// 进行录像下载时无人观看不断流
-					if (streamInfoForDownload != null) {
-						ret.put("close", false);
-					}
-				}
-			}
-			MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
-			if (mediaServerItem != null && mediaServerItem.getStreamNoneReaderDelayMS() == -1) {
-				ret.put("close", false);
-			}
-			return ret;
-		}else {
-			StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(app, streamId);
-			if (streamProxyItem != null ) {
-				if (streamProxyItem.isEnable_remove_none_reader()) {
-					// 无人观看自动移除
-					ret.put("close", true);
-					streamProxyService.del(app, streamId);
-					String url = streamProxyItem.getUrl() != null?streamProxyItem.getUrl():streamProxyItem.getSrc_url();
-					logger.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除",  app, streamId, url);
-				}else if (streamProxyItem.isEnable_disable_none_reader()) {
-					// 无人观看停用
-					ret.put("close", true);
-				}else {
-					ret.put("close", false);
-				}
-			}
-			return ret;
-		}
-	}
-	
-	/**
-	 * 流未找到事件,用户可以在此事件触发时,立即去拉流,这样可以实现按需拉流;此事件对回复不敏感。
-	 *  
-	 */
-	@ResponseBody
-	@PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8")
-	public JSONObject onStreamNotFound(@RequestBody JSONObject json){
-		if (logger.isDebugEnabled()) {
-			logger.debug("[ ZLM HOOK ]on_stream_not_found API调用,参数:" + json.toString());
-		}
-		String mediaServerId = json.getString("mediaServerId");
-		MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
-		if (userSetting.isAutoApplyPlay() && mediaInfo != null && mediaInfo.isRtpEnable()) {
-			String app = json.getString("app");
-			String streamId = json.getString("stream");
-			if ("rtp".equals(app)) {
-				String[] s = streamId.split("_");
-				if (s.length == 2) {
-					String deviceId = s[0];
-					String channelId = s[1];
-					Device device = redisCatchStorage.getDevice(deviceId);
-					if (device != null) {
-						playService.play(mediaInfo,deviceId, channelId,1, null, null, null);
-					}
-				}
-			}
-		}
-
-		JSONObject ret = new JSONObject();
-		ret.put("code", 0);
-		ret.put("msg", "success");
-		return ret;
-	}
-	
-	/**
-	 * 服务器启动事件,可以用于监听服务器崩溃重启;此事件对回复不敏感。
-	 *  
-	 */
-	@ResponseBody
-	@PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8")
-	public JSONObject onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject){
-
-		if (logger.isDebugEnabled()) {
-			logger.debug("[ ZLM HOOK ]on_server_started API调用,参数:" + jsonObject.toString());
-		}
-		String remoteAddr = request.getRemoteAddr();
-		jsonObject.put("ip", remoteAddr);
-		List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_started);
-		if (subscribes != null  && subscribes.size() > 0) {
-			for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
-				subscribe.response(null, jsonObject);
-			}
-		}
-
-		ZLMServerConfig zlmServerConfig = JSONObject.toJavaObject(jsonObject, ZLMServerConfig.class);
-		if (zlmServerConfig !=null ) {
-			mediaServerService.zlmServerOnline(zlmServerConfig);
-		}
-		JSONObject ret = new JSONObject();
-		ret.put("code", 0);
-		ret.put("msg", "success");
-		return ret;
-	}
-
-	/**
-	 * 发送rtp(startSendRtp)被动关闭时回调
-	 */
-	@ResponseBody
-	@PostMapping(value = "/on_send_rtp_stopped", produces = "application/json;charset=UTF-8")
-	public JSONObject onSendRtpStopped(HttpServletRequest request, @RequestBody JSONObject jsonObject){
-
-		logger.info("[ ZLM HOOK ]on_send_rtp_stopped API调用,参数:" + jsonObject);
-
-		JSONObject ret = new JSONObject();
-		ret.put("code", 0);
-		ret.put("msg", "success");
-
-		// 查找对应的上级推流,发送停止
-		String app = jsonObject.getString("app");
-		if (!"rtp".equals(app)) {
-			return ret;
-		}
-		String stream = jsonObject.getString("stream");
-		List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(stream);
-		if (sendRtpItems.size() > 0) {
-			for (SendRtpItem sendRtpItem : sendRtpItems) {
-				ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
-				try {
-					commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
-				} catch (SipException | InvalidArgumentException | ParseException e) {
-					logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
-				}
-				redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
-						sendRtpItem.getCallId(), sendRtpItem.getStreamId());
-			}
-		}
-
-
-		return ret;
-	}
-
-	private Map<String, String> urlParamToMap(String params) {
-		HashMap<String, String> map = new HashMap<>();
-		if (ObjectUtils.isEmpty(params)) {
-			return map;
-		}
-		String[] paramsArray = params.split("&");
-		if (paramsArray.length == 0) {
-			return map;
-		}
-		for (String param : paramsArray) {
-			String[] paramArray = param.split("=");
-			if (paramArray.length == 2){
-				map.put(paramArray[0], paramArray[1]);
-			}
-		}
-		return map;
-	}
+    private final static Logger logger = LoggerFactory.getLogger(ZLMHttpHookListener.class);
+
+    @Autowired
+    private SIPCommander cmder;
+
+    @Autowired
+    private SIPCommanderFroPlatform commanderFroPlatform;
+
+    @Autowired
+    private IPlayService playService;
+
+    @Autowired
+    private IVideoManagerStorage storager;
+
+    @Autowired
+    private IRedisCatchStorage redisCatchStorage;
+
+    @Autowired
+    private IDeviceService deviceService;
+
+    @Autowired
+    private IMediaServerService mediaServerService;
+
+    @Autowired
+    private IStreamProxyService streamProxyService;
+
+    @Autowired
+    private DeferredResultHolder resultHolder;
+
+    @Autowired
+    private IMediaService mediaService;
+
+    @Autowired
+    private EventPublisher eventPublisher;
+
+    @Autowired
+    private ZLMMediaListManager zlmMediaListManager;
+
+    @Autowired
+    private ZlmHttpHookSubscribe subscribe;
+
+    @Autowired
+    private UserSetting userSetting;
+
+    @Autowired
+    private IUserService userService;
+
+    @Autowired
+    private VideoStreamSessionManager sessionManager;
+
+    @Autowired
+    private AssistRESTfulUtils assistRESTfulUtils;
+
+    @Qualifier("taskExecutor")
+    @Autowired
+    private ThreadPoolTaskExecutor taskExecutor;
+
+    /**
+     * 服务器定时上报时间,上报间隔可配置,默认10s上报一次
+     */
+    @ResponseBody
+    
+    @PostMapping(value = "/on_server_keepalive", produces = "application/json;charset=UTF-8")
+    public HookResult onServerKeepalive(@RequestBody OnServerKeepaliveHookParam param) {
+
+        logger.info("[ZLM HOOK] 收到zlm心跳:" + param.getMediaServerId());
+
+        taskExecutor.execute(() -> {
+            List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_keepalive);
+            JSONObject json = (JSONObject) JSON.toJSON(param);
+            if (subscribes != null && subscribes.size() > 0) {
+                for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
+                    subscribe.response(null, json);
+                }
+            }
+        });
+        mediaServerService.updateMediaServerKeepalive(param.getMediaServerId(), param.getData());
+
+        return HookResult.SUCCESS();
+    }
+
+    /**
+     * 播放器鉴权事件,rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件。
+     */
+    @ResponseBody
+    
+    @PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8")
+    public HookResult onPlay(@RequestBody OnPlayHookParam param) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("[ZLM HOOK] 播放鉴权:{}->{}" + param.getMediaServerId(), param);
+        }
+        String mediaServerId = param.getMediaServerId();
+
+        taskExecutor.execute(() -> {
+            JSONObject json = (JSONObject) JSON.toJSON(param);
+            ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_play, json);
+            if (subscribe != null) {
+                MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
+                if (mediaInfo != null) {
+                    subscribe.response(mediaInfo, json);
+                }
+            }
+        });
+        if (!"rtp".equals(param.getApp())) {
+            Map<String, String> paramMap = urlParamToMap(param.getParams());
+            StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
+            if (streamAuthorityInfo != null && streamAuthorityInfo.getCallId() != null && !streamAuthorityInfo.getCallId().equals(paramMap.get("callId"))) {
+                return new HookResult(401, "Unauthorized");
+            }
+        }
+
+        return HookResult.SUCCESS();
+    }
+
+    /**
+     * rtsp/rtmp/rtp推流鉴权事件。
+     */
+    @ResponseBody
+    @PostMapping(value = "/on_publish", produces = "application/json;charset=UTF-8")
+    public HookResultForOnPublish onPublish(@RequestBody OnPublishHookParam param) {
+
+        JSONObject json = (JSONObject) JSON.toJSON(param);
+
+        logger.info("[ZLM HOOK]推流鉴权:{}->{}", param.getMediaServerId(), param);
+
+        String mediaServerId = json.getString("mediaServerId");
+        MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
+
+        if (!"rtp".equals(param.getApp())) {
+            if (userSetting.getPushAuthority()) {
+                // 推流鉴权
+                if (param.getParams() == null) {
+                    logger.info("推流鉴权失败: 缺少不要参数:sign=md5(user表的pushKey)");
+                    return new HookResultForOnPublish(401, "Unauthorized");
+                }
+                Map<String, String> paramMap = urlParamToMap(param.getParams());
+                String sign = paramMap.get("sign");
+                if (sign == null) {
+                    logger.info("推流鉴权失败: 缺少不要参数:sign=md5(user表的pushKey)");
+                    return new HookResultForOnPublish(401, "Unauthorized");
+                }
+                // 推流自定义播放鉴权码
+                String callId = paramMap.get("callId");
+                // 鉴权配置
+                boolean hasAuthority = userService.checkPushAuthority(callId, sign);
+                if (!hasAuthority) {
+                    logger.info("推流鉴权失败: sign 无权限: callId={}. sign={}", callId, sign);
+                    return new HookResultForOnPublish(401, "Unauthorized");
+                }
+                StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param);
+                streamAuthorityInfo.setCallId(callId);
+                streamAuthorityInfo.setSign(sign);
+                // 鉴权通过
+                redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo);
+                // 通知assist新的callId
+                if (mediaInfo != null && mediaInfo.getRecordAssistPort() > 0) {
+                    taskExecutor.execute(() -> {
+                        assistRESTfulUtils.addStreamCallInfo(mediaInfo, param.getApp(), param.getStream(), callId, null);
+                    });
+                }
+            }
+        } else {
+            zlmMediaListManager.sendStreamEvent(param.getApp(), param.getStream(), param.getMediaServerId());
+        }
+
+
+        HookResultForOnPublish result = HookResultForOnPublish.SUCCESS();
+        if (!"rtp".equals(param.getApp())) {
+            result.setEnable_audio(true);
+        }
+
+        taskExecutor.execute(() -> {
+            ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_publish, json);
+            if (subscribe != null) {
+                if (mediaInfo != null) {
+                    subscribe.response(mediaInfo, json);
+                } else {
+                    new HookResultForOnPublish(1, "zlm not register");
+                }
+            }
+        });
+
+        if ("rtp".equals(param.getApp())) {
+            result.setEnable_mp4(userSetting.getRecordSip());
+        } else {
+            result.setEnable_mp4(userSetting.isRecordPushLive());
+        }
+        List<SsrcTransaction> ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, param.getStream());
+        if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) {
+            String deviceId = ssrcTransactionForAll.get(0).getDeviceId();
+            String channelId = ssrcTransactionForAll.get(0).getChannelId();
+            DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
+            if (deviceChannel != null) {
+                result.setEnable_audio(deviceChannel.isHasAudio());
+            }
+            // 如果是录像下载就设置视频间隔十秒
+            if (ssrcTransactionForAll.get(0).getType() == VideoStreamSessionManager.SessionType.download) {
+                result.setMp4_max_second(10);
+                result.setEnable_audio(true);
+                result.setEnable_mp4(true);
+            }
+        }
+        return result;
+    }
+
+
+    /**
+     * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。
+     */
+    @ResponseBody
+    @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8")
+    public HookResult onStreamChanged(@RequestBody OnStreamChangedHookParam param) {
+
+        if (param.isRegist()) {
+            logger.info("[ZLM HOOK] 流注册, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
+        } else {
+            logger.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
+        }
+
+
+        JSONObject json = (JSONObject) JSON.toJSON(param);
+        taskExecutor.execute(() -> {
+            ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json);
+            if (subscribe != null) {
+                MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId());
+                if (mediaInfo != null) {
+                    subscribe.response(mediaInfo, json);
+                }
+            }
+
+            List<OnStreamChangedHookParam.MediaTrack> tracks = param.getTracks();
+            // TODO 重构此处逻辑
+
+            if (param.isRegist()) {
+                // 处理流注册的鉴权信息
+                if (param.getOriginType() == OriginType.RTMP_PUSH.ordinal()
+                        || param.getOriginType() == OriginType.RTSP_PUSH.ordinal()
+                        || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) {
+
+                    StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
+                    if (streamAuthorityInfo == null) {
+                        streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param);
+                    } else {
+                        streamAuthorityInfo.setOriginType(param.getOriginType());
+                        streamAuthorityInfo.setOriginTypeStr(param.getOriginTypeStr());
+                    }
+                    redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo);
+                }
+            } else {
+                redisCatchStorage.removeStreamAuthorityInfo(param.getApp(), param.getStream());
+            }
+
+            if ("rtsp".equals(param.getSchema())) {
+                // 更新流媒体负载信息
+                if (param.isRegist()) {
+                    mediaServerService.addCount(param.getMediaServerId());
+                } else {
+                    mediaServerService.removeCount(param.getMediaServerId());
+                }
+                // 设置拉流代理上线/离线
+                streamProxyService.updateStatus(param.isRegist(), param.getApp(), param.getStream());
+
+                if ("rtp".equals(param.getApp()) && !param.isRegist()) {
+                    StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(param.getStream());
+                    if (streamInfo != null) {
+                        redisCatchStorage.stopPlay(streamInfo);
+                        storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
+                    } else {
+                        streamInfo = redisCatchStorage.queryPlayback(null, null, param.getStream(), null);
+                        if (streamInfo != null) {
+                            redisCatchStorage.stopPlayback(streamInfo.getDeviceID(), streamInfo.getChannelId(),
+                                    streamInfo.getStream(), null);
+                        }
+                    }
+                } else {
+                    if (!"rtp".equals(param.getApp())) {
+                        String type = OriginType.values()[param.getOriginType()].getType();
+                        MediaServerItem mediaServerItem = mediaServerService.getOne(param.getMediaServerId());
+
+                        if (mediaServerItem != null) {
+                            if (param.isRegist()) {
+                                StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
+                                String callId = null;
+                                if (streamAuthorityInfo != null) {
+                                    callId = streamAuthorityInfo.getCallId();
+                                }
+                                StreamInfo streamInfoByAppAndStream = mediaService.getStreamInfoByAppAndStream(mediaServerItem,
+                                        param.getApp(), param.getStream(), tracks, callId);
+                                param.setStreamInfo(new StreamContent(streamInfoByAppAndStream));
+                                redisCatchStorage.addStream(mediaServerItem, type, param.getApp(), param.getStream(), param);
+                                if (param.getOriginType() == OriginType.RTSP_PUSH.ordinal()
+                                        || param.getOriginType() == OriginType.RTMP_PUSH.ordinal()
+                                        || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) {
+                                    param.setSeverId(userSetting.getServerId());
+                                    zlmMediaListManager.addPush(param);
+                                }
+                            } else {
+                                // 兼容流注销时类型从redis记录获取
+                                OnStreamChangedHookParam onStreamChangedHookParam = redisCatchStorage.getStreamInfo(param.getApp(), param.getStream(), param.getMediaServerId());
+                                if (onStreamChangedHookParam != null) {
+                                    type = OriginType.values()[onStreamChangedHookParam.getOriginType()].getType();
+                                    redisCatchStorage.removeStream(mediaServerItem.getId(), type, param.getApp(), param.getStream());
+                                }
+                                GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream());
+                                if (gbStream != null) {
+//									eventPublisher.catalogEventPublishForStream(null, gbStream, CatalogEvent.OFF);
+                                }
+                                zlmMediaListManager.removeMedia(param.getApp(), param.getStream());
+                            }
+                            if (type != null) {
+                                // 发送流变化redis消息
+                                JSONObject jsonObject = new JSONObject();
+                                jsonObject.put("serverId", userSetting.getServerId());
+                                jsonObject.put("app", param.getApp());
+                                jsonObject.put("stream", param.getStream());
+                                jsonObject.put("register", param.isRegist());
+                                jsonObject.put("mediaServerId", param.getMediaServerId());
+                                redisCatchStorage.sendStreamChangeMsg(type, jsonObject);
+                            }
+                        }
+                    }
+                }
+                if (!param.isRegist()) {
+                    List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream());
+                    if (sendRtpItems.size() > 0) {
+                        for (SendRtpItem sendRtpItem : sendRtpItems) {
+                            if (sendRtpItem.getApp().equals(param.getApp())) {
+                                String platformId = sendRtpItem.getPlatformId();
+                                ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);
+                                Device device = deviceService.getDevice(platformId);
+
+                                try {
+                                    if (platform != null) {
+                                        commanderFroPlatform.streamByeCmd(platform, sendRtpItem);
+                                    } else {
+                                        cmder.streamByeCmd(device, sendRtpItem.getChannelId(), param.getStream(), sendRtpItem.getCallId());
+                                    }
+                                } catch (SipException | InvalidArgumentException | ParseException |
+                                         SsrcTransactionNotFoundException e) {
+                                    logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        });
+
+        return HookResult.SUCCESS();
+    }
+
+    /**
+     * 流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流。
+     */
+    @ResponseBody
+    @PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8")
+    public JSONObject onStreamNoneReader(@RequestBody OnStreamNoneReaderHookParam param) {
+
+        logger.info("[ZLM HOOK]流无人观看:{]->{}->{}/{}" + param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
+        JSONObject ret = new JSONObject();
+        ret.put("code", 0);
+        // 国标类型的流
+        if ("rtp".equals(param.getApp())) {
+            ret.put("close", userSetting.getStreamOnDemand());
+            // 国标流, 点播/录像回放/录像下载
+            StreamInfo streamInfoForPlayCatch = redisCatchStorage.queryPlayByStreamId(param.getStream());
+            // 点播
+            if (streamInfoForPlayCatch != null) {
+                // 收到无人观看说明流也没有在往上级推送
+                if (redisCatchStorage.isChannelSendingRTP(streamInfoForPlayCatch.getChannelId())) {
+                    List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByChnnelId(streamInfoForPlayCatch.getChannelId());
+                    if (sendRtpItems.size() > 0) {
+                        for (SendRtpItem sendRtpItem : sendRtpItems) {
+                            ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
+                            try {
+                                commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
+                            } catch (SipException | InvalidArgumentException | ParseException e) {
+                                logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
+                            }
+                            redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
+                                    sendRtpItem.getCallId(), sendRtpItem.getStreamId());
+                        }
+                    }
+                }
+                Device device = deviceService.getDevice(streamInfoForPlayCatch.getDeviceID());
+                if (device != null) {
+                    try {
+                        cmder.streamByeCmd(device, streamInfoForPlayCatch.getChannelId(),
+                                streamInfoForPlayCatch.getStream(), null);
+                    } catch (InvalidArgumentException | ParseException | SipException |
+                             SsrcTransactionNotFoundException e) {
+                        logger.error("[无人观看]点播, 发送BYE失败 {}", e.getMessage());
+                    }
+                }
+
+                redisCatchStorage.stopPlay(streamInfoForPlayCatch);
+                storager.stopPlay(streamInfoForPlayCatch.getDeviceID(), streamInfoForPlayCatch.getChannelId());
+                return ret;
+            }
+            // 录像回放
+            StreamInfo streamInfoForPlayBackCatch = redisCatchStorage.queryPlayback(null, null, param.getStream(), null);
+            if (streamInfoForPlayBackCatch != null) {
+                if (streamInfoForPlayBackCatch.isPause()) {
+                    ret.put("close", false);
+                } else {
+                    Device device = deviceService.getDevice(streamInfoForPlayBackCatch.getDeviceID());
+                    if (device != null) {
+                        try {
+                            cmder.streamByeCmd(device, streamInfoForPlayBackCatch.getChannelId(),
+                                    streamInfoForPlayBackCatch.getStream(), null);
+                        } catch (InvalidArgumentException | ParseException | SipException |
+                                 SsrcTransactionNotFoundException e) {
+                            logger.error("[无人观看]回放, 发送BYE失败 {}", e.getMessage());
+                        }
+                    }
+                    redisCatchStorage.stopPlayback(streamInfoForPlayBackCatch.getDeviceID(),
+                            streamInfoForPlayBackCatch.getChannelId(), streamInfoForPlayBackCatch.getStream(), null);
+                }
+                return ret;
+            }
+            // 录像下载
+            StreamInfo streamInfoForDownload = redisCatchStorage.queryDownload(null, null, param.getStream(), null);
+            // 进行录像下载时无人观看不断流
+            if (streamInfoForDownload != null) {
+                ret.put("close", false);
+                return ret;
+            }
+        } else {
+            // 非国标流 推流/拉流代理
+            // 拉流代理
+            StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream());
+            if (streamProxyItem != null) {
+                if (streamProxyItem.isEnable_remove_none_reader()) {
+                    // 无人观看自动移除
+                    ret.put("close", true);
+                    streamProxyService.del(param.getApp(), param.getStream());
+                    String url = streamProxyItem.getUrl() != null ? streamProxyItem.getUrl() : streamProxyItem.getSrc_url();
+                    logger.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除", param.getApp(), param.getStream(), url);
+                } else if (streamProxyItem.isEnable_disable_none_reader()) {
+                    // 无人观看停用
+                    ret.put("close", true);
+                    // 修改数据
+                    streamProxyService.stop(param.getApp(), param.getStream());
+                } else {
+                    // 无人观看不做处理
+                    ret.put("close", false);
+                }
+                return ret;
+            }
+            // 推流具有主动性,暂时不做处理
+//			StreamPushItem streamPushItem = streamPushService.getPush(app, streamId);
+//			if (streamPushItem != null) {
+//				// TODO 发送停止
+//
+//			}
+        }
+        return ret;
+    }
+
+    /**
+     * 流未找到事件,用户可以在此事件触发时,立即去拉流,这样可以实现按需拉流;此事件对回复不敏感。
+     */
+    @ResponseBody
+    @PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8")
+    public DeferredResult<HookResult> onStreamNotFound(@RequestBody OnStreamNotFoundHookParam param) {
+        logger.info("[ZLM HOOK] 流未找到:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
+
+        DeferredResult<HookResult> defaultResult = new DeferredResult<>();
+
+        MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId());
+        if (!userSetting.isAutoApplyPlay() || mediaInfo == null) {
+            defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg()));
+            return defaultResult;
+        }
+
+        if ("rtp".equals(param.getApp())) {
+            String[] s = param.getStream().split("_");
+            if (!mediaInfo.isRtpEnable() || s.length != 2) {
+                defaultResult.setResult(HookResult.SUCCESS());
+                return defaultResult;
+            }
+            String deviceId = s[0];
+            String channelId = s[1];
+            Device device = redisCatchStorage.getDevice(deviceId);
+            if (device == null) {
+                defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg()));
+                return defaultResult;
+            }
+            DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
+            if (deviceChannel == null) {
+                defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg()));
+                return defaultResult;
+            }
+            logger.info("[ZLM HOOK] 流未找到, 发起自动点播:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
+            RequestMessage msg = new RequestMessage();
+            String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId;
+            boolean exist = resultHolder.exist(key, null);
+            msg.setKey(key);
+            String uuid = UUID.randomUUID().toString();
+            msg.setId(uuid);
+            DeferredResult<HookResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
+            DeferredResultEx<HookResult> deferredResultEx = new DeferredResultEx<>(result);
+
+            result.onTimeout(() -> {
+                logger.info("点播接口等待超时");
+                // 释放rtpserver
+                msg.setData(new HookResult(ErrorCode.ERROR100.getCode(), "点播超时"));
+                resultHolder.invokeResult(msg);
+            });
+            // TODO 在点播未成功的情况下在此调用接口点播会导致返回的流地址ip错误
+            deferredResultEx.setFilter(result1 -> {
+                WVPResult<StreamInfo> wvpResult1 = (WVPResult<StreamInfo>) result1;
+                HookResult resultForEnd = new HookResult();
+                resultForEnd.setCode(wvpResult1.getCode());
+                resultForEnd.setMsg(wvpResult1.getMsg());
+                return resultForEnd;
+            });
+
+            // 录像查询以channelId作为deviceId查询
+            resultHolder.put(key, uuid, deferredResultEx);
+
+            if (!exist) {
+                playService.play(mediaInfo, deviceId, channelId, null, eventResult -> {
+                    msg.setData(new HookResult(eventResult.statusCode, eventResult.msg));
+                    resultHolder.invokeResult(msg);
+                }, null);
+            }
+            return result;
+        } else {
+            // 拉流代理
+            StreamProxyItem streamProxyByAppAndStream = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream());
+            if (streamProxyByAppAndStream != null && streamProxyByAppAndStream.isEnable_disable_none_reader()) {
+                streamProxyService.start(param.getApp(), param.getStream());
+            }
+            DeferredResult<HookResult> result = new DeferredResult<>();
+            result.setResult(HookResult.SUCCESS());
+            return result;
+        }
+    }
+
+    /**
+     * 服务器启动事件,可以用于监听服务器崩溃重启;此事件对回复不敏感。
+     */
+    @ResponseBody
+    @PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8")
+    public HookResult onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject) {
+
+        jsonObject.put("ip", request.getRemoteAddr());
+        ZLMServerConfig zlmServerConfig = JSON.to(ZLMServerConfig.class, jsonObject);
+        zlmServerConfig.setIp(request.getRemoteAddr());
+        logger.info("[ZLM HOOK] zlm 启动 " + zlmServerConfig.getGeneralMediaServerId());
+        taskExecutor.execute(() -> {
+            List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_started);
+            if (subscribes != null && subscribes.size() > 0) {
+                for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
+                    subscribe.response(null, jsonObject);
+                }
+            }
+            mediaServerService.zlmServerOnline(zlmServerConfig);
+        });
+
+        return HookResult.SUCCESS();
+    }
+
+    /**
+     * 发送rtp(startSendRtp)被动关闭时回调
+     */
+    @ResponseBody
+    @PostMapping(value = "/on_send_rtp_stopped", produces = "application/json;charset=UTF-8")
+    public HookResult onSendRtpStopped(HttpServletRequest request, @RequestBody OnSendRtpStoppedHookParam param) {
+
+        logger.info("[ZLM HOOK] rtp发送关闭:{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream());
+
+        // 查找对应的上级推流,发送停止
+        if (!"rtp".equals(param.getApp())) {
+            return HookResult.SUCCESS();
+        }
+        taskExecutor.execute(() -> {
+            List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream());
+            if (sendRtpItems.size() > 0) {
+                for (SendRtpItem sendRtpItem : sendRtpItems) {
+                    ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
+                    try {
+                        commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
+                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                        logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
+                    }
+                    redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
+                            sendRtpItem.getCallId(), sendRtpItem.getStreamId());
+                }
+            }
+        });
+
+        return HookResult.SUCCESS();
+    }
+
+    /**
+     * rtpServer收流超时
+     */
+    @ResponseBody
+    @PostMapping(value = "/on_rtp_server_timeout", produces = "application/json;charset=UTF-8")
+    public HookResult onRtpServerTimeout(HttpServletRequest request, @RequestBody OnRtpServerTimeoutHookParam param) {
+        logger.info("[ZLM HOOK] rtpServer收流超时:{}->{}({})", param.getMediaServerId(), param.getStream_id(), param.getSsrc());
+
+        taskExecutor.execute(() -> {
+            JSONObject json = (JSONObject) JSON.toJSON(param);
+            List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_rtp_server_timeout);
+            if (subscribes != null && subscribes.size() > 0) {
+                for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
+                    subscribe.response(null, json);
+                }
+            }
+        });
+
+        return HookResult.SUCCESS();
+    }
+
+    private Map<String, String> urlParamToMap(String params) {
+        HashMap<String, String> map = new HashMap<>();
+        if (ObjectUtils.isEmpty(params)) {
+            return map;
+        }
+        String[] paramsArray = params.split("&");
+        if (paramsArray.length == 0) {
+            return map;
+        }
+        for (String param : paramsArray) {
+            String[] paramArray = param.split("=");
+            if (paramArray.length == 2) {
+                map.put(paramArray[0], paramArray[1]);
+            }
+        }
+        return map;
+    }
 }

+ 7 - 6
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java

@@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.media.zlm;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.GbStream;
 import com.genersoft.iot.vmp.media.zlm.dto.*;
+import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.IStreamProxyService;
 import com.genersoft.iot.vmp.service.IStreamPushService;
@@ -67,19 +68,19 @@ public class ZLMMediaListManager {
 
     private Map<String, ChannelOnlineEvent> channelOnPublishEvents = new ConcurrentHashMap<>();
 
-    public StreamPushItem addPush(MediaItem mediaItem) {
-        StreamPushItem transform = streamPushService.transform(mediaItem);
-        StreamPushItem pushInDb = streamPushService.getPush(mediaItem.getApp(), mediaItem.getStream());
-        transform.setPushIng(mediaItem.isRegist());
+    public StreamPushItem addPush(OnStreamChangedHookParam onStreamChangedHookParam) {
+        StreamPushItem transform = streamPushService.transform(onStreamChangedHookParam);
+        StreamPushItem pushInDb = streamPushService.getPush(onStreamChangedHookParam.getApp(), onStreamChangedHookParam.getStream());
+        transform.setPushIng(onStreamChangedHookParam.isRegist());
         transform.setUpdateTime(DateUtil.getNow());
         transform.setPushTime(DateUtil.getNow());
-        transform.setSelf(userSetting.getServerId().equals(mediaItem.getSeverId()));
+        transform.setSelf(userSetting.getServerId().equals(onStreamChangedHookParam.getSeverId()));
         if (pushInDb == null) {
             transform.setCreateTime(DateUtil.getNow());
             streamPushMapper.add(transform);
         }else {
             streamPushMapper.update(transform);
-            gbStreamMapper.updateMediaServer(mediaItem.getApp(), mediaItem.getStream(), mediaItem.getMediaServerId());
+            gbStreamMapper.updateMediaServer(onStreamChangedHookParam.getApp(), onStreamChangedHookParam.getStream(), onStreamChangedHookParam.getMediaServerId());
         }
         ChannelOnlineEvent channelOnlineEventLister = getChannelOnlineEventLister(transform.getApp(), transform.getStream());
         if ( channelOnlineEventLister != null)  {

+ 28 - 29
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java

@@ -1,7 +1,7 @@
 package com.genersoft.iot.vmp.media.zlm;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import okhttp3.*;
 import okhttp3.logging.HttpLoggingInterceptor;
@@ -23,27 +23,34 @@ public class ZLMRESTfulUtils {
 
     private final static Logger logger = LoggerFactory.getLogger(ZLMRESTfulUtils.class);
 
-
-
+    private OkHttpClient client;
 
     public interface RequestCallback{
         void run(JSONObject response);
     }
 
     private OkHttpClient getClient(){
-        OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
-        //todo 暂时写死超时时间 均为5s
-        httpClientBuilder.connectTimeout(5,TimeUnit.SECONDS);  //设置连接超时时间
-        httpClientBuilder.readTimeout(5,TimeUnit.SECONDS);     //设置读取超时时间
-        if (logger.isDebugEnabled()) {
-            HttpLoggingInterceptor logging = new HttpLoggingInterceptor(message -> {
-                logger.debug("http请求参数:" + message);
-            });
-            logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
-            // OkHttp進行添加攔截器loggingInterceptor
-            httpClientBuilder.addInterceptor(logging);
+        if (client == null) {
+            OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
+            //todo 暂时写死超时时间 均为5s
+            // 设置连接超时时间
+            httpClientBuilder.connectTimeout(5,TimeUnit.SECONDS);
+            // 设置读取超时时间
+            httpClientBuilder.readTimeout(10,TimeUnit.SECONDS);
+            // 设置连接池
+            httpClientBuilder.connectionPool(new ConnectionPool(16, 5, TimeUnit.MINUTES));
+            if (logger.isDebugEnabled()) {
+                HttpLoggingInterceptor logging = new HttpLoggingInterceptor(message -> {
+                    logger.debug("http请求参数:" + message);
+                });
+                logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
+                // OkHttp進行添加攔截器loggingInterceptor
+                httpClientBuilder.addInterceptor(logging);
+            }
+            client = httpClientBuilder.build();
         }
-        return httpClientBuilder.build();
+        return client;
+
     }
 
 
@@ -164,12 +171,9 @@ public class ZLMRESTfulUtils {
                 .build();
         logger.info(request.toString());
         try {
-            OkHttpClient client = new OkHttpClient.Builder()
-                    .readTimeout(10, TimeUnit.SECONDS)
-                    .build();
+            OkHttpClient client = getClient();
             Response response = client.newCall(request).execute();
             if (response.isSuccessful()) {
-                logger.info("response body contentType: " + Objects.requireNonNull(response.body()).contentType());
                 if (targetPath != null) {
                     File snapFolder = new File(targetPath);
                     if (!snapFolder.exists()) {
@@ -182,6 +186,7 @@ public class ZLMRESTfulUtils {
                     FileOutputStream outStream = new FileOutputStream(snapFile);
 
                     outStream.write(Objects.requireNonNull(response.body()).bytes());
+                    outStream.flush();
                     outStream.close();
                 } else {
                     logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message()));
@@ -237,14 +242,13 @@ public class ZLMRESTfulUtils {
     }
 
     public JSONObject addFFmpegSource(MediaServerItem mediaServerItem, String src_url, String dst_url, String timeout_ms,
-                                      boolean enable_hls, boolean enable_mp4, String ffmpeg_cmd_key){
+                                      boolean enable_audio, boolean enable_mp4, String ffmpeg_cmd_key){
         logger.info(src_url);
         logger.info(dst_url);
         Map<String, Object> param = new HashMap<>();
         param.put("src_url", src_url);
         param.put("dst_url", dst_url);
         param.put("timeout_ms", timeout_ms);
-        param.put("enable_hls", enable_hls);
         param.put("enable_mp4", enable_mp4);
         param.put("ffmpeg_cmd_key", ffmpeg_cmd_key);
         return sendPost(mediaServerItem, "addFFmpegSource",param, null);
@@ -292,19 +296,14 @@ public class ZLMRESTfulUtils {
         return sendPost(mediaServerItem, "restartServer",null, null);
     }
 
-    public JSONObject addStreamProxy(MediaServerItem mediaServerItem, String app, String stream, String url, boolean enable_hls, boolean enable_mp4, String rtp_type) {
+    public JSONObject addStreamProxy(MediaServerItem mediaServerItem, String app, String stream, String url, boolean enable_audio, boolean enable_mp4, String rtp_type) {
         Map<String, Object> param = new HashMap<>();
         param.put("vhost", "__defaultVhost__");
         param.put("app", app);
         param.put("stream", stream);
         param.put("url", url);
-        param.put("enable_hls", enable_hls?1:0);
         param.put("enable_mp4", enable_mp4?1:0);
-        param.put("enable_rtmp", 1);
-        param.put("enable_fmp4", 1);
-        param.put("enable_audio", 1);
-        param.put("enable_rtsp", 1);
-        param.put("add_mute_audio", 1);
+        param.put("enable_audio", enable_audio?1:0);
         param.put("rtp_type", rtp_type);
         return sendPost(mediaServerItem, "addStreamProxy",param, null);
     }

Some files were not shown because too many files changed in this diff