Kaynağa Gözat

更新同步wvp

wangyimeng 3 yıl önce
ebeveyn
işleme
c8831bc58f
100 değiştirilmiş dosya ile 3302 ekleme ve 1515 silme
  1. 1 1
      DOCKERFILE
  2. 16 5
      README.md
  3. 3 3
      pom.xml
  4. 13 0
      sql/clean.sql
  5. 216 203
      sql/mysql.sql
  6. 12 0
      sql/update.sql
  7. 6 2
      src/main/java/com/genersoft/iot/vmp/common/ApiSaveConstant.java
  8. 28 0
      src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java
  9. 24 1
      src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
  10. 2 2
      src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java
  11. 65 12
      src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java
  12. 6 1
      src/main/java/com/genersoft/iot/vmp/conf/RedisConfig.java
  13. 4 5
      src/main/java/com/genersoft/iot/vmp/conf/RedisKeyExpirationEventMessageListener.java
  14. 1 1
      src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java
  15. 2 3
      src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java
  16. 8 0
      src/main/java/com/genersoft/iot/vmp/conf/ThreadPoolTaskConfig.java
  17. 18 5
      src/main/java/com/genersoft/iot/vmp/conf/UserSetup.java
  18. 13 8
      src/main/java/com/genersoft/iot/vmp/conf/runner/SipDeviceRunner.java
  19. 4 4
      src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java
  20. 7 5
      src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
  21. 6 2
      src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java
  22. 2 2
      src/main/java/com/genersoft/iot/vmp/gb28181/auth/RegisterLogicHandler.java
  23. 46 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/AlarmChannelMessage.java
  24. 32 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/CatalogData.java
  25. 53 2
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
  26. 24 1
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarm.java
  27. 14 1
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java
  28. 23 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannelInPlatform.java
  29. 1 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbStream.java
  30. 8 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamType.java
  31. 45 6
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java
  32. 6 6
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java
  33. 11 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java
  34. 54 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java
  35. 27 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeInfo.java
  36. 34 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SyncStatus.java
  37. 0 28
      src/main/java/com/genersoft/iot/vmp/gb28181/event/DeviceOffLineDetector.java
  38. 18 24
      src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/KeepaliveTimeoutListenerForPlatform.java
  39. 6 9
      src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/KeepliveTimeoutListener.java
  40. 7 12
      src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/OfflineEventListener.java
  41. 16 14
      src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEventListener.java
  42. 2 3
      src/main/java/com/genersoft/iot/vmp/gb28181/event/platformKeepaliveExpire/PlatformKeepaliveExpireEventLister.java
  43. 11 12
      src/main/java/com/genersoft/iot/vmp/gb28181/event/platformNotRegister/PlatformCycleRegisterEventLister.java
  44. 14 26
      src/main/java/com/genersoft/iot/vmp/gb28181/event/platformNotRegister/PlatformNotRegisterEventLister.java
  45. 2 4
      src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEventListener.java
  46. 0 50
      src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/SubscribeListenerForPlatform.java
  47. 29 22
      src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java
  48. 97 29
      src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java
  49. 3 0
      src/main/java/com/genersoft/iot/vmp/gb28181/session/SsrcConfig.java
  50. 65 26
      src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java
  51. 0 71
      src/main/java/com/genersoft/iot/vmp/gb28181/task/GPSSubscribeTask.java
  52. 12 0
      src/main/java/com/genersoft/iot/vmp/gb28181/task/ISubscribeTask.java
  53. 102 0
      src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/CatalogSubscribeTask.java
  54. 95 0
      src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeHandlerTask.java
  55. 99 0
      src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeTask.java
  56. 2 3
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java
  57. 1 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/CheckForAllRecordsThread.java
  58. 7 3
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java
  59. 21 15
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  60. 10 4
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
  61. 1 55
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java
  62. 9 5
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java
  63. 244 84
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  64. 227 102
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
  65. 9 3
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java
  66. 3 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
  67. 19 6
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
  68. 158 67
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
  69. 47 35
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java
  70. 161 163
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java
  71. 48 54
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java
  72. 25 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageRequestProcessor.java
  73. 40 8
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java
  74. 103 20
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java
  75. 41 42
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/CatalogNotifyMessageHandler.java
  76. 5 5
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java
  77. 6 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java
  78. 12 11
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MobilePositionNotifyMessageHandler.java
  79. 2 8
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/AlarmQueryMessageHandler.java
  80. 47 35
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java
  81. 2 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceStatusQueryMessageHandler.java
  82. 2 4
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/RecordInfoQueryMessageHandler.java
  83. 121 44
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java
  84. 6 6
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceInfoResponseMessageHandler.java
  85. 5 6
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceStatusResponseMessageHandler.java
  86. 12 11
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/MobilePositionResponseMessageHandler.java
  87. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java
  88. 8 6
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java
  89. 126 0
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/Coordtransform.java
  90. 12 0
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java
  91. 66 13
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java
  92. 139 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java
  93. 26 29
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
  94. 15 8
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookSubscribe.java
  95. 54 17
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java
  96. 5 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
  97. 14 6
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
  98. 21 19
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
  99. 6 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ChannelOnlineEvent.java
  100. 0 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java

+ 1 - 1
DOCKERFILE

@@ -84,7 +84,7 @@ RUN     echo '#!/bin/bash' > run.sh && \
         echo 'nohup java -jar *.jar --userSettings.record=/opt/media/www/record/ &' >> run.sh && \
         echo 'nohup /opt/media/MediaServer -d -m 3 &' >> run.sh && \
         echo 'cd /opt/wvp' >> run.sh && \
-        echo 'if [${WVP_CONFIG}]; then' >> run.sh && \
+        echo 'if [-n "${WVP_CONFIG}"]; then' >> run.sh && \
         echo '        java -jar *.jar --spring.config.location=/opt/wvp/config/application.yml --media.record-assist-port=18081 ${WVP_CONFIG}' >> run.sh && \
         echo 'else' >> run.sh && \
         echo '        java -jar *.jar --spring.config.location=/opt/wvp/config/application.yml --media.record-assist-port=18081 --media.ip=127.0.0.1 --media.sdp-ip=${WVP_IP} --sip.ip=${WVP_IP} --media.stream-ip=${WVP_IP}' >> run.sh  && \

+ 16 - 5
README.md

@@ -1,4 +1,4 @@
-![logo](https://gitee.com/pan648540858/wvp-GB28181-pro/raw/wvp-28181-2.0/web_src/static/logo.png)
+![logo](https://raw.githubusercontent.com/648540858/wvp-GB28181-pro/wvp-28181-2.0/web_src/static/logo.png)
 # 开箱即用的的28181协议视频平台
 
 [![Build Status](https://travis-ci.org/xia-chu/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xia-chu/ZLMediaKit)
@@ -10,9 +10,9 @@
 
 WEB VIDEO PLATFORM是一个基于GB28181-2016标准实现的开箱即用的网络视频平台,负责实现核心信令与设备管理后台部分,支持NAT穿透,支持海康、大华、宇视等品牌的IPC、NVR接入。支持国标级联,支持将不带国标功能的摄像机/直播流/直播推流转发到其他国标平台。   
 
-流媒体服务基于ZLMediaKit-https://github.com/xiongziliang/ZLMediaKit
-
-前端页面基于MediaServerUI进行修改.  
+流媒体服务基于@夏楚 ZLMediaKit [https://github.com/ZLMediaKit/ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit)   
+播放器使用@dexter jessibuca [https://github.com/langhuihui/jessibuca/tree/v3](https://github.com/langhuihui/jessibuca/tree/v3)  
+前端页面基于@Kyle MediaServerUI [https://gitee.com/kkkkk5G/MediaServerUI](https://gitee.com/kkkkk5G/MediaServerUI) 进行修改.  
 
 # 应用场景:
 支持浏览器无插件播放摄像头视频。  
@@ -128,7 +128,18 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
 QQ群: 901799015, ZLM使用文档[https://github.com/ZLMediaKit/ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit)  
 QQ私信一般不回, 精力有限.欢迎大家在群里讨论.觉得项目对你有帮助,欢迎star和提交pr。
 
+# 授权协议
+本项目自有代码使用宽松的MIT协议,在保留版权信息的情况下可以自由应用于各自商用、非商业的项目。 但是本项目也零碎的使用了一些其他的开源代码,在商用的情况下请自行替代或剔除; 由于使用本项目而产生的商业纠纷或侵权行为一概与本项目及开发者无关,请自行承担法律风险。 在使用本项目代码时,也应该在授权协议中同时表明本项目依赖的第三方库的协议
 
 # 致谢
-感谢作者[夏楚](https://github.com/xia-chu) 提供这么棒的开源流媒体服务框架  
+感谢作者[夏楚](https://github.com/xia-chu) 提供这么棒的开源流媒体服务框架,并在开发过程中给予支持与帮助。     
+感谢作者[dexter langhuihui](https://github.com/langhuihui) 开源这么好用的WEB播放器。     
+感谢作者[Kyle](https://gitee.com/kkkkk5G) 开源了好用的前端页面     
+感谢各位大佬的赞助以及对项目的指正与帮助。包括但不限于代码贡献、问题反馈、资金捐赠等各种方式的支持!以下排名不分先后:  
+[lawrencehj](https://github.com/lawrencehj) [Smallwhitepig](https://github.com/Smallwhitepig) [swwhaha](https://github.com/swwheihei) 
+[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)
+
+ps: 刚增加了这个名单,肯定遗漏了一些大佬,欢迎大佬联系我添加。
 

+ 3 - 3
pom.xml

@@ -11,7 +11,7 @@
 
 	<groupId>com.genersoft</groupId>
 	<artifactId>wvp-pro</artifactId>
-	<version>2.0.2</version>
+	<version>2.2.1</version>
 	<name>web video platform</name>
 	<description>国标28181视频平台</description>
 
@@ -269,13 +269,13 @@
 				</configuration>
 			</plugin>
 
-			<plugin>
+	<!--		<plugin>
 				<groupId>pl.project13.maven</groupId>
 				<artifactId>git-commit-id-plugin</artifactId>
 				<configuration>
 					<offline>true</offline>
 				</configuration>
-			</plugin>
+			</plugin>-->
 
 			<plugin>
 				<groupId>org.apache.maven.plugins</groupId>

+ 13 - 0
sql/clean.sql

@@ -0,0 +1,13 @@
+delete from  device;
+delete from  device_alarm;
+delete from  device_channel;
+delete from  device_mobile_position;
+delete from  gb_stream;
+delete from  log;
+delete from  media_server;
+delete from  parent_platform;
+delete from  platform_catalog;
+delete from  platform_gb_channel;
+delete from  platform_gb_stream;
+delete from  stream_proxy;
+delete from  stream_push;

+ 216 - 203
sql/mysql.sql

@@ -1,13 +1,13 @@
+-- MariaDB dump 10.19  Distrib 10.7.3-MariaDB, for Linux (x86_64)
 --
+-- Host: 127.0.0.1    Database: wvp3
 -- ------------------------------------------------------
+-- Server version	8.0.0-dmr
 
 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
 /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
 /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
-/*!50503 SET NAMES utf8mb4 */;
+/*!40101 SET NAMES utf8mb4 */;
 /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
 /*!40103 SET TIME_ZONE='+00:00' */;
 /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
@@ -21,30 +21,34 @@
 
 DROP TABLE IF EXISTS `device`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!50503 SET character_set_client = utf8mb4 */;
+/*!40101 SET character_set_client = utf8 */;
 CREATE TABLE `device` (
-                          `id` int NOT NULL AUTO_INCREMENT,
-                          `deviceId` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
-                          `name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                          `manufacturer` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                          `model` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                          `firmware` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                          `transport` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                          `streamMode` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                          `online` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                          `registerTime` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                          `keepaliveTime` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                          `ip` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
-                          `createTime` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
-                          `updateTime` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
-                          `port` int NOT NULL,
-                          `expires` int NOT NULL,
-                          `subscribeCycleForCatalog` int NOT NULL,
-                          `hostAddress` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
-                          `charset` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
+                          `id` int(11) NOT NULL AUTO_INCREMENT,
+                          `deviceId` varchar(50) NOT NULL,
+                          `name` varchar(255) DEFAULT NULL,
+                          `manufacturer` varchar(255) DEFAULT NULL,
+                          `model` varchar(255) DEFAULT NULL,
+                          `firmware` varchar(255) DEFAULT NULL,
+                          `transport` varchar(50) DEFAULT NULL,
+                          `streamMode` varchar(50) DEFAULT NULL,
+                          `online` varchar(50) DEFAULT NULL,
+                          `registerTime` varchar(50) DEFAULT NULL,
+                          `keepaliveTime` varchar(50) DEFAULT NULL,
+                          `ip` varchar(50) NOT NULL,
+                          `createTime` varchar(50) NOT NULL,
+                          `updateTime` varchar(50) NOT NULL,
+                          `port` int(11) NOT NULL,
+                          `expires` int(11) NOT NULL,
+                          `subscribeCycleForCatalog` int(11) NOT NULL,
+                          `subscribeCycleForMobilePosition` int(11) NOT NULL,
+                          `mobilePositionSubmissionInterval` int(11) NOT NULL DEFAULT '5',
+                          `subscribeCycleForAlarm` int(11) NOT NULL,
+                          `hostAddress` varchar(50) NOT NULL,
+                          `charset` varchar(50) NOT NULL,
+                          `ssrcCheck` int(11) DEFAULT '0',
                           PRIMARY KEY (`id`),
                           UNIQUE KEY `device_deviceId_uindex` (`deviceId`)
-) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+) ENGINE=InnoDB AUTO_INCREMENT=47 DEFAULT CHARSET=utf8mb4;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -62,20 +66,20 @@ UNLOCK TABLES;
 
 DROP TABLE IF EXISTS `device_alarm`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!50503 SET character_set_client = utf8mb4 */;
+/*!40101 SET character_set_client = utf8 */;
 CREATE TABLE `device_alarm` (
-                                `id` int NOT NULL AUTO_INCREMENT,
-                                `deviceId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                `channelId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                `alarmPriority` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                `alarmMethod` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                `alarmTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                `alarmDescription` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
+                                `id` int(11) NOT NULL AUTO_INCREMENT,
+                                `deviceId` varchar(50) NOT NULL,
+                                `channelId` varchar(50) NOT NULL,
+                                `alarmPriority` varchar(50) NOT NULL,
+                                `alarmMethod` varchar(50) DEFAULT NULL,
+                                `alarmTime` varchar(50) NOT NULL,
+                                `alarmDescription` varchar(255) DEFAULT NULL,
                                 `longitude` double DEFAULT NULL,
                                 `latitude` double DEFAULT NULL,
-                                `alarmType` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
+                                `alarmType` varchar(50) DEFAULT NULL,
                                 PRIMARY KEY (`id`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -93,43 +97,43 @@ UNLOCK TABLES;
 
 DROP TABLE IF EXISTS `device_channel`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!50503 SET character_set_client = utf8mb4 */;
+/*!40101 SET character_set_client = utf8 */;
 CREATE TABLE `device_channel` (
-                                  `id` int NOT NULL AUTO_INCREMENT,
-                                  `channelId` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
-                                  `name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                  `manufacture` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                  `model` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                  `owner` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                  `civilCode` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                  `block` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                  `address` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                  `parentId` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                  `safetyWay` int DEFAULT NULL,
-                                  `registerWay` int DEFAULT NULL,
-                                  `certNum` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                  `certifiable` int DEFAULT NULL,
-                                  `errCode` int DEFAULT NULL,
-                                  `endTime` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                  `secrecy` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                  `ipAddress` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                  `port` int DEFAULT NULL,
-                                  `password` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                  `PTZType` int DEFAULT NULL,
-                                  `status` int DEFAULT NULL,
+                                  `id` int(11) NOT NULL AUTO_INCREMENT,
+                                  `channelId` varchar(50) NOT NULL,
+                                  `name` varchar(255) DEFAULT NULL,
+                                  `manufacture` varchar(50) DEFAULT NULL,
+                                  `model` varchar(50) DEFAULT NULL,
+                                  `owner` varchar(50) DEFAULT NULL,
+                                  `civilCode` varchar(50) DEFAULT NULL,
+                                  `block` varchar(50) DEFAULT NULL,
+                                  `address` varchar(50) DEFAULT NULL,
+                                  `parentId` varchar(50) DEFAULT NULL,
+                                  `safetyWay` int(11) DEFAULT NULL,
+                                  `registerWay` int(11) DEFAULT NULL,
+                                  `certNum` varchar(50) DEFAULT NULL,
+                                  `certifiable` int(11) DEFAULT NULL,
+                                  `errCode` int(11) DEFAULT NULL,
+                                  `endTime` varchar(50) DEFAULT NULL,
+                                  `secrecy` varchar(50) DEFAULT NULL,
+                                  `ipAddress` varchar(50) DEFAULT NULL,
+                                  `port` int(11) DEFAULT NULL,
+                                  `password` varchar(255) DEFAULT NULL,
+                                  `PTZType` int(11) DEFAULT NULL,
+                                  `status` int(11) DEFAULT NULL,
                                   `longitude` double DEFAULT NULL,
                                   `latitude` double DEFAULT NULL,
-                                  `streamId` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                  `deviceId` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
-                                  `parental` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
+                                  `streamId` varchar(50) DEFAULT NULL,
+                                  `deviceId` varchar(50) NOT NULL,
+                                  `parental` varchar(50) DEFAULT NULL,
                                   `hasAudio` bit(1) DEFAULT NULL,
-                                  `createTime` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
-                                  `updateTime` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
-                                  `subCount` int DEFAULT '0',
+                                  `createTime` varchar(50) NOT NULL,
+                                  `updateTime` varchar(50) NOT NULL,
+                                  `subCount` int(11) DEFAULT '0',
                                   PRIMARY KEY (`id`),
                                   UNIQUE KEY `device_channel_id_uindex` (`id`),
                                   UNIQUE KEY `device_channel_pk` (`channelId`,`deviceId`)
-) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+) ENGINE=InnoDB AUTO_INCREMENT=81657 DEFAULT CHARSET=utf8mb4;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -147,24 +151,24 @@ UNLOCK TABLES;
 
 DROP TABLE IF EXISTS `device_mobile_position`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!50503 SET character_set_client = utf8mb4 */;
+/*!40101 SET character_set_client = utf8 */;
 CREATE TABLE `device_mobile_position` (
-                                          `id` int NOT NULL AUTO_INCREMENT,
-                                          `deviceId` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
-                                          `channelId` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
-                                          `deviceName` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                          `time` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
+                                          `id` int(11) NOT NULL AUTO_INCREMENT,
+                                          `deviceId` varchar(50) NOT NULL,
+                                          `channelId` varchar(50) NOT NULL,
+                                          `deviceName` varchar(255) DEFAULT NULL,
+                                          `time` varchar(50) NOT NULL,
                                           `longitude` double NOT NULL,
                                           `latitude` double NOT NULL,
                                           `altitude` double DEFAULT NULL,
                                           `speed` double DEFAULT NULL,
                                           `direction` double DEFAULT NULL,
-                                          `reportSource` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                          `geodeticSystem` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                          `cnLng` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                          `cnLat` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
+                                          `reportSource` varchar(50) DEFAULT NULL,
+                                          `geodeticSystem` varchar(50) DEFAULT NULL,
+                                          `cnLng` varchar(50) DEFAULT NULL,
+                                          `cnLat` varchar(50) DEFAULT NULL,
                                           PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+) ENGINE=InnoDB AUTO_INCREMENT=6108 DEFAULT CHARSET=utf8mb4;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -182,23 +186,23 @@ UNLOCK TABLES;
 
 DROP TABLE IF EXISTS `gb_stream`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!50503 SET character_set_client = utf8mb4 */;
+/*!40101 SET character_set_client = utf8 */;
 CREATE TABLE `gb_stream` (
-                             `gbStreamId` int NOT NULL AUTO_INCREMENT,
-                             `app` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                             `stream` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                             `gbId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                             `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
+                             `gbStreamId` int(11) NOT NULL AUTO_INCREMENT,
+                             `app` varchar(255) NOT NULL,
+                             `stream` varchar(255) NOT NULL,
+                             `gbId` varchar(50) NOT NULL,
+                             `name` varchar(255) DEFAULT NULL,
                              `longitude` double DEFAULT NULL,
                              `latitude` double DEFAULT NULL,
-                             `streamType` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
-                             `mediaServerId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
-                             `status` int DEFAULT NULL,
-                             `createStamp` bigint DEFAULT NULL,
+                             `streamType` varchar(50) DEFAULT NULL,
+                             `mediaServerId` varchar(50) DEFAULT NULL,
+                             `status` int(11) DEFAULT NULL,
+                             `createStamp` bigint(20) DEFAULT NULL,
                              PRIMARY KEY (`gbStreamId`) USING BTREE,
                              UNIQUE KEY `app` (`app`,`stream`) USING BTREE,
                              UNIQUE KEY `gbId` (`gbId`) USING BTREE
-) ENGINE=InnoDB AUTO_INCREMENT=300766 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB AUTO_INCREMENT=300769 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -216,19 +220,19 @@ UNLOCK TABLES;
 
 DROP TABLE IF EXISTS `log`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!50503 SET character_set_client = utf8mb4 */;
+/*!40101 SET character_set_client = utf8 */;
 CREATE TABLE `log` (
-                       `id` int NOT NULL AUTO_INCREMENT,
-                       `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                       `type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                       `uri` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                       `address` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                       `result` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                       `timing` bigint NOT NULL,
-                       `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                       `createTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
+                       `id` int(11) NOT NULL AUTO_INCREMENT,
+                       `name` varchar(50) NOT NULL,
+                       `type` varchar(50) NOT NULL,
+                       `uri` varchar(200) NOT NULL,
+                       `address` varchar(50) NOT NULL,
+                       `result` varchar(50) NOT NULL,
+                       `timing` bigint(20) NOT NULL,
+                       `username` varchar(50) NOT NULL,
+                       `createTime` varchar(50) NOT NULL,
                        PRIMARY KEY (`id`) USING BTREE
-) ENGINE=InnoDB AUTO_INCREMENT=962 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB AUTO_INCREMENT=1552 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -246,34 +250,34 @@ UNLOCK TABLES;
 
 DROP TABLE IF EXISTS `media_server`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!50503 SET character_set_client = utf8mb4 */;
+/*!40101 SET character_set_client = utf8 */;
 CREATE TABLE `media_server` (
-                                `id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                `ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                `hookIp` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                `sdpIp` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                `streamIp` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                `httpPort` int NOT NULL,
-                                `httpSSlPort` int NOT NULL,
-                                `rtmpPort` int NOT NULL,
-                                `rtmpSSlPort` int NOT NULL,
-                                `rtpProxyPort` int NOT NULL,
-                                `rtspPort` int NOT NULL,
-                                `rtspSSLPort` int NOT NULL,
-                                `autoConfig` int NOT NULL,
-                                `secret` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                `streamNoneReaderDelayMS` int NOT NULL,
-                                `rtpEnable` int NOT NULL,
-                                `rtpPortRange` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                `sendRtpPortRange` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                `recordAssistPort` int NOT NULL,
-                                `defaultServer` int NOT NULL,
-                                `createTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                `updateTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                `hookAliveInterval` int NOT NULL,
+                                `id` varchar(255) NOT NULL,
+                                `ip` varchar(50) NOT NULL,
+                                `hookIp` varchar(50) NOT NULL,
+                                `sdpIp` varchar(50) NOT NULL,
+                                `streamIp` varchar(50) NOT NULL,
+                                `httpPort` int(11) NOT NULL,
+                                `httpSSlPort` int(11) NOT NULL,
+                                `rtmpPort` int(11) NOT NULL,
+                                `rtmpSSlPort` int(11) NOT NULL,
+                                `rtpProxyPort` int(11) NOT NULL,
+                                `rtspPort` int(11) NOT NULL,
+                                `rtspSSLPort` int(11) NOT NULL,
+                                `autoConfig` int(11) NOT NULL,
+                                `secret` varchar(50) NOT NULL,
+                                `streamNoneReaderDelayMS` int(11) NOT NULL,
+                                `rtpEnable` int(11) NOT NULL,
+                                `rtpPortRange` varchar(50) NOT NULL,
+                                `sendRtpPortRange` varchar(50) NOT NULL,
+                                `recordAssistPort` int(11) NOT NULL,
+                                `defaultServer` int(11) NOT NULL,
+                                `createTime` varchar(50) NOT NULL,
+                                `updateTime` varchar(50) NOT NULL,
+                                `hookAliveInterval` int(11) NOT NULL,
                                 PRIMARY KEY (`id`) USING BTREE,
                                 UNIQUE KEY `media_server_i` (`ip`,`httpPort`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -291,33 +295,36 @@ UNLOCK TABLES;
 
 DROP TABLE IF EXISTS `parent_platform`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!50503 SET character_set_client = utf8mb4 */;
+/*!40101 SET character_set_client = utf8 */;
 CREATE TABLE `parent_platform` (
-                                   `id` int NOT NULL AUTO_INCREMENT,
-                                   `enable` int DEFAULT NULL,
-                                   `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                   `serverGBId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                   `serverGBDomain` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                   `serverIP` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                   `serverPort` int DEFAULT NULL,
-                                   `deviceGBId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                   `deviceIp` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                   `devicePort` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                   `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                   `password` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                   `expires` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                   `keepTimeout` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                   `transport` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                   `characterSet` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                   `catalogId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                   `ptz` int DEFAULT NULL,
-                                   `rtcp` int DEFAULT NULL,
+                                   `id` int(11) NOT NULL AUTO_INCREMENT,
+                                   `enable` int(11) DEFAULT NULL,
+                                   `name` varchar(255) DEFAULT NULL,
+                                   `serverGBId` varchar(50) NOT NULL,
+                                   `serverGBDomain` varchar(50) DEFAULT NULL,
+                                   `serverIP` varchar(50) DEFAULT NULL,
+                                   `serverPort` int(11) DEFAULT NULL,
+                                   `deviceGBId` varchar(50) NOT NULL,
+                                   `deviceIp` varchar(50) DEFAULT NULL,
+                                   `devicePort` varchar(50) DEFAULT NULL,
+                                   `username` varchar(255) DEFAULT NULL,
+                                   `password` varchar(50) DEFAULT NULL,
+                                   `expires` varchar(50) DEFAULT NULL,
+                                   `keepTimeout` varchar(50) DEFAULT NULL,
+                                   `transport` varchar(50) DEFAULT NULL,
+                                   `characterSet` varchar(50) DEFAULT NULL,
+                                   `catalogId` varchar(50) NOT NULL,
+                                   `ptz` int(11) DEFAULT NULL,
+                                   `rtcp` int(11) DEFAULT NULL,
                                    `status` bit(1) DEFAULT NULL,
-                                   `shareAllLiveStream` int DEFAULT NULL,
+                                   `shareAllLiveStream` int(11) DEFAULT NULL,
+                                   `startOfflinePush` int(11) DEFAULT '0',
+                                   `administrativeDivision` varchar(50) NOT NULL,
+                                   `catalogGroup` int(11) DEFAULT '1',
                                    PRIMARY KEY (`id`),
                                    UNIQUE KEY `parent_platform_id_uindex` (`id`),
                                    UNIQUE KEY `parent_platform_pk` (`serverGBId`)
-) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -335,14 +342,14 @@ UNLOCK TABLES;
 
 DROP TABLE IF EXISTS `platform_catalog`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!50503 SET character_set_client = utf8mb4 */;
+/*!40101 SET character_set_client = utf8 */;
 CREATE TABLE `platform_catalog` (
-                                    `id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                    `platformId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                    `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                    `parentId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
+                                    `id` varchar(50) NOT NULL,
+                                    `platformId` varchar(50) NOT NULL,
+                                    `name` varchar(255) NOT NULL,
+                                    `parentId` varchar(50) DEFAULT NULL,
                                     PRIMARY KEY (`id`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -360,14 +367,14 @@ UNLOCK TABLES;
 
 DROP TABLE IF EXISTS `platform_gb_channel`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!50503 SET character_set_client = utf8mb4 */;
+/*!40101 SET character_set_client = utf8 */;
 CREATE TABLE `platform_gb_channel` (
-                                       `id` int NOT NULL AUTO_INCREMENT,
-                                       `platformId` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
-                                       `catalogId` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
-                                       `deviceChannelId` int NOT NULL,
+                                       `id` int(11) NOT NULL AUTO_INCREMENT,
+                                       `platformId` varchar(50) NOT NULL,
+                                       `catalogId` varchar(50) NOT NULL,
+                                       `deviceChannelId` int(11) NOT NULL,
                                        PRIMARY KEY (`id`)
-) ENGINE=InnoDB AUTO_INCREMENT=47 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+) ENGINE=InnoDB AUTO_INCREMENT=250 DEFAULT CHARSET=utf8mb4;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -385,15 +392,15 @@ UNLOCK TABLES;
 
 DROP TABLE IF EXISTS `platform_gb_stream`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!50503 SET character_set_client = utf8mb4 */;
+/*!40101 SET character_set_client = utf8 */;
 CREATE TABLE `platform_gb_stream` (
-                                      `platformId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                      `catalogId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                      `gbStreamId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                      `id` int NOT NULL AUTO_INCREMENT,
+                                      `platformId` varchar(50) NOT NULL,
+                                      `catalogId` varchar(50) NOT NULL,
+                                      `gbStreamId` int(11) NOT NULL,
+                                      `id` int(11) NOT NULL AUTO_INCREMENT,
                                       PRIMARY KEY (`id`),
                                       UNIQUE KEY `platform_gb_stream_pk` (`platformId`,`catalogId`,`gbStreamId`)
-) ENGINE=InnoDB AUTO_INCREMENT=301207 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB AUTO_INCREMENT=301210 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -411,29 +418,29 @@ UNLOCK TABLES;
 
 DROP TABLE IF EXISTS `stream_proxy`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!50503 SET character_set_client = utf8mb4 */;
+/*!40101 SET character_set_client = utf8 */;
 CREATE TABLE `stream_proxy` (
-                                `id` int NOT NULL AUTO_INCREMENT,
-                                `type` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
-                                `app` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
-                                `name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
-                                `stream` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
-                                `url` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                `src_url` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                `dst_url` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                `timeout_ms` int DEFAULT NULL,
-                                `ffmpeg_cmd_key` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                `rtp_type` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                                `mediaServerId` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
+                                `id` int(11) NOT NULL AUTO_INCREMENT,
+                                `type` varchar(50) NOT NULL,
+                                `app` varchar(255) NOT NULL,
+                                `stream` varchar(255) NOT NULL,
+                                `url` varchar(255) DEFAULT NULL,
+                                `src_url` varchar(255) DEFAULT NULL,
+                                `dst_url` varchar(255) DEFAULT NULL,
+                                `timeout_ms` int(11) DEFAULT NULL,
+                                `ffmpeg_cmd_key` varchar(255) DEFAULT NULL,
+                                `rtp_type` varchar(50) DEFAULT NULL,
+                                `mediaServerId` varchar(50) DEFAULT NULL,
                                 `enable_hls` bit(1) DEFAULT NULL,
                                 `enable_mp4` bit(1) DEFAULT NULL,
                                 `enable` bit(1) NOT NULL,
                                 `status` bit(1) NOT NULL,
                                 `enable_remove_none_reader` bit(1) NOT NULL,
-                                `createTime` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
+                                `createTime` varchar(50) NOT NULL,
+                                `name` varchar(255) DEFAULT NULL,
                                 PRIMARY KEY (`id`),
                                 UNIQUE KEY `stream_proxy_pk` (`app`,`stream`)
-) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -451,20 +458,20 @@ UNLOCK TABLES;
 
 DROP TABLE IF EXISTS `stream_push`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!50503 SET character_set_client = utf8mb4 */;
+/*!40101 SET character_set_client = utf8 */;
 CREATE TABLE `stream_push` (
-                               `id` int NOT NULL AUTO_INCREMENT,
-                               `app` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
-                               `stream` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
-                               `totalReaderCount` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                               `originType` int DEFAULT NULL,
-                               `originTypeStr` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
-                               `createStamp` bigint DEFAULT NULL,
-                               `aliveSecond` int DEFAULT NULL,
-                               `mediaServerId` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
+                               `id` int(11) NOT NULL AUTO_INCREMENT,
+                               `app` varchar(255) NOT NULL,
+                               `stream` varchar(255) NOT NULL,
+                               `totalReaderCount` varchar(50) DEFAULT NULL,
+                               `originType` int(11) DEFAULT NULL,
+                               `originTypeStr` varchar(50) DEFAULT NULL,
+                               `createStamp` bigint(20) DEFAULT NULL,
+                               `aliveSecond` int(11) DEFAULT NULL,
+                               `mediaServerId` varchar(50) DEFAULT NULL,
                                PRIMARY KEY (`id`),
                                UNIQUE KEY `stream_push_pk` (`app`,`stream`)
-) ENGINE=InnoDB AUTO_INCREMENT=300799 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+) ENGINE=InnoDB AUTO_INCREMENT=300838 DEFAULT CHARSET=utf8mb4;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -482,17 +489,17 @@ UNLOCK TABLES;
 
 DROP TABLE IF EXISTS `user`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!50503 SET character_set_client = utf8mb4 */;
+/*!40101 SET character_set_client = utf8 */;
 CREATE TABLE `user` (
-                        `id` int NOT NULL AUTO_INCREMENT,
-                        `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                        `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                        `roleId` int NOT NULL,
-                        `createTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                        `updateTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
+                        `id` int(11) NOT NULL AUTO_INCREMENT,
+                        `username` varchar(255) NOT NULL,
+                        `password` varchar(255) NOT NULL,
+                        `roleId` int(11) NOT NULL,
+                        `createTime` varchar(50) NOT NULL,
+                        `updateTime` varchar(50) NOT NULL,
                         PRIMARY KEY (`id`) USING BTREE,
                         UNIQUE KEY `user_username_uindex` (`username`) USING BTREE
-) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -501,7 +508,8 @@ CREATE TABLE `user` (
 
 LOCK TABLES `user` WRITE;
 /*!40000 ALTER TABLE `user` DISABLE KEYS */;
-INSERT INTO `user` VALUES (1,'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021 - 04 - 13 14:14:57','2021 - 04 - 13 14:14:57');
+INSERT INTO `user` VALUES
+    (1,'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021 - 04 - 13 14:14:57','2021 - 04 - 13 14:14:57');
 /*!40000 ALTER TABLE `user` ENABLE KEYS */;
 UNLOCK TABLES;
 
@@ -511,15 +519,15 @@ UNLOCK TABLES;
 
 DROP TABLE IF EXISTS `user_role`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!50503 SET character_set_client = utf8mb4 */;
+/*!40101 SET character_set_client = utf8 */;
 CREATE TABLE `user_role` (
-                             `id` int NOT NULL AUTO_INCREMENT,
-                             `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                             `authority` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                             `createTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                             `updateTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
+                             `id` int(11) NOT NULL AUTO_INCREMENT,
+                             `name` varchar(50) NOT NULL,
+                             `authority` varchar(50) NOT NULL,
+                             `createTime` varchar(50) NOT NULL,
+                             `updateTime` varchar(50) NOT NULL,
                              PRIMARY KEY (`id`) USING BTREE
-) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -528,7 +536,8 @@ CREATE TABLE `user_role` (
 
 LOCK TABLES `user_role` WRITE;
 /*!40000 ALTER TABLE `user_role` DISABLE KEYS */;
-INSERT INTO `user_role` VALUES (1,'admin','0','2021-04-13 14:14:57','2021-04-13 14:14:57');
+INSERT INTO `user_role` VALUES
+    (1,'admin','0','2021-04-13 14:14:57','2021-04-13 14:14:57');
 /*!40000 ALTER TABLE `user_role` ENABLE KEYS */;
 UNLOCK TABLES;
 /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
@@ -541,4 +550,4 @@ UNLOCK TABLES;
 /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
 /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
 
+-- Dump completed on 2022-04-18 10:50:27

+ 12 - 0
sql/update.sql

@@ -0,0 +1,12 @@
+alter table parent_platform
+    add startOfflinePush int default 0 null;
+
+alter table parent_platform
+    add administrativeDivision varchar(50) not null;
+
+alter table parent_platform
+    add catalogGroup int default 1 null;
+
+alter table device
+    add ssrcCheck int default 0 null;
+

+ 6 - 2
src/main/java/com/genersoft/iot/vmp/common/ApiSaveConstant.java

@@ -38,10 +38,14 @@ public class ApiSaveConstant {
                             }
                             break;
                             case "query":
-                                if (keyItemArray.length <= 5) return null;
+                                if (keyItemArray.length <= 5) {
+                                    return null;
+                                }
                                 switch (keyItemArray[4]) {
                                     case "devices":
-                                        if (keyItemArray.length < 7) return null;
+                                        if (keyItemArray.length < 7) {
+                                            return null;
+                                        }
                                         switch (keyItemArray[6]) {
                                             case "sync":
                                                 return "[设备查询] 同步设备通道";

+ 28 - 0
src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java

@@ -31,6 +31,9 @@ public class StreamInfo {
     private String rtc;
     private String mediaServerId;
     private Object tracks;
+    private String startTime;
+    private String endTime;
+    private double progress;
 
     public static class TransactionInfo{
         public String callId;
@@ -264,4 +267,29 @@ public class StreamInfo {
     public void setHttps_ts(String https_ts) {
         this.https_ts = https_ts;
     }
+
+
+    public String getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(String startTime) {
+        this.startTime = startTime;
+    }
+
+    public String getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(String endTime) {
+        this.endTime = endTime;
+    }
+
+    public double getProgress() {
+        return progress;
+    }
+
+    public void setProgress(double progress) {
+        this.progress = progress;
+    }
 }

+ 24 - 1
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java

@@ -22,6 +22,9 @@ public class VideoManagerConstants {
 
 	public static final String DEVICE_PREFIX = "VMP_DEVICE_";
 
+	// 设备同步完成
+	public static final String DEVICE_SYNC_PREFIX = "VMP_DEVICE_SYNC_";
+
 	public static final String CACHEKEY_PREFIX = "VMP_CHANNEL_";
 
 	public static final String KEEPLIVEKEY_PREFIX = "VMP_KEEPALIVE_";
@@ -69,9 +72,29 @@ public class VideoManagerConstants {
 
 	public static final String SYSTEM_INFO_NET_PREFIX = "VMP_SYSTEM_INFO_NET_";
 
+
 	//************************** redis 消息*********************************
+
+	// 流变化的通知
 	public static final String WVP_MSG_STREAM_CHANGE_PREFIX = "WVP_MSG_STREAM_CHANGE_";
-	public static final String WVP_MSG_GPS_PREFIX = "VM_MSG_GPS";
+
+	// 接收推流设备的GPS变化通知
+	public static final String VM_MSG_GPS = "VM_MSG_GPS";
+
+	// redis 消息通知设备推流到平台
+	public static final String VM_MSG_STREAM_PUSH_REQUESTED = "VM_MSG_STREAM_PUSH_REQUESTED";
+
+	// 移动位置订阅通知
+	public static final String VM_MSG_SUBSCRIBE_MOBILE_POSITION = "mobileposition";
+
+	// 报警订阅的通知(收到报警向redis发出通知)
+	public static final String VM_MSG_SUBSCRIBE_ALARM = "alarm";
+
+	// 报警通知的发送 (收到redis发出的通知,转发给其他平台)
+	public static final String VM_MSG_SUBSCRIBE_ALARM_RECEIVE= "alarm_receive";
+
+	// 设备状态订阅的通知
+	public static final String VM_MSG_SUBSCRIBE_DEVICE_STATUS = "device";
 
 	//**************************    第三方  ****************************************
 	public static final String WVP_STREAM_GB_ID_PREFIX = "memberNo_";

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

@@ -29,7 +29,7 @@ public class ApiAccessFilter extends OncePerRequestFilter {
     private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 
     @Autowired
-    private UserSetup userSetup;
+    private UserSetting userSetting;
 
     @Autowired
     private ILogService logService;
@@ -48,7 +48,7 @@ public class ApiAccessFilter extends OncePerRequestFilter {
 
         filterChain.doFilter(servletRequest, servletResponse);
 
-        if (uriName != null && userSetup.getLogInDatebase()) {
+        if (uriName != null && userSetting.getLogInDatebase()) {
 
             LogDto logDto = new LogDto();
             logDto.setName(uriName);

+ 65 - 12
src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java

@@ -1,5 +1,9 @@
 package com.genersoft.iot.vmp.conf;
 
+import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
+import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler;
+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.concurrent.ThreadPoolTaskScheduler;
@@ -7,6 +11,7 @@ import org.springframework.stereotype.Component;
 
 import java.util.Date;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ScheduledFuture;
 
@@ -16,14 +21,22 @@ import java.util.concurrent.ScheduledFuture;
 @Component
 public class DynamicTask {
 
+    private Logger logger = LoggerFactory.getLogger(DynamicTask.class);
+
     @Autowired
     private ThreadPoolTaskScheduler threadPoolTaskScheduler;
 
     private Map<String, ScheduledFuture<?>> futureMap = new ConcurrentHashMap<>();
+    private Map<String, Runnable> runnableMap = new ConcurrentHashMap<>();
 
     @Bean
     public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
-        return new ThreadPoolTaskScheduler();
+        ThreadPoolTaskScheduler schedulerPool = new ThreadPoolTaskScheduler();
+        schedulerPool.setPoolSize(300);
+        schedulerPool.setWaitForTasksToCompleteOnShutdown(true);
+        schedulerPool.setAwaitTerminationSeconds(10);
+        return schedulerPool;
+
     }
 
     /**
@@ -33,33 +46,66 @@ public class DynamicTask {
      * @param cycleForCatalog 间隔
      * @return
      */
-    public String startCron(String key, Runnable task, int cycleForCatalog) {
-        stop(key);
+    public void startCron(String key, Runnable task, int cycleForCatalog) {
+        ScheduledFuture future = futureMap.get(key);
+        if (future != null) {
+            if (future.isCancelled()) {
+                logger.info("任务【{}】已存在但是关闭状态!!!", key);
+            } else {
+                logger.info("任务【{}】已存在且已启动!!!", key);
+                return;
+            }
+        }
         // scheduleWithFixedDelay 必须等待上一个任务结束才开始计时period, cycleForCatalog表示执行的间隔
-        ScheduledFuture future = threadPoolTaskScheduler.scheduleWithFixedDelay(task, cycleForCatalog * 1000L);
-        futureMap.put(key, future);
-        return "startCron";
+        future = threadPoolTaskScheduler.scheduleAtFixedRate(task, cycleForCatalog * 1000L);
+        if (future != null){
+            futureMap.put(key, future);
+            runnableMap.put(key, task);
+            logger.info("任务【{}】启动成功!!!", key);
+        }else {
+            logger.info("任务【{}】启动失败!!!", key);
+        }
     }
 
     /**
      * 延时任务
      * @param key 任务ID
      * @param task 任务
-     * @param delay 延时 /秒
+     * @param delay 延时 /
      * @return
      */
-    public String startDelay(String key, Runnable task, int delay) {
+    public void startDelay(String key, Runnable task, int delay) {
         stop(key);
-        Date starTime = new Date(System.currentTimeMillis() + delay * 1000);
+        Date starTime = new Date(System.currentTimeMillis() + delay);
+
+        ScheduledFuture future = futureMap.get(key);
+        if (future != null) {
+            if (future.isCancelled()) {
+                logger.info("任务【{}】已存在但是关闭状态!!!", key);
+            } else {
+                logger.info("任务【{}】已存在且已启动!!!", key);
+                return;
+            }
+        }
         // scheduleWithFixedDelay 必须等待上一个任务结束才开始计时period, cycleForCatalog表示执行的间隔
-        ScheduledFuture future = threadPoolTaskScheduler.schedule(task, starTime);
-        futureMap.put(key, future);
-        return "startCron";
+        future = threadPoolTaskScheduler.schedule(task, starTime);
+        if (future != null){
+            futureMap.put(key, future);
+            runnableMap.put(key, task);
+            logger.info("任务【{}】启动成功!!!", key);
+        }else {
+            logger.info("任务【{}】启动失败!!!", key);
+        }
     }
 
     public void stop(String key) {
         if (futureMap.get(key) != null && !futureMap.get(key).isCancelled()) {
             futureMap.get(key).cancel(true);
+            Runnable runnable = runnableMap.get(key);
+            if (runnable instanceof ISubscribeTask) {
+                ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
+                subscribeTask.stop();
+            }
         }
     }
 
@@ -67,4 +113,11 @@ public class DynamicTask {
         return futureMap.get(key) != null;
     }
 
+    public Set<String> getAllKeys() {
+        return futureMap.keySet();
+    }
+
+    public Runnable get(String key) {
+        return runnableMap.get(key);
+    }
 }

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

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.conf;
 
 import com.genersoft.iot.vmp.common.VideoManagerConstants;
+import com.genersoft.iot.vmp.service.impl.RedisAlarmMsgListener;
 import com.genersoft.iot.vmp.service.impl.RedisGPSMsgListener;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -48,6 +49,9 @@ public class RedisConfig extends CachingConfigurerSupport {
 	@Autowired
 	private RedisGPSMsgListener redisGPSMsgListener;
 
+	@Autowired
+	private RedisAlarmMsgListener redisAlarmMsgListener;
+
 	@Bean
 	public JedisPool jedisPool() {
 		if (StringUtils.isBlank(password)) {
@@ -92,7 +96,8 @@ public class RedisConfig extends CachingConfigurerSupport {
 
         RedisMessageListenerContainer container = new RedisMessageListenerContainer();
         container.setConnectionFactory(connectionFactory);
-		container.addMessageListener(redisGPSMsgListener, new PatternTopic(VideoManagerConstants.WVP_MSG_GPS_PREFIX));
+		container.addMessageListener(redisGPSMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_GPS));
+		container.addMessageListener(redisAlarmMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_SUBSCRIBE_ALARM_RECEIVE));
         return container;
     }
 

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

@@ -3,25 +3,24 @@ package com.genersoft.iot.vmp.conf;
 import org.springframework.data.redis.connection.RedisConnection;
 import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
 import org.springframework.data.redis.listener.RedisMessageListenerContainer;
-import org.springframework.util.StringUtils;
 
 import java.util.Properties;
 
 public class RedisKeyExpirationEventMessageListener extends KeyExpirationEventMessageListener {
 
-    private UserSetup userSetup;
+    private UserSetting userSetting;
     private RedisMessageListenerContainer listenerContainer;
     private String keyspaceNotificationsConfigParameter = "EA";
 
-    public RedisKeyExpirationEventMessageListener(RedisMessageListenerContainer listenerContainer, UserSetup userSetup) {
+    public RedisKeyExpirationEventMessageListener(RedisMessageListenerContainer listenerContainer, UserSetting userSetting) {
         super(listenerContainer);
         this.listenerContainer = listenerContainer;
-        this.userSetup = userSetup;
+        this.userSetting = userSetting;
     }
 
     @Override
     public void init() {
-        if (!userSetup.getRedisConfig()) {
+        if (!userSetting.getRedisConfig()) {
             // 配置springboot默认Config为空,即不让应用去修改redis的默认配置,因为Redis服务出于安全会禁用CONFIG命令给远程用户使用
             setKeyspaceNotificationsConfigParameter("");
         }else {

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

@@ -29,7 +29,7 @@ public class SipConfig {
 
 	Integer registerTimeInterval = 120;
 
-	private boolean alarm = false;
+	private boolean alarm;
 
 	public void setIp(String ip) {
 		this.ip = ip;

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

@@ -4,9 +4,8 @@ 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.media.zlm.ZLMRTPServerFactory;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.CommandLineRunner;
 import org.springframework.core.annotation.Order;
@@ -22,7 +21,7 @@ import java.util.List;
 public class SipPlatformRunner implements CommandLineRunner {
 
     @Autowired
-    private IVideoManagerStorager storager;
+    private IVideoManagerStorage storager;
 
     @Autowired
     private IRedisCatchStorage redisCatchStorage;

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

@@ -7,6 +7,10 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
 import java.util.concurrent.ThreadPoolExecutor;
 
+/**
+ *  ThreadPoolTask 配置类
+ * @author lin
+ */
 @Configuration
 @EnableAsync(proxyTargetClass = true)
 public class ThreadPoolTaskConfig {
@@ -40,6 +44,10 @@ public class ThreadPoolTaskConfig {
      */
     private static final String threadNamePrefix = "wvp-";
 
+    /**
+     *
+     * @return
+     */
     @Bean("taskExecutor") // bean的名称,默认为首字母小写的方法名
     public ThreadPoolTaskExecutor taskExecutor() {
         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

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

@@ -1,15 +1,18 @@
 package com.genersoft.iot.vmp.conf;
 
+import io.swagger.models.auth.In;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
 
 import java.util.ArrayList;
 import java.util.List;
 
-
+/**
+ * 配置文件 user-settings 映射的配置信息
+ */
 @Component
 @ConfigurationProperties(prefix = "user-settings", ignoreInvalidFields = true)
-public class UserSetup {
+public class UserSetting {
 
     private Boolean savePositionHistory = Boolean.FALSE;
 
@@ -17,7 +20,9 @@ public class UserSetup {
 
     private Boolean seniorSdp = Boolean.FALSE;
 
-    private Long playTimeout = 18000L;
+    private Integer playTimeout = 18000;
+
+    private int platformPlayTimeout = 60000;
 
     private Boolean interfaceAuthentication = Boolean.TRUE;
 
@@ -51,7 +56,7 @@ public class UserSetup {
         return seniorSdp;
     }
 
-    public Long getPlayTimeout() {
+    public Integer getPlayTimeout() {
         return playTimeout;
     }
 
@@ -79,7 +84,7 @@ public class UserSetup {
         this.seniorSdp = seniorSdp;
     }
 
-    public void setPlayTimeout(Long playTimeout) {
+    public void setPlayTimeout(Integer playTimeout) {
         this.playTimeout = playTimeout;
     }
 
@@ -134,4 +139,12 @@ public class UserSetup {
     public void setRecordSip(Boolean recordSip) {
         this.recordSip = recordSip;
     }
+
+    public int getPlatformPlayTimeout() {
+        return platformPlayTimeout;
+    }
+
+    public void setPlatformPlayTimeout(int platformPlayTimeout) {
+        this.platformPlayTimeout = platformPlayTimeout;
+    }
 }

+ 13 - 8
src/main/java/com/genersoft/iot/vmp/conf/runner/SipDeviceRunner.java

@@ -1,10 +1,10 @@
 package com.genersoft.iot.vmp.conf.runner;
 
-import com.genersoft.iot.vmp.conf.UserSetup;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.service.IDeviceService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.CommandLineRunner;
 import org.springframework.core.annotation.Order;
@@ -14,20 +14,20 @@ import java.util.List;
 
 
 /**
- * 系统启动时控制设备离线
+ * 系统启动时控制设备
  */
 @Component
 @Order(value=4)
 public class SipDeviceRunner implements CommandLineRunner {
 
     @Autowired
-    private IVideoManagerStorager storager;
+    private IVideoManagerStorage storager;
 
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
 
     @Autowired
-    private UserSetup userSetup;
+    private UserSetting userSetting;
 
     @Autowired
     private IDeviceService deviceService;
@@ -41,9 +41,14 @@ public class SipDeviceRunner implements CommandLineRunner {
         for (String deviceId : onlineForAll) {
             storager.online(deviceId);
             Device device = redisCatchStorage.getDevice(deviceId);
-            if (device != null && device.getSubscribeCycleForCatalog() > 0) {
-                // 查询在线设备那些开启了订阅,为设备开启定时的目录订阅
-                deviceService.addCatalogSubscribe(device);
+            if (device != null ) {
+                if (device.getSubscribeCycleForCatalog() > 0) {
+                    // 查询在线设备那些开启了订阅,为设备开启定时的目录订阅
+                    deviceService.addCatalogSubscribe(device);
+                }
+                if (device.getSubscribeCycleForMobilePosition() > 0) {
+                    deviceService.addMobilePositionSubscribe(device);
+                }
             }
         }
         // 重置cseq计数

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

@@ -1,6 +1,6 @@
 package com.genersoft.iot.vmp.conf.security;
 
-import com.genersoft.iot.vmp.conf.UserSetup;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -29,7 +29,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
     private final static Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);
 
     @Autowired
-    private UserSetup userSetup;
+    private UserSetting userSetting;
 
     @Autowired
     private DefaultUserDetailsServiceImpl userDetailsService;
@@ -77,7 +77,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
     @Override
     public void configure(WebSecurity web) {
 
-        if (!userSetup.isInterfaceAuthentication()) {
+        if (!userSetting.isInterfaceAuthentication()) {
             web.ignoring().antMatchers("**");
         }else {
             // 可以直接访问的静态数据
@@ -91,7 +91,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
                     .antMatchers("/swagger-resources/**")
                     .antMatchers("/v3/api-docs/**")
                     .antMatchers("/js/**");
-            List<String> interfaceAuthenticationExcludes = userSetup.getInterfaceAuthenticationExcludes();
+            List<String> interfaceAuthenticationExcludes = userSetting.getInterfaceAuthenticationExcludes();
             for (String interfaceAuthenticationExclude : interfaceAuthenticationExcludes) {
                 if (interfaceAuthenticationExclude.split("/").length < 4 ) {
                     logger.warn("{}不满足两级目录,已忽略", interfaceAuthenticationExclude);

+ 7 - 5
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java

@@ -8,6 +8,7 @@ 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.stereotype.Component;
 
@@ -18,7 +19,7 @@ import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
-@Component
+@Configuration
 public class SipLayer{
 
 	private final static Logger logger = LoggerFactory.getLogger(SipLayer.class);
@@ -35,7 +36,7 @@ public class SipLayer{
 
 
 	@Bean("sipFactory")
-	private SipFactory createSipFactory() {
+	SipFactory createSipFactory() {
 		sipFactory = SipFactory.getInstance();
 		sipFactory.setPathName("gov.nist");
 		return sipFactory;
@@ -43,11 +44,12 @@ public class SipLayer{
 	
 	@Bean("sipStack")
 	@DependsOn({"sipFactory"})
-	private SipStack createSipStack() throws PeerUnavailableException {
+	SipStack createSipStack() throws PeerUnavailableException {
 		Properties properties = new Properties();
 		properties.setProperty("javax.sip.STACK_NAME", "GB28181_SIP");
 		properties.setProperty("javax.sip.IP_ADDRESS", sipConfig.getMonitorIp());
 		properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "true");
+		properties.setProperty("gov.nist.javax.sip.DELIVER_UNSOLICITED_NOTIFY", "true"); // 接收所有notify请求,即使没有订阅
 		/**
 		 * sip_server_log.log 和 sip_debug_log.log public static final int TRACE_NONE =
 		 * 0; public static final int TRACE_MESSAGES = 16; public static final int
@@ -63,7 +65,7 @@ public class SipLayer{
 
 	@Bean(name = "tcpSipProvider")
 	@DependsOn("sipStack")
-	private SipProviderImpl startTcpListener() {
+	SipProviderImpl startTcpListener() {
 		ListeningPoint tcpListeningPoint = null;
 		SipProviderImpl tcpSipProvider  = null;
 		try {
@@ -88,7 +90,7 @@ public class SipLayer{
 	
 	@Bean(name = "udpSipProvider")
 	@DependsOn("sipStack")
-	private SipProviderImpl startUdpListener() {
+	SipProviderImpl startUdpListener() {
 		ListeningPoint udpListeningPoint = null;
 		SipProviderImpl udpSipProvider = null;
 		try {

+ 6 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java

@@ -129,7 +129,9 @@ public class DigestServerAuthenticationHelper  {
      */
     public boolean doAuthenticateHashedPassword(Request request, String hashedPassword) {
         AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
-        if ( authHeader == null ) return false;
+        if ( authHeader == null ) {
+            return false;
+        }
         String realm = authHeader.getRealm();
         String username = authHeader.getUsername();
 
@@ -176,7 +178,9 @@ public class DigestServerAuthenticationHelper  {
      */
     public boolean doAuthenticatePlainTextPassword(Request request, String pass) {
         AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
-        if ( authHeader == null ) return false;
+        if ( authHeader == null ) {
+            return false;
+        }
         String realm = authHeader.getRealm().trim();
         String username = authHeader.getUsername().trim();
 

+ 2 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/auth/RegisterLogicHandler.java

@@ -1,6 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.auth;
 
-import com.genersoft.iot.vmp.storager.impl.VideoManagerStoragerImpl;
+import com.genersoft.iot.vmp.storager.impl.VideoManagerStorageImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -23,7 +23,7 @@ public class RegisterLogicHandler {
 	private SIPCommander cmder;
 
 	@Autowired
-	private VideoManagerStoragerImpl storager;
+	private VideoManagerStorageImpl storager;
 	
 	public void onRegister(Device device) {
 		// 只有第一次注册时调用查询设备信息,如需更新调用更新API接口

+ 46 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/AlarmChannelMessage.java

@@ -0,0 +1,46 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+/**
+ * 通过redis分发报警消息
+ */
+public class AlarmChannelMessage {
+    /**
+     * 国标编号
+     */
+    private String gbId;
+
+    /**
+     * 报警编号
+     */
+    private int alarmSn;
+
+
+    /**
+     * 报警描述
+     */
+    private String alarmDescription;
+
+    public String getGbId() {
+        return gbId;
+    }
+
+    public void setGbId(String gbId) {
+        this.gbId = gbId;
+    }
+
+    public int getAlarmSn() {
+        return alarmSn;
+    }
+
+    public void setAlarmSn(int alarmSn) {
+        this.alarmSn = alarmSn;
+    }
+
+    public String getAlarmDescription() {
+        return alarmDescription;
+    }
+
+    public void setAlarmDescription(String alarmDescription) {
+        this.alarmDescription = alarmDescription;
+    }
+}

+ 32 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/CatalogData.java

@@ -4,10 +4,26 @@ import java.util.Date;
 import java.util.List;
 
 public class CatalogData {
+    private int sn; // 命令序列号
     private int total;
     private List<DeviceChannel> channelList;
     private Date lastTime;
     private Device device;
+    private String errorMsg;
+
+    public enum CatalogDataStatus{
+        ready, runIng, end
+    }
+    private CatalogDataStatus status;
+
+
+    public int getSn() {
+        return sn;
+    }
+
+    public void setSn(int sn) {
+        this.sn = sn;
+    }
 
     public int getTotal() {
         return total;
@@ -40,4 +56,20 @@ public class CatalogData {
     public void setDevice(Device device) {
         this.device = device;
     }
+
+    public String getErrorMsg() {
+        return errorMsg;
+    }
+
+    public void setErrorMsg(String errorMsg) {
+        this.errorMsg = errorMsg;
+    }
+
+    public CatalogDataStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(CatalogDataStatus status) {
+        this.status = status;
+    }
 }

+ 53 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java

@@ -105,15 +105,34 @@ public class Device {
 	private boolean firsRegister;
 
 	/**
-	 * 字符集, 支持 utf-8 与 gb2312
+	 * 字符集, 支持 UTF-8 与 GB2312
 	 */
 	private String charset ;
 
 	/**
 	 * 目录订阅周期,0为不订阅
 	 */
-	private int subscribeCycleForCatalog ;
+	private int subscribeCycleForCatalog;
 
+	/**
+	 * 移动设备位置订阅周期,0为不订阅
+	 */
+	private int subscribeCycleForMobilePosition;
+
+	/**
+	 * 移动设备位置信息上报时间间隔,单位:秒,默认值5
+	 */
+	private int mobilePositionSubmissionInterval = 5;
+
+	/**
+	 * 报警订阅周期,0为不订阅
+	 */
+	private int subscribeCycleForAlarm;
+
+	/**
+	 * 是否开启ssrc校验,默认关闭,开启可以防止串流
+	 */
+	private boolean ssrcCheck;
 
 
 	public String getDeviceId() {
@@ -283,4 +302,36 @@ public class Device {
 	public void setSubscribeCycleForCatalog(int subscribeCycleForCatalog) {
 		this.subscribeCycleForCatalog = subscribeCycleForCatalog;
 	}
+
+	public int getSubscribeCycleForMobilePosition() {
+		return subscribeCycleForMobilePosition;
+	}
+
+	public void setSubscribeCycleForMobilePosition(int subscribeCycleForMobilePosition) {
+		this.subscribeCycleForMobilePosition = subscribeCycleForMobilePosition;
+	}
+
+	public int getMobilePositionSubmissionInterval() {
+		return mobilePositionSubmissionInterval;
+	}
+
+	public void setMobilePositionSubmissionInterval(int mobilePositionSubmissionInterval) {
+		this.mobilePositionSubmissionInterval = mobilePositionSubmissionInterval;
+	}
+
+	public int getSubscribeCycleForAlarm() {
+		return subscribeCycleForAlarm;
+	}
+
+	public void setSubscribeCycleForAlarm(int subscribeCycleForAlarm) {
+		this.subscribeCycleForAlarm = subscribeCycleForAlarm;
+	}
+
+	public boolean isSsrcCheck() {
+		return ssrcCheck;
+	}
+
+	public void setSsrcCheck(boolean ssrcCheck) {
+		this.ssrcCheck = ssrcCheck;
+	}
 }

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

@@ -50,7 +50,30 @@ public class DeviceAlarm {
 	private double latitude;
 
 	/**
-	 * 报警类型
+	 * 报警类型,
+	 * 报警方式为2时,不携带 AlarmType为默认的报警设备报警,
+	 * 携带 AlarmType取值及对应报警类型如下:
+	 * 		1-视频丢失报警;
+	 * 		2-设备防拆报警;
+	 * 		3-存储设备磁盘满报警;
+	 * 		4-设备高温报警;
+	 * 		5-设备低温报警。
+	 * 报警方式为5时,取值如下:
+	 * 		1-人工视频报警;
+	 * 		2-运动目标检测报警;
+	 * 		3-遗留物检测报警;
+	 * 		4-物体移除检测报警;
+	 * 		5-绊线检测报警;
+	 * 		6-入侵检测报警;
+	 * 		7-逆行检测报警;
+	 * 		8-徘徊检测报警;
+	 * 		9-流量统计报警;
+	 * 		10-密度检测报警;
+	 * 		11-视频异常检测报警;
+	 * 		12-快速移动报警。
+	 * 报警方式为6时,取值下:
+	 * 		1-存储设备磁盘故障报警;
+	 * 		2-存储设备风扇故障报警。
 	 */
 	private String alarmType;
 

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

@@ -4,7 +4,7 @@ public class DeviceChannel {
 
 
 	/**
-	 * 数据库自ID
+	 * 数据库自ID
 	 */
 	private int id;
 
@@ -169,6 +169,11 @@ public class DeviceChannel {
 	 */
 	private boolean hasAudio;
 
+	/**
+	 * 标记通道的类型,0->国标通道 1->直播流通道 2->业务分组/虚拟组织/行政区划
+	 */
+	private int channelType;
+
 	public int getId() {
 		return id;
 	}
@@ -441,4 +446,12 @@ public class DeviceChannel {
 	public void setUpdateTime(String updateTime) {
 		this.updateTime = updateTime;
 	}
+
+	public int getChannelType() {
+		return channelType;
+	}
+
+	public void setChannelType(int channelType) {
+		this.channelType = channelType;
+	}
 }

+ 23 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannelInPlatform.java

@@ -0,0 +1,23 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+public class DeviceChannelInPlatform extends DeviceChannel{
+
+	private String platFormId;
+	private String catalogId;
+
+	public String getPlatFormId() {
+		return platFormId;
+	}
+
+	public void setPlatFormId(String platFormId) {
+		this.platFormId = platFormId;
+	}
+
+	public String getCatalogId() {
+		return catalogId;
+	}
+
+	public void setCatalogId(String catalogId) {
+		this.catalogId = catalogId;
+	}
+}

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

@@ -25,6 +25,7 @@ public class GbStream extends PlatformGbStream{
         return gbStreamId;
     }
 
+    @Override
     public void setGbStreamId(Integer gbStreamId) {
         this.gbStreamId = gbStreamId;
     }

+ 8 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamType.java

@@ -0,0 +1,8 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+public enum InviteStreamType {
+
+    PLAY,PLAYBACK,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY
+
+
+}

+ 45 - 6
src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java

@@ -125,9 +125,24 @@ public class ParentPlatform {
     private boolean alarmSubscribe;
 
     /**
-     * 已被订阅GPS信息
+     * 已被订阅移动位置信息
      */
-    private boolean gpsSubscribe;
+    private boolean mobilePositionSubscribe;
+
+    /**
+     * 点播未推流的设备时是否使用redis通知拉起
+     */
+    private boolean startOfflinePush;
+
+    /**
+     * 目录分组-每次向上级发送通道信息时单个包携带的通道数量,取值1,2,4,8
+     */
+    private int catalogGroup;
+
+    /**
+     * 行政区划
+     */
+    private String administrativeDivision;
 
     public Integer getId() {
         return id;
@@ -322,11 +337,35 @@ public class ParentPlatform {
         this.alarmSubscribe = alarmSubscribe;
     }
 
-    public boolean isGpsSubscribe() {
-        return gpsSubscribe;
+    public boolean isMobilePositionSubscribe() {
+        return mobilePositionSubscribe;
+    }
+
+    public void setMobilePositionSubscribe(boolean mobilePositionSubscribe) {
+        this.mobilePositionSubscribe = mobilePositionSubscribe;
+    }
+
+    public boolean isStartOfflinePush() {
+        return startOfflinePush;
+    }
+
+    public void setStartOfflinePush(boolean startOfflinePush) {
+        this.startOfflinePush = startOfflinePush;
+    }
+
+    public int getCatalogGroup() {
+        return catalogGroup;
+    }
+
+    public void setCatalogGroup(int catalogGroup) {
+        this.catalogGroup = catalogGroup;
+    }
+
+    public String getAdministrativeDivision() {
+        return administrativeDivision;
     }
 
-    public void setGpsSubscribe(boolean gpsSubscribe) {
-        this.gpsSubscribe = gpsSubscribe;
+    public void setAdministrativeDivision(String administrativeDivision) {
+        this.administrativeDivision = administrativeDivision;
     }
 }

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

@@ -77,9 +77,9 @@ public class SendRtpItem {
     private String CallId;
 
     /**
-     * 是否是play, false是playback
+     * 播放类型
      */
-    private boolean isPlay;
+    private InviteStreamType playType;
 
     private byte[] transaction;
 
@@ -197,12 +197,12 @@ public class SendRtpItem {
         CallId = callId;
     }
 
-    public boolean isPlay() {
-        return isPlay;
+    public InviteStreamType getPlayType() {
+        return playType;
     }
 
-    public void setPlay(boolean play) {
-        isPlay = play;
+    public void setPlayType(InviteStreamType playType) {
+        this.playType = playType;
     }
 
     public byte[] getTransaction() {

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

@@ -1,5 +1,7 @@
 package com.genersoft.iot.vmp.gb28181.bean;
 
+import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
+
 public class SsrcTransaction {
 
     private String deviceId;
@@ -10,6 +12,7 @@ public class SsrcTransaction {
     private byte[] dialog;
     private String mediaServerId;
     private String ssrc;
+    private VideoStreamSessionManager.SessionType type;
 
     public String getDeviceId() {
         return deviceId;
@@ -74,4 +77,12 @@ public class SsrcTransaction {
     public void setSsrc(String ssrc) {
         this.ssrc = ssrc;
     }
+
+    public VideoStreamSessionManager.SessionType getType() {
+        return type;
+    }
+
+    public void setType(VideoStreamSessionManager.SessionType type) {
+        this.type = type;
+    }
 }

+ 54 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java

@@ -1,5 +1,12 @@
 package com.genersoft.iot.vmp.gb28181.bean;
 
+import com.genersoft.iot.vmp.common.VideoManagerConstants;
+import com.genersoft.iot.vmp.conf.DynamicTask;
+import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeHandlerTask;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
+import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import java.util.ArrayList;
@@ -9,12 +16,32 @@ import java.util.concurrent.ConcurrentHashMap;
 @Component
 public class SubscribeHolder {
 
+    @Autowired
+    private DynamicTask dynamicTask;
+
+    @Autowired
+    private IRedisCatchStorage redisCatchStorage;
+
+    @Autowired
+    private ISIPCommanderForPlatform sipCommanderForPlatform;
+
+    @Autowired
+    private IVideoManagerStorage storager;
+
+    private final String taskOverduePrefix = "subscribe_overdue_";
+
     private static ConcurrentHashMap<String, SubscribeInfo> catalogMap = new ConcurrentHashMap<>();
     private static ConcurrentHashMap<String, SubscribeInfo> mobilePositionMap = new ConcurrentHashMap<>();
 
 
     public void putCatalogSubscribe(String platformId, SubscribeInfo subscribeInfo) {
         catalogMap.put(platformId, subscribeInfo);
+        // 添加订阅到期
+        String taskOverdueKey = taskOverduePrefix +  "catalog_" + platformId;
+        dynamicTask.stop(taskOverdueKey);
+        // 添加任务处理订阅过期
+        dynamicTask.startDelay(taskOverdueKey, () -> removeCatalogSubscribe(subscribeInfo.getId()),
+                subscribeInfo.getExpires() * 1000);
     }
 
     public SubscribeInfo getCatalogSubscribe(String platformId) {
@@ -23,10 +50,26 @@ public class SubscribeHolder {
 
     public void removeCatalogSubscribe(String platformId) {
         catalogMap.remove(platformId);
+        String taskOverdueKey = taskOverduePrefix +  "catalog_" + platformId;
+        // 添加任务处理订阅过期
+        dynamicTask.stop(taskOverdueKey);
     }
 
     public void putMobilePositionSubscribe(String platformId, SubscribeInfo subscribeInfo) {
         mobilePositionMap.put(platformId, subscribeInfo);
+        String key = VideoManagerConstants.SIP_SUBSCRIBE_PREFIX +  "MobilePosition_" + platformId;
+        // 添加任务处理GPS定时推送
+        dynamicTask.startCron(key, new MobilePositionSubscribeHandlerTask(redisCatchStorage, sipCommanderForPlatform,
+                storager,  platformId, subscribeInfo.getSn(), key, this, dynamicTask),
+                subscribeInfo.getGpsInterval());
+        String taskOverdueKey = taskOverduePrefix +  "MobilePosition_" + platformId;
+        dynamicTask.stop(taskOverdueKey);
+        // 添加任务处理订阅过期
+        dynamicTask.startDelay(taskOverdueKey, () -> {
+                    System.out.println("订阅过期");
+                    removeMobilePositionSubscribe(subscribeInfo.getId());
+                },
+                subscribeInfo.getExpires() * 1000);
     }
 
     public SubscribeInfo getMobilePositionSubscribe(String platformId) {
@@ -35,6 +78,12 @@ public class SubscribeHolder {
 
     public void removeMobilePositionSubscribe(String platformId) {
         mobilePositionMap.remove(platformId);
+        String key = VideoManagerConstants.SIP_SUBSCRIBE_PREFIX +  "MobilePosition_" + platformId;
+        // 结束任务处理GPS定时推送
+        dynamicTask.stop(key);
+        String taskOverdueKey = taskOverduePrefix +  "MobilePosition_" + platformId;
+        // 添加任务处理订阅过期
+        dynamicTask.stop(taskOverdueKey);
     }
 
     public List<String> getAllCatalogSubscribePlatform() {
@@ -46,4 +95,9 @@ public class SubscribeHolder {
         }
         return platforms;
     }
+
+    public void removeAllSubscribe(String platformId) {
+        removeMobilePositionSubscribe(platformId);
+        removeCatalogSubscribe(platformId);
+    }
 }

+ 27 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeInfo.java

@@ -25,6 +25,9 @@ public class SubscribeInfo {
         this.callId = callIdHeader.getCallId();
     }
 
+    public SubscribeInfo() {
+    }
+
     private String id;
     private int expires;
     private String callId;
@@ -33,6 +36,14 @@ public class SubscribeInfo {
     private ServerTransaction transaction;
     private Dialog dialog;
 
+    /**
+     * 以下为可选字段
+     * @return
+     */
+    private String sn;
+    private int gpsInterval;
+
+
     public String getId() {
         return id;
     }
@@ -88,4 +99,20 @@ public class SubscribeInfo {
     public void setDialog(Dialog dialog) {
         this.dialog = dialog;
     }
+
+    public String getSn() {
+        return sn;
+    }
+
+    public void setSn(String sn) {
+        this.sn = sn;
+    }
+
+    public int getGpsInterval() {
+        return gpsInterval;
+    }
+
+    public void setGpsInterval(int gpsInterval) {
+        this.gpsInterval = gpsInterval;
+    }
 }

+ 34 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SyncStatus.java

@@ -0,0 +1,34 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+/**
+ * 摄像机同步状态
+ */
+public class SyncStatus {
+    private int total;
+    private int current;
+    private String errorMsg;
+
+    public int getTotal() {
+        return total;
+    }
+
+    public void setTotal(int total) {
+        this.total = total;
+    }
+
+    public int getCurrent() {
+        return current;
+    }
+
+    public void setCurrent(int current) {
+        this.current = current;
+    }
+
+    public String getErrorMsg() {
+        return errorMsg;
+    }
+
+    public void setErrorMsg(String errorMsg) {
+        this.errorMsg = errorMsg;
+    }
+}

+ 0 - 28
src/main/java/com/genersoft/iot/vmp/gb28181/event/DeviceOffLineDetector.java

@@ -1,28 +0,0 @@
-package com.genersoft.iot.vmp.gb28181.event;
-
-import com.genersoft.iot.vmp.conf.UserSetup;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import com.genersoft.iot.vmp.common.VideoManagerConstants;
-import com.genersoft.iot.vmp.utils.redis.RedisUtil;
-
-/**    
- * @description:设备离在线状态检测器,用于检测设备状态
- * @author: swwheihei
- * @date:   2020年5月13日 下午2:40:29     
- */
-@Component
-public class DeviceOffLineDetector {
-
-	@Autowired
-    private RedisUtil redis;
-
-	@Autowired
-    private UserSetup userSetup;
-	
-	public boolean isOnline(String deviceId) {
-		String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + userSetup.getServerId() + "_" + deviceId;
-		return redis.hasKey(key);
-	}
-}

+ 18 - 24
src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/KeepaliveTimeoutListenerForPlatform.java

@@ -1,27 +1,20 @@
 package com.genersoft.iot.vmp.gb28181.event.offline;
 
 import com.genersoft.iot.vmp.conf.RedisKeyExpirationEventMessageListener;
-import com.genersoft.iot.vmp.conf.UserSetup;
+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.SipSubscribe;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.DependsOn;
 import org.springframework.data.redis.connection.Message;
-import org.springframework.data.redis.connection.RedisConnection;
-import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
 import org.springframework.data.redis.listener.RedisMessageListenerContainer;
 import org.springframework.stereotype.Component;
 
 import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
-import org.springframework.util.StringUtils;
-
-import java.util.Properties;
 
 /**    
  * @description:设备心跳超时监听,借助redis过期特性,进行监听,监听到说明设备心跳超时,发送离线事件
@@ -37,16 +30,16 @@ public class KeepaliveTimeoutListenerForPlatform extends RedisKeyExpirationEvent
 	private EventPublisher publisher;
 
 	@Autowired
-	private UserSetup userSetup;
+	private UserSetting userSetting;
 
 	@Autowired
 	private SipSubscribe sipSubscribe;
 
 	@Autowired
-	private IVideoManagerStorager storager;
+	private IVideoManagerStorage storager;
 
-    public KeepaliveTimeoutListenerForPlatform(RedisMessageListenerContainer listenerContainer, UserSetup userSetup) {
-        super(listenerContainer, userSetup);
+    public KeepaliveTimeoutListenerForPlatform(RedisMessageListenerContainer listenerContainer, UserSetting userSetting) {
+        super(listenerContainer, userSetting);
     }
 
 
@@ -59,12 +52,11 @@ public class KeepaliveTimeoutListenerForPlatform extends RedisKeyExpirationEvent
     public void onMessage(Message message, byte[] pattern) {
         //  获取失效的key
         String expiredKey = message.toString();
-        logger.debug(expiredKey);
         // 平台心跳到期,需要重发, 判断是否已经多次未收到心跳回复, 多次未收到,则重新发起注册, 注册尝试多次未得到回复,则认为平台离线
-        String PLATFORM_KEEPLIVEKEY_PREFIX = VideoManagerConstants.PLATFORM_KEEPALIVE_PREFIX + userSetup.getServerId() + "_";
-        String PLATFORM_REGISTER_PREFIX = VideoManagerConstants.PLATFORM_REGISTER_PREFIX + userSetup.getServerId() + "_";
-        String KEEPLIVEKEY_PREFIX = VideoManagerConstants.KEEPLIVEKEY_PREFIX + userSetup.getServerId() + "_";
-        String REGISTER_INFO_PREFIX = VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + userSetup.getServerId() + "_";
+        String PLATFORM_KEEPLIVEKEY_PREFIX = VideoManagerConstants.PLATFORM_KEEPALIVE_PREFIX + userSetting.getServerId() + "_";
+        String PLATFORM_REGISTER_PREFIX = VideoManagerConstants.PLATFORM_REGISTER_PREFIX + userSetting.getServerId() + "_";
+        String KEEPLIVEKEY_PREFIX = VideoManagerConstants.KEEPLIVEKEY_PREFIX + userSetting.getServerId() + "_";
+        String REGISTER_INFO_PREFIX = VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + userSetting.getServerId() + "_";
         if (expiredKey.startsWith(PLATFORM_KEEPLIVEKEY_PREFIX)) {
             String platformGBId = expiredKey.substring(PLATFORM_KEEPLIVEKEY_PREFIX.length(),expiredKey.length());
             ParentPlatform platform = storager.queryParentPlatByServerGBId(platformGBId);
@@ -84,12 +76,14 @@ public class KeepaliveTimeoutListenerForPlatform extends RedisKeyExpirationEvent
                 publisher.outlineEventPublish(deviceId, KEEPLIVEKEY_PREFIX);
             }
         }else if (expiredKey.startsWith(REGISTER_INFO_PREFIX)) {
-            String callid = expiredKey.substring(REGISTER_INFO_PREFIX.length());
-            SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult();
-            eventResult.callId = callid;
-            eventResult.msg = "注册超时";
-            eventResult.type = "register timeout";
-            sipSubscribe.getErrorSubscribe(callid).response(eventResult);
+            String callId = expiredKey.substring(REGISTER_INFO_PREFIX.length());
+            if (sipSubscribe.getErrorSubscribe(callId) != null) {
+                SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult();
+                eventResult.callId = callId;
+                eventResult.msg = "注册超时";
+                eventResult.type = "register timeout";
+                sipSubscribe.getErrorSubscribe(callId).response(eventResult);
+            }
         }
 
     }

+ 6 - 9
src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/KeepliveTimeoutListener.java

@@ -1,14 +1,11 @@
 package com.genersoft.iot.vmp.gb28181.event.offline;
 
 import com.genersoft.iot.vmp.conf.RedisKeyExpirationEventMessageListener;
-import com.genersoft.iot.vmp.conf.UserSetup;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.DependsOn;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.data.redis.connection.Message;
-import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
 import org.springframework.data.redis.listener.RedisMessageListenerContainer;
 import org.springframework.stereotype.Component;
 
@@ -29,15 +26,15 @@ public class KeepliveTimeoutListener extends RedisKeyExpirationEventMessageListe
 	private EventPublisher publisher;
 
 	@Autowired
-	private UserSetup userSetup;
+	private UserSetting userSetting;
 
-    public KeepliveTimeoutListener(RedisMessageListenerContainer listenerContainer, UserSetup userSetup) {
-        super(listenerContainer, userSetup);
+    public KeepliveTimeoutListener(RedisMessageListenerContainer listenerContainer, UserSetting userSetting) {
+        super(listenerContainer, userSetting);
     }
 
     @Override
     public void init() {
-        if (!userSetup.getRedisConfig()) {
+        if (!userSetting.getRedisConfig()) {
             // 配置springboot默认Config为空,即不让应用去修改redis的默认配置,因为Redis服务出于安全会禁用CONFIG命令给远程用户使用
             setKeyspaceNotificationsConfigParameter("");
         }
@@ -54,7 +51,7 @@ public class KeepliveTimeoutListener extends RedisKeyExpirationEventMessageListe
     public void onMessage(Message message, byte[] pattern) {
         //  获取失效的key
         String expiredKey = message.toString();
-        String KEEPLIVEKEY_PREFIX = VideoManagerConstants.KEEPLIVEKEY_PREFIX + userSetup.getServerId() + "_";
+        String KEEPLIVEKEY_PREFIX = VideoManagerConstants.KEEPLIVEKEY_PREFIX + userSetting.getServerId() + "_";
         if(!expiredKey.startsWith(KEEPLIVEKEY_PREFIX)){
         	logger.debug("收到redis过期监听,但开头不是"+KEEPLIVEKEY_PREFIX+",忽略");
         	return;

+ 7 - 12
src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/OfflineEventListener.java

@@ -1,24 +1,21 @@
 package com.genersoft.iot.vmp.gb28181.event.offline;
 
-import com.genersoft.iot.vmp.conf.UserSetup;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
 import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 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 org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationListener;
-import org.springframework.context.annotation.DependsOn;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Component;
 
 import com.genersoft.iot.vmp.common.VideoManagerConstants;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.redis.RedisUtil;
 
 import java.util.List;
@@ -36,7 +33,7 @@ public class OfflineEventListener implements ApplicationListener<OfflineEvent> {
 	private final static Logger logger = LoggerFactory.getLogger(OfflineEventListener.class);
 	
 	@Autowired
-	private IVideoManagerStorager storager;
+	private IVideoManagerStorage storager;
 
 	@Autowired
 	private VideoStreamSessionManager streamSession;
@@ -45,7 +42,7 @@ public class OfflineEventListener implements ApplicationListener<OfflineEvent> {
     private RedisUtil redis;
 
 	@Autowired
-    private UserSetup userSetup;
+    private UserSetting userSetting;
 
 	@Autowired
     private EventPublisher eventPublisher;
@@ -60,12 +57,10 @@ public class OfflineEventListener implements ApplicationListener<OfflineEvent> {
 
 	@Override
 	public void onApplicationEvent(OfflineEvent event) {
-		
-		if (logger.isDebugEnabled()) {
-			logger.debug("设备离线事件触发,deviceId:" + event.getDeviceId() + ",from:" + event.getFrom());
-		}
 
-		String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + userSetup.getServerId() + "_" + event.getDeviceId();
+		logger.info("设备离线事件触发,deviceId:" + event.getDeviceId() + ",from:" + event.getFrom());
+
+		String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + userSetting.getServerId() + "_" + event.getDeviceId();
 
 		switch (event.getFrom()) {
 			// 心跳超时触发的离线事件,说明redis中已删除,无需处理

+ 16 - 14
src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEventListener.java

@@ -1,14 +1,13 @@
 package com.genersoft.iot.vmp.gb28181.event.online;
 
 import com.genersoft.iot.vmp.conf.SipConfig;
-import com.genersoft.iot.vmp.conf.UserSetup;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.service.IDeviceService;
-import com.genersoft.iot.vmp.storager.dao.dto.User;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -16,7 +15,7 @@ import org.springframework.context.ApplicationListener;
 import org.springframework.stereotype.Component;
 
 import com.genersoft.iot.vmp.common.VideoManagerConstants;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.redis.RedisUtil;
 
 import java.text.SimpleDateFormat;
@@ -35,7 +34,7 @@ public class OnlineEventListener implements ApplicationListener<OnlineEvent> {
 	private final static Logger logger = LoggerFactory.getLogger(OnlineEventListener.class);
 
 	@Autowired
-	private IVideoManagerStorager storager;
+	private IVideoManagerStorage storager;
 
 	@Autowired
 	private IDeviceService deviceService;
@@ -47,7 +46,7 @@ public class OnlineEventListener implements ApplicationListener<OnlineEvent> {
     private SipConfig sipConfig;
 
 	@Autowired
-    private UserSetup userSetup;
+    private UserSetting userSetting;
 
 	@Autowired
     private EventPublisher eventPublisher;
@@ -55,17 +54,18 @@ public class OnlineEventListener implements ApplicationListener<OnlineEvent> {
 	@Autowired
 	private SIPCommander cmder;
 
+
 	private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 
 	@Override
 	public void onApplicationEvent(OnlineEvent event) {
-		
-		if (logger.isDebugEnabled()) {
-			logger.debug("设备上线事件触发,deviceId:" + event.getDevice().getDeviceId() + ",from:" + event.getFrom());
-		}
+
+		logger.info("设备上线事件触发,deviceId:" + event.getDevice().getDeviceId() + ",from:" + event.getFrom());
 		Device device = event.getDevice();
-		if (device == null) return;
-		String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + userSetup.getServerId() + "_" + event.getDevice().getDeviceId();
+		if (device == null) {
+			return;
+		}
+		String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + userSetting.getServerId() + "_" + event.getDevice().getDeviceId();
 		Device deviceInStore = storager.queryVideoDevice(device.getDeviceId());
 		device.setOnline(1);
 		switch (event.getFrom()) {
@@ -77,7 +77,7 @@ public class OnlineEventListener implements ApplicationListener<OnlineEvent> {
 			if (deviceInStore == null) { //第一次上线
 				logger.info("[{}] 首次注册,查询设备信息以及通道信息", device.getDeviceId());
 				cmder.deviceInfoQuery(device);
-				cmder.catalogQuery(device, null);
+				deviceService.sync(device);
 			}
 			break;
 		// 设备主动发送心跳触发的在线事件
@@ -98,11 +98,13 @@ public class OnlineEventListener implements ApplicationListener<OnlineEvent> {
 		}
 		// 处理上线监听
 		storager.updateDevice(device);
-		List<DeviceChannel> deviceChannelList = storager.queryOnlineChannelsByDeviceId(device.getDeviceId());
-		eventPublisher.catalogEventPublish(null, deviceChannelList, CatalogEvent.ON);
 		// 上线添加订阅
 		if (device.getSubscribeCycleForCatalog() > 0) {
+			// 查询在线设备那些开启了订阅,为设备开启定时的目录订阅
 			deviceService.addCatalogSubscribe(device);
 		}
+		if (device.getSubscribeCycleForMobilePosition() > 0) {
+			deviceService.addMobilePositionSubscribe(device);
+		}
 	}
 }

+ 2 - 3
src/main/java/com/genersoft/iot/vmp/gb28181/event/platformKeepaliveExpire/PlatformKeepaliveExpireEventLister.java

@@ -6,7 +6,7 @@ import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.jetbrains.annotations.NotNull;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -14,7 +14,6 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationListener;
 import org.springframework.stereotype.Component;
 
-import javax.sip.ResponseEvent;
 import javax.sip.message.Response;
 
 /**
@@ -29,7 +28,7 @@ public class PlatformKeepaliveExpireEventLister implements ApplicationListener<P
     private final static Logger logger = LoggerFactory.getLogger(PlatformKeepaliveExpireEventLister.class);
 
     @Autowired
-    private IVideoManagerStorager storager;
+    private IVideoManagerStorage storager;
 
     @Autowired
     private IRedisCatchStorage redisCatchStorage;

+ 11 - 12
src/main/java/com/genersoft/iot/vmp/gb28181/event/platformNotRegister/PlatformCycleRegisterEventLister.java

@@ -1,9 +1,10 @@
 package com.genersoft.iot.vmp.gb28181.event.platformNotRegister;
 
+import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -19,9 +20,11 @@ public class PlatformCycleRegisterEventLister implements ApplicationListener<Pla
     private final static Logger logger = LoggerFactory.getLogger(PlatformCycleRegisterEventLister.class);
 
     @Autowired
-    private IVideoManagerStorager storager;
+    private IVideoManagerStorage storager;
     @Autowired
     private ISIPCommanderForPlatform sipCommanderFroPlatform;
+    @Autowired
+    private DynamicTask dynamicTask;
 
     @Override
     public void onApplicationEvent(PlatformCycleRegisterEvent event) {
@@ -31,17 +34,13 @@ public class PlatformCycleRegisterEventLister implements ApplicationListener<Pla
             logger.info("[ 平台未注册事件 ] 平台已经删除!!! 平台国标ID:" + event.getPlatformGbID());
             return;
         }
-        Timer timer = new Timer();
+        String taskKey = "platform-cycle-register" + parentPlatform.getServerGBId();;
         SipSubscribe.Event okEvent = (responseEvent)->{
-            timer.cancel();
+            dynamicTask.stop(taskKey);
         };
-        sipCommanderFroPlatform.register(parentPlatform, null, okEvent);
-        timer.schedule(new TimerTask() {
-            @Override
-            public void run() {
-                logger.info("[平台注册]再次向平台注册,平台国标ID:" + event.getPlatformGbID());
-                sipCommanderFroPlatform.register(parentPlatform, null, okEvent);
-            }
-        }, 15*1000 ,Long.parseLong(parentPlatform.getExpires())* 1000);
+        dynamicTask.startCron(taskKey, ()->{
+            logger.info("[平台注册]再次向平台注册,平台国标ID:" + event.getPlatformGbID());
+            sipCommanderFroPlatform.register(parentPlatform, null, okEvent);
+        }, Integer.parseInt(parentPlatform.getExpires())* 1000);
     }
 }

+ 14 - 26
src/main/java/com/genersoft/iot/vmp/gb28181/event/platformNotRegister/PlatformNotRegisterEventLister.java

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.event.platformNotRegister;
 
+import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
@@ -9,7 +10,7 @@ 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.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -31,7 +32,7 @@ public class PlatformNotRegisterEventLister implements ApplicationListener<Platf
     private final static Logger logger = LoggerFactory.getLogger(PlatformNotRegisterEventLister.class);
 
     @Autowired
-    private IVideoManagerStorager storager;
+    private IVideoManagerStorage storager;
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
     @Autowired
@@ -46,6 +47,9 @@ public class PlatformNotRegisterEventLister implements ApplicationListener<Platf
     @Autowired
     private SipConfig config;
 
+    @Autowired
+    private DynamicTask dynamicTask;
+
     // @Autowired
     // private RedisUtil redis;
 
@@ -64,40 +68,24 @@ public class PlatformNotRegisterEventLister implements ApplicationListener<Platf
         logger.info("[ 平台未注册事件 ] 停止[ {} ]的所有推流size", sendRtpItems.size());
         if (sendRtpItems != null && sendRtpItems.size() > 0) {
             logger.info("[ 平台未注册事件 ] 停止[ {} ]的所有推流", event.getPlatformGbID());
-            StringBuilder app = new StringBuilder();
-            StringBuilder stream = new StringBuilder();
             for (SendRtpItem sendRtpItem : sendRtpItems) {
-                if (app.length() != 0) {
-                    app.append(",");
-                }
-                app.append(sendRtpItem.getApp());
-                if (stream.length() != 0) {
-                    stream.append(",");
-                }
-                stream.append(sendRtpItem.getStreamId());
                 redisCatchStorage.deleteSendRTPServer(event.getPlatformGbID(), sendRtpItem.getChannelId(), null, null);
                 MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
                 Map<String, Object> param = new HashMap<>();
                 param.put("vhost", "__defaultVhost__");
-                param.put("app", app.toString());
-                param.put("stream", stream.toString());
+                param.put("app", sendRtpItem.getApp());
+                param.put("stream", sendRtpItem.getStreamId());
                 zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
             }
 
         }
-        Timer timer = new Timer();
+        String taskKey = "platform-not-register-" + parentPlatform.getServerGBId();
         SipSubscribe.Event okEvent = (responseEvent)->{
-            timer.cancel();
+            dynamicTask.stop(taskKey);
         };
-        logger.info("[平台注册]平台国标ID:" + event.getPlatformGbID());
-        sipCommanderFroPlatform.register(parentPlatform, null, okEvent);
-        // 设置注册失败则每隔15秒发起一次注册
-        timer.schedule(new TimerTask() {
-            @Override
-            public void run() {
-                logger.info("[平台注册]再次向平台注册,平台国标ID:" + event.getPlatformGbID());
-                sipCommanderFroPlatform.register(parentPlatform, null, okEvent);
-            }
-        }, config.getRegisterTimeInterval()* 1000, config.getRegisterTimeInterval()* 1000);//十五秒后再次发起注册
+        dynamicTask.startCron(taskKey, ()->{
+            logger.info("[平台注册]再次向平台注册,平台国标ID:" + event.getPlatformGbID());
+            sipCommanderFroPlatform.register(parentPlatform, null, okEvent);
+        }, config.getRegisterTimeInterval()* 1000);
     }
 }

+ 2 - 4
src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEventListener.java

@@ -31,10 +31,8 @@ public class RecordEndEventListener implements ApplicationListener<RecordEndEven
     private Map<String, RecordEndEventHandler> handlerMap = new HashMap<>();
     @Override
     public void onApplicationEvent(RecordEndEvent event) {
-        if (logger.isDebugEnabled()) {
-            logger.debug("录像查询完成事件触发,deviceId:{}, channelId: {}, 录像数量{}条", event.getRecordInfo().getDeviceId(),
-                    event.getRecordInfo().getChannelId(), event.getRecordInfo().getRecordList().size() );
-        }
+        logger.info("录像查询完成事件触发,deviceId:{}, channelId: {}, 录像数量{}条", event.getRecordInfo().getDeviceId(),
+                event.getRecordInfo().getChannelId(), event.getRecordInfo().getSumNum() );
         if (handlerMap.size() > 0) {
             for (RecordEndEventHandler recordEndEventHandler : handlerMap.values()) {
                 recordEndEventHandler.handler(event.getRecordInfo());

+ 0 - 50
src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/SubscribeListenerForPlatform.java

@@ -1,50 +0,0 @@
-package com.genersoft.iot.vmp.gb28181.event.subscribe;
-
-import com.genersoft.iot.vmp.common.VideoManagerConstants;
-import com.genersoft.iot.vmp.conf.DynamicTask;
-import com.genersoft.iot.vmp.conf.RedisKeyExpirationEventMessageListener;
-import com.genersoft.iot.vmp.conf.UserSetup;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.redis.connection.Message;
-import org.springframework.data.redis.listener.RedisMessageListenerContainer;
-import org.springframework.stereotype.Component;
-
-/**    
- * 平台订阅到期事件
- */
-@Component
-public class SubscribeListenerForPlatform extends RedisKeyExpirationEventMessageListener {
-
-    private Logger logger = LoggerFactory.getLogger(SubscribeListenerForPlatform.class);
-
-	@Autowired
-	private UserSetup userSetup;
-
-    @Autowired
-    private DynamicTask dynamicTask;
-
-    public SubscribeListenerForPlatform(RedisMessageListenerContainer listenerContainer, UserSetup userSetup) {
-        super(listenerContainer, userSetup);
-    }
-
-
-    /**
-     * 监听失效的key
-     * @param message
-     * @param pattern
-     */
-    @Override
-    public void onMessage(Message message, byte[] pattern) {
-        //  获取失效的key
-        String expiredKey = message.toString();
-        logger.debug(expiredKey);
-        // 订阅到期
-        String PLATFORM_KEEPLIVEKEY_PREFIX = VideoManagerConstants.SIP_SUBSCRIBE_PREFIX + userSetup.getServerId() + "_";
-        if (expiredKey.startsWith(PLATFORM_KEEPLIVEKEY_PREFIX)) {
-            // 取消定时任务
-            dynamicTask.stop(expiredKey);
-        }
-    }
-}

+ 29 - 22
src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java

@@ -2,17 +2,14 @@ package com.genersoft.iot.vmp.gb28181.event.subscribe.catalog;
 
 import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.conf.SipConfig;
-import com.genersoft.iot.vmp.conf.UserSetup;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.*;
-import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
-import com.genersoft.iot.vmp.gb28181.event.platformNotRegister.PlatformNotRegisterEvent;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
-import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.IGbStreamService;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -31,7 +28,7 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
     private final static Logger logger = LoggerFactory.getLogger(CatalogEventLister.class);
 
     @Autowired
-    private IVideoManagerStorager storager;
+    private IVideoManagerStorage storager;
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
     @Autowired
@@ -47,7 +44,7 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
     private SipConfig config;
 
     @Autowired
-    private UserSetup userSetup;
+    private UserSetting userSetting;
 
     @Autowired
     private IGbStreamService gbStreamService;
@@ -63,13 +60,13 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
         Map<String, List<ParentPlatform>> parentPlatformMap = new HashMap<>();
         if (event.getPlatformId() != null) {
             parentPlatform = storager.queryParentPlatByServerGBId(event.getPlatformId());
-            if (parentPlatform != null && !parentPlatform.isStatus())return;
-            String key = VideoManagerConstants.SIP_SUBSCRIBE_PREFIX + userSetup.getServerId() +  "_Catalog_" + event.getPlatformId();
-//            subscribe = redisCatchStorage.getSubscribe(key);
+            if (parentPlatform != null && !parentPlatform.isStatus()) {
+                return;
+            }
             subscribe = subscribeHolder.getCatalogSubscribe(event.getPlatformId());
 
             if (subscribe == null) {
-                logger.debug("发送订阅消息时发现订阅信息已经不存在");
+                logger.info("发送订阅消息时发现订阅信息已经不存在");
                 return;
             }
         }else {
@@ -85,7 +82,9 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
             }else if (event.getGbStreams() != null) {
                 if (platforms.size() > 0) {
                     for (GbStream gbStream : event.getGbStreams()) {
-                        if (gbStream == null || StringUtils.isEmpty(gbStream.getGbId())) continue;
+                        if (gbStream == null || StringUtils.isEmpty(gbStream.getGbId())) {
+                            continue;
+                        }
                         List<ParentPlatform> parentPlatformsForGB = storager.queryPlatFormListForStreamWithGBId(gbStream.getApp(),gbStream.getStream(), platforms);
                         parentPlatformMap.put(gbStream.getGbId(), parentPlatformsForGB);
                     }
@@ -104,7 +103,7 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
                     }
                     if (event.getGbStreams() != null && event.getGbStreams().size() > 0){
                         for (GbStream gbStream : event.getGbStreams()) {
-                            DeviceChannel deviceChannelByStream = gbStreamService.getDeviceChannelListByStream(gbStream, gbStream.getCatalogId(), parentPlatform.getDeviceGBId());
+                            DeviceChannel deviceChannelByStream = gbStreamService.getDeviceChannelListByStream(gbStream, gbStream.getCatalogId(), parentPlatform);
                             deviceChannelList.add(deviceChannelByStream);
                         }
                     }
@@ -118,7 +117,9 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
                         if (parentPlatforms != null && parentPlatforms.size() > 0) {
                             for (ParentPlatform platform : parentPlatforms) {
                                 SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(platform.getServerGBId());
-                                if (subscribeInfo == null) continue;
+                                if (subscribeInfo == null) {
+                                    continue;
+                                }
                                 logger.info("[Catalog事件: {}]平台:{},影响通道{}", event.getType(), platform.getServerGBId(), gbId);
                                 List<DeviceChannel> deviceChannelList = new ArrayList<>();
                                 DeviceChannel deviceChannel = new DeviceChannel();
@@ -143,7 +144,10 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
                      }
                     if (event.getGbStreams() != null && event.getGbStreams().size() > 0){
                         for (GbStream gbStream : event.getGbStreams()) {
-                            DeviceChannel deviceChannelByStream = gbStreamService.getDeviceChannelListByStream(gbStream, gbStream.getCatalogId(), parentPlatform.getDeviceGBId());
+                            DeviceChannel deviceChannelByStream = gbStreamService.getDeviceChannelListByStream(gbStream, gbStream.getCatalogId(), parentPlatform);
+                            if (deviceChannelByStream.getParentId().length() <= 10) { // 父节点是行政区划,则设置CivilCode使用此行政区划
+                                deviceChannelByStream.setCivilCode(deviceChannelByStream.getParentId());
+                            }
                             deviceChannelList.add(deviceChannelByStream);
                         }
                     }
@@ -156,18 +160,20 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
                         List<ParentPlatform> parentPlatforms = parentPlatformMap.get(gbId);
                         if (parentPlatforms != null && parentPlatforms.size() > 0) {
                             for (ParentPlatform platform : parentPlatforms) {
-//                                String key = VideoManagerConstants.SIP_SUBSCRIBE_PREFIX + userSetup.getServerId() + "_Catalog_" + platform.getServerGBId();
-//                                SubscribeInfo subscribeInfo = redisCatchStorage.getSubscribe(key);
-                                SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(event.getPlatformId());
-                                if (subscribeInfo == null) continue;
+                                SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(platform.getServerGBId());
+                                if (subscribeInfo == null) {
+                                    continue;
+                                }
                                 logger.info("[Catalog事件: {}]平台:{},影响通道{}", event.getType(), platform.getServerGBId(), gbId);
                                 List<DeviceChannel> deviceChannelList = new ArrayList<>();
                                 DeviceChannel deviceChannel = storager.queryChannelInParentPlatform(platform.getServerGBId(), gbId);
                                 deviceChannelList.add(deviceChannel);
                                 GbStream gbStream = storager.queryStreamInParentPlatform(platform.getServerGBId(), gbId);
-                                DeviceChannel deviceChannelByStream = gbStreamService.getDeviceChannelListByStream(gbStream, gbStream.getCatalogId(), platform.getDeviceGBId());
-                                deviceChannelList.add(deviceChannelByStream);
-                                sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), platform, deviceChannelList, subscribeInfo, null);
+                                if(gbStream != null){
+                                    DeviceChannel deviceChannelByStream = gbStreamService.getDeviceChannelListByStream(gbStream, gbStream.getCatalogId(), platform);
+                                    deviceChannelList.add(deviceChannelByStream);
+                                }
+                                sipCommanderFroPlatform.sendNotifyForCatalogAddOrUpdate(event.getType(), platform, deviceChannelList, subscribeInfo, null);
                             }
                         }
                     }
@@ -178,3 +184,4 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
         }
     }
 }
+ 

+ 97 - 29
src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java

@@ -3,9 +3,10 @@ package com.genersoft.iot.vmp.gb28181.session;
 import com.genersoft.iot.vmp.gb28181.bean.CatalogData;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
+import com.genersoft.iot.vmp.gb28181.bean.SyncStatus;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Scheduled;
@@ -23,52 +24,119 @@ public class CatalogDataCatch {
     private DeferredResultHolder deferredResultHolder;
 
     @Autowired
-    private IVideoManagerStorager storager;
+    private IVideoManagerStorage storager;
 
-    public void put(String key, int total, Device device, List<DeviceChannel> deviceChannelList) {
-        CatalogData catalogData = data.get(key);
+    public void addReady(Device device, int sn ) {
+        CatalogData catalogData = data.get(device.getDeviceId());
+        if (catalogData == null || catalogData.getStatus().equals(CatalogData.CatalogDataStatus.end)) {
+            catalogData = new CatalogData();
+            catalogData.setChannelList(new ArrayList<>());
+            catalogData.setDevice(device);
+            catalogData.setSn(sn);
+            catalogData.setStatus(CatalogData.CatalogDataStatus.ready);
+            catalogData.setLastTime(new Date(System.currentTimeMillis()));
+            data.put(device.getDeviceId(), catalogData);
+        }
+    }
+
+    public void put(String deviceId, int sn, int total, Device device, List<DeviceChannel> deviceChannelList) {
+        CatalogData catalogData = data.get(deviceId);
         if (catalogData == null) {
             catalogData = new CatalogData();
+            catalogData.setSn(sn);
             catalogData.setTotal(total);
             catalogData.setDevice(device);
             catalogData.setChannelList(new ArrayList<>());
-            data.put(key, catalogData);
+            catalogData.setStatus(CatalogData.CatalogDataStatus.runIng);
+            catalogData.setLastTime(new Date(System.currentTimeMillis()));
+            data.put(deviceId, catalogData);
+        }else {
+            // 同一个设备的通道同步请求只考虑一个,其他的直接忽略
+            if (catalogData.getSn() != sn) {
+                return;
+            }
+            catalogData.setTotal(total);
+            catalogData.setDevice(device);
+            catalogData.setStatus(CatalogData.CatalogDataStatus.runIng);
+            catalogData.getChannelList().addAll(deviceChannelList);
+            catalogData.setLastTime(new Date(System.currentTimeMillis()));
         }
-        catalogData.getChannelList().addAll(deviceChannelList);
-        catalogData.setLastTime(new Date(System.currentTimeMillis()));
     }
 
-    public List<DeviceChannel> get(String key) {
-        CatalogData catalogData = data.get(key);
-        if (catalogData == null) return null;
+    public List<DeviceChannel> get(String deviceId) {
+        CatalogData catalogData = data.get(deviceId);
+        if (catalogData == null) {
+            return null;
+        }
         return catalogData.getChannelList();
     }
 
-    public void del(String key) {
-        data.remove(key);
+    public int getTotal(String deviceId) {
+        CatalogData catalogData = data.get(deviceId);
+        if (catalogData == null) {
+            return 0;
+        }
+        return catalogData.getTotal();
+    }
+
+    public SyncStatus getSyncStatus(String deviceId) {
+        CatalogData catalogData = data.get(deviceId);
+        if (catalogData == null) {
+            return null;
+        }
+        SyncStatus syncStatus = new SyncStatus();
+        syncStatus.setCurrent(catalogData.getChannelList().size());
+        syncStatus.setTotal(catalogData.getTotal());
+        syncStatus.setErrorMsg(catalogData.getErrorMsg());
+        return syncStatus;
+    }
+
+    public boolean isSyncRunning(String deviceId) {
+        CatalogData catalogData = data.get(deviceId);
+        if (catalogData == null) {
+            return false;
+        }
+        return !catalogData.getStatus().equals(CatalogData.CatalogDataStatus.end);
     }
 
     @Scheduled(fixedRate = 5 * 1000)   //每5秒执行一次, 发现数据5秒未更新则移除数据并认为数据接收超时
     private void timerTask(){
         Set<String> keys = data.keySet();
-        Calendar calendar = Calendar.getInstance();
-        calendar.setTime(new Date());
-        calendar.set(Calendar.SECOND, calendar.get(Calendar.SECOND) - 5);
-        for (String key : keys) {
-            CatalogData catalogData = data.get(key);
-            if (catalogData.getLastTime().before(calendar.getTime())) {
-
-                storager.resetChannels(catalogData.getDevice().getDeviceId(), catalogData.getChannelList());
-                RequestMessage msg = new RequestMessage();
-                msg.setKey(key);
-                WVPResult<Object> result = new WVPResult<>();
-                result.setCode(0);
-                result.setMsg("更新成功,共" + catalogData.getTotal() + "条,已更新" + catalogData.getChannelList().size() + "条");
-                result.setData(catalogData.getDevice());
-                msg.setData(result);
-                deferredResultHolder.invokeAllResult(msg);
-                data.remove(key);
+        Calendar calendarBefore5S = Calendar.getInstance();
+        calendarBefore5S.setTime(new Date());
+        calendarBefore5S.set(Calendar.SECOND, calendarBefore5S.get(Calendar.SECOND) - 5);
+
+        Calendar calendarBefore30S = Calendar.getInstance();
+        calendarBefore30S.setTime(new Date());
+        calendarBefore30S.set(Calendar.SECOND, calendarBefore30S.get(Calendar.SECOND) - 30);
+        for (String deviceId : keys) {
+            CatalogData catalogData = data.get(deviceId);
+            if ( catalogData.getLastTime().before(calendarBefore5S.getTime())) { // 超过五秒收不到消息任务超时, 只更新这一部分数据
+                if (catalogData.getStatus().equals(CatalogData.CatalogDataStatus.runIng)) {
+                    storager.resetChannels(catalogData.getDevice().getDeviceId(), catalogData.getChannelList());
+                    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 = "同步失败,等待回复超时";
+                    catalogData.setErrorMsg(errorMsg);
+                }
+                catalogData.setStatus(CatalogData.CatalogDataStatus.end);
+            }
+            if (catalogData.getStatus().equals(CatalogData.CatalogDataStatus.end) && catalogData.getLastTime().before(calendarBefore30S.getTime())) { // 超过三十秒,如果标记为end则删除
+                data.remove(deviceId);
             }
         }
     }
+
+
+    public void setChannelSyncEnd(String deviceId, String errorMsg) {
+        CatalogData catalogData = data.get(deviceId);
+        if (catalogData == null) {
+            return;
+        }
+        catalogData.setStatus(CatalogData.CatalogDataStatus.end);
+        catalogData.setErrorMsg(errorMsg);
+    }
 }

+ 3 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/session/SsrcConfig.java

@@ -136,4 +136,7 @@ public class SsrcConfig {
         this.notUsed = notUsed;
     }
 
+    public boolean checkSsrc(String ssrcInResponse) {
+        return !isUsed.contains(ssrcInResponse);
+    }
 }

+ 65 - 26
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java

@@ -7,7 +7,7 @@ import javax.sip.ClientTransaction;
 import javax.sip.Dialog;
 
 import com.genersoft.iot.vmp.common.VideoManagerConstants;
-import com.genersoft.iot.vmp.conf.UserSetup;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
 import com.genersoft.iot.vmp.utils.SerializeUtils;
 import com.genersoft.iot.vmp.utils.redis.RedisUtil;
@@ -28,7 +28,13 @@ public class VideoStreamSessionManager {
 	private RedisUtil redisUtil;
 
 	@Autowired
-	private UserSetup userSetup;
+	private UserSetting userSetting;
+
+	public enum SessionType {
+		play,
+		playback,
+		download
+	}
 
 	/**
 	 * 添加一个点播/回放的事务信息
@@ -40,7 +46,7 @@ public class VideoStreamSessionManager {
 	 * @param mediaServerId 所使用的流媒体ID
 	 * @param transaction 事务
 	 */
-	public void put(String deviceId, String channelId, String callId, String stream, String ssrc, String mediaServerId, ClientTransaction transaction){
+	public void put(String deviceId, String channelId, String callId, String stream, String ssrc, String mediaServerId, ClientTransaction transaction, SessionType type){
 		SsrcTransaction ssrcTransaction = new SsrcTransaction();
 		ssrcTransaction.setDeviceId(deviceId);
 		ssrcTransaction.setChannelId(channelId);
@@ -50,10 +56,11 @@ public class VideoStreamSessionManager {
 		ssrcTransaction.setCallId(callId);
 		ssrcTransaction.setSsrc(ssrc);
 		ssrcTransaction.setMediaServerId(mediaServerId);
+		ssrcTransaction.setType(type);
 
-		redisUtil.set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetup.getServerId()
+		redisUtil.set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId()
 				+ "_" +  deviceId + "_" + channelId + "_" + callId + "_" + stream, ssrcTransaction);
-		redisUtil.set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetup.getServerId()
+		redisUtil.set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId()
 				+ "_" +  deviceId + "_" + channelId + "_" + callId + "_" + stream, ssrcTransaction);
 	}
 
@@ -63,7 +70,7 @@ public class VideoStreamSessionManager {
 			byte[] dialogByteArray = SerializeUtils.serialize(dialog);
 			ssrcTransaction.setDialog(dialogByteArray);
 		}
-		redisUtil.set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetup.getServerId()
+		redisUtil.set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId()
 				+ "_" +  deviceId + "_" + channelId + "_" + ssrcTransaction.getCallId() + "_"
 				+ ssrcTransaction.getStream(), ssrcTransaction);
 	}
@@ -71,7 +78,9 @@ public class VideoStreamSessionManager {
 	
 	public ClientTransaction getTransactionByStream(String deviceId, String channelId, String stream){
 		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream);
-		if (ssrcTransaction == null) return null;
+		if (ssrcTransaction == null) {
+			return null;
+		}
 		byte[] transactionByteArray = ssrcTransaction.getTransaction();
 		ClientTransaction clientTransaction = (ClientTransaction)SerializeUtils.deSerialize(transactionByteArray);
 		return clientTransaction;
@@ -79,39 +88,63 @@ public class VideoStreamSessionManager {
 
 	public SIPDialog getDialogByStream(String deviceId, String channelId, String stream){
 		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream);
-		if (ssrcTransaction == null) return null;
+		if (ssrcTransaction == null) {
+			return null;
+		}
 		byte[] dialogByteArray = ssrcTransaction.getDialog();
-		if (dialogByteArray == null) return null;
+		if (dialogByteArray == null) {
+			return null;
+		}
 		SIPDialog dialog = (SIPDialog)SerializeUtils.deSerialize(dialogByteArray);
 		return dialog;
 	}
 
 	public SIPDialog getDialogByCallId(String deviceId, String channelId, String callID){
 		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, callID, null);
-		if (ssrcTransaction == null) return null;
+		if (ssrcTransaction == null) {
+			return null;
+		}
 		byte[] dialogByteArray = ssrcTransaction.getDialog();
-		if (dialogByteArray == null) return null;
+		if (dialogByteArray == null) {
+			return null;
+		}
 		SIPDialog dialog = (SIPDialog)SerializeUtils.deSerialize(dialogByteArray);
 		return dialog;
 	}
 
 	public SsrcTransaction getSsrcTransaction(String deviceId, String channelId, String callId, String stream){
-		if (StringUtils.isEmpty(callId)) callId ="*";
-		if (StringUtils.isEmpty(stream)) stream ="*";
-		String key = VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetup.getServerId() + "_" + deviceId + "_" + channelId + "_" + callId+ "_" + stream;
+		if (StringUtils.isEmpty(callId)) {
+			callId ="*";
+		}
+		if (StringUtils.isEmpty(stream)) {
+			stream ="*";
+		}
+		String key = VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_" + deviceId + "_" + channelId + "_" + callId+ "_" + stream;
 		List<Object> scanResult = redisUtil.scan(key);
-		if (scanResult.size() == 0) return null;
+		if (scanResult.size() == 0) {
+			return null;
+		}
 		return (SsrcTransaction)redisUtil.get((String) scanResult.get(0));
 	}
 
 	public List<SsrcTransaction> getSsrcTransactionForAll(String deviceId, String channelId, String callId, String stream){
-		if (StringUtils.isEmpty(deviceId)) deviceId ="*";
-		if (StringUtils.isEmpty(channelId)) channelId ="*";
-		if (StringUtils.isEmpty(callId)) callId ="*";
-		if (StringUtils.isEmpty(stream)) stream ="*";
-		String key = VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetup.getServerId() + "_" + deviceId + "_" + channelId + "_" + callId+ "_" + stream;
+		if (StringUtils.isEmpty(deviceId)) {
+			deviceId ="*";
+		}
+		if (StringUtils.isEmpty(channelId)) {
+			channelId ="*";
+		}
+		if (StringUtils.isEmpty(callId)) {
+			callId ="*";
+		}
+		if (StringUtils.isEmpty(stream)) {
+			stream ="*";
+		}
+		String key = VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_" + deviceId + "_" + channelId + "_" + callId+ "_" + stream;
 		List<Object> scanResult = redisUtil.scan(key);
-		if (scanResult.size() == 0) return null;
+		if (scanResult.size() == 0) {
+			return null;
+		}
 		List<SsrcTransaction> result = new ArrayList<>();
 		for (Object keyObj : scanResult) {
 			result.add((SsrcTransaction)redisUtil.get((String) keyObj));
@@ -121,26 +154,32 @@ public class VideoStreamSessionManager {
 
 	public String getMediaServerId(String deviceId, String channelId, String stream){
 		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream);
-		if (ssrcTransaction == null) return null;
+		if (ssrcTransaction == null) {
+			return null;
+		}
 		return ssrcTransaction.getMediaServerId();
 	}
 
 	public String getSSRC(String deviceId, String channelId, String stream){
 		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream);
-		if (ssrcTransaction == null) return null;
+		if (ssrcTransaction == null) {
+			return null;
+		}
 		return ssrcTransaction.getSsrc();
 	}
 	
 	public void remove(String deviceId, String channelId, String stream) {
 		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream);
-		if (ssrcTransaction == null) return;
-		redisUtil.del(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetup.getServerId() + "_"
+		if (ssrcTransaction == null) {
+			return;
+		}
+		redisUtil.del(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_"
 				+  deviceId + "_" + channelId + "_" + ssrcTransaction.getCallId() + "_" + ssrcTransaction.getStream());
 	}
 
 
 	public List<SsrcTransaction> getAllSsrc() {
-		List<Object> ssrcTransactionKeys = redisUtil.scan(String.format("%s_*_*_*_*", VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX+ userSetup.getServerId() + "_" ));
+		List<Object> ssrcTransactionKeys = redisUtil.scan(String.format("%s_*_*_*_*", VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX+ userSetting.getServerId() + "_" ));
 		List<SsrcTransaction> result= new ArrayList<>();
 		for (int i = 0; i < ssrcTransactionKeys.size(); i++) {
 			String key = (String)ssrcTransactionKeys.get(i);

+ 0 - 71
src/main/java/com/genersoft/iot/vmp/gb28181/task/GPSSubscribeTask.java

@@ -1,71 +0,0 @@
-package com.genersoft.iot.vmp.gb28181.task;
-
-import com.genersoft.iot.vmp.gb28181.bean.GbStream;
-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.transmit.cmd.ISIPCommanderForPlatform;
-import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
-import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
-
-import java.text.SimpleDateFormat;
-import java.util.List;
-
-public class GPSSubscribeTask implements Runnable{
-
-    private IRedisCatchStorage redisCatchStorage;
-    private IVideoManagerStorager storager;
-    private ISIPCommanderForPlatform sipCommanderForPlatform;
-    private SubscribeHolder subscribeHolder;
-    private String platformId;
-    private String sn;
-    private String key;
-
-    private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-
-    public GPSSubscribeTask(IRedisCatchStorage redisCatchStorage, ISIPCommanderForPlatform sipCommanderForPlatform, IVideoManagerStorager storager, String platformId, String sn, String key, SubscribeHolder subscribeInfo) {
-        this.redisCatchStorage = redisCatchStorage;
-        this.storager = storager;
-        this.platformId = platformId;
-        this.sn = sn;
-        this.key = key;
-        this.sipCommanderForPlatform = sipCommanderForPlatform;
-        this.subscribeHolder = subscribeInfo;
-    }
-
-    @Override
-    public void run() {
-
-        SubscribeInfo subscribe = subscribeHolder.getMobilePositionSubscribe(platformId);
-
-        if (subscribe != null) {
-            ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(platformId);
-            if (parentPlatform == null || parentPlatform.isStatus()) {
-                // TODO 暂时只处理视频流的回复,后续增加对国标设备的支持
-                List<GbStream> gbStreams = storager.queryGbStreamListInPlatform(platformId);
-                if (gbStreams.size() > 0) {
-                    for (GbStream gbStream : gbStreams) {
-                        String gbId = gbStream.getGbId();
-                        GPSMsgInfo gpsMsgInfo = redisCatchStorage.getGpsMsgInfo(gbId);
-                        if (gbStream.isStatus()) {
-                            if (gpsMsgInfo != null) {
-                                // 发送GPS消息
-                                sipCommanderForPlatform.sendNotifyMobilePosition(parentPlatform, gpsMsgInfo, subscribe);
-                            }else {
-                                // 没有在redis找到新的消息就使用数据库的消息
-                                gpsMsgInfo = new GPSMsgInfo();
-                                gpsMsgInfo.setId(gbId);
-                                gpsMsgInfo.setLat(gbStream.getLongitude());
-                                gpsMsgInfo.setLng(gbStream.getLongitude());
-                                // 发送GPS消息
-                                sipCommanderForPlatform.sendNotifyMobilePosition(parentPlatform, gpsMsgInfo, subscribe);
-                            }
-                        }
-
-                    }
-                }
-            }
-        }
-    }
-}

+ 12 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/task/ISubscribeTask.java

@@ -0,0 +1,12 @@
+package com.genersoft.iot.vmp.gb28181.task;
+
+import javax.sip.DialogState;
+
+/**
+ * @author lin
+ */
+public interface ISubscribeTask extends Runnable{
+    void stop();
+
+    DialogState getDialogState();
+}

+ 102 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/CatalogSubscribeTask.java

@@ -0,0 +1,102 @@
+package com.genersoft.iot.vmp.gb28181.task.impl;
+
+import com.genersoft.iot.vmp.conf.DynamicTask;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import javax.sip.Dialog;
+import javax.sip.DialogState;
+import javax.sip.ResponseEvent;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * 目录订阅任务
+ * @author lin
+ */
+public class CatalogSubscribeTask implements ISubscribeTask {
+    private final Logger logger = LoggerFactory.getLogger(CatalogSubscribeTask.class);
+    private Device device;
+    private final ISIPCommander sipCommander;
+    private Dialog dialog;
+
+    private DynamicTask dynamicTask;
+
+    private String taskKey = "catalog-subscribe-timeout";
+
+
+    public CatalogSubscribeTask(Device device, ISIPCommander sipCommander, DynamicTask dynamicTask) {
+        this.device = device;
+        this.sipCommander = sipCommander;
+        this.dynamicTask = dynamicTask;
+    }
+
+    @Override
+    public void run() {
+        if (dynamicTask.get(taskKey) != null) {
+            dynamicTask.stop(taskKey);
+        }
+        sipCommander.catalogSubscribe(device, dialog, eventResult -> {
+            if (eventResult.dialog != null || eventResult.dialog.getState().equals(DialogState.CONFIRMED)) {
+                dialog = eventResult.dialog;
+            }
+            ResponseEvent event = (ResponseEvent) eventResult.event;
+            if (event.getResponse().getRawContent() != null) {
+                // 成功
+                logger.info("[目录订阅]成功: {}", device.getDeviceId());
+            }else {
+                // 成功
+                logger.info("[目录订阅]成功: {}", device.getDeviceId());
+            }
+        },eventResult -> {
+            dialog = null;
+            // 失败
+            logger.warn("[目录订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg);
+            dynamicTask.startDelay(taskKey, CatalogSubscribeTask.this, 2000);
+        });
+    }
+
+    @Override
+    public void stop() {
+        /**
+         * dialog 的各个状态
+         * EARLY-> Early state状态-初始请求发送以后,收到了一个临时响应消息
+         * CONFIRMED-> Confirmed Dialog状态-已确认
+         * COMPLETED-> Completed Dialog状态-已完成
+         * TERMINATED-> Terminated Dialog状态-终止
+         */
+        logger.info("取消目录订阅时dialog状态为{}", DialogState.CONFIRMED);
+        if (dynamicTask.get(taskKey) != null) {
+            dynamicTask.stop(taskKey);
+        }
+        if (dialog != null && dialog.getState().equals(DialogState.CONFIRMED)) {
+            device.setSubscribeCycleForCatalog(0);
+            sipCommander.catalogSubscribe(device, dialog, eventResult -> {
+                ResponseEvent event = (ResponseEvent) eventResult.event;
+                if (event.getResponse().getRawContent() != null) {
+                    // 成功
+                    logger.info("[取消目录订阅订阅]成功: {}", device.getDeviceId());
+                }else {
+                    // 成功
+                    logger.info("[取消目录订阅订阅]成功: {}", device.getDeviceId());
+                }
+            },eventResult -> {
+                // 失败
+                logger.warn("[取消目录订阅订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg);
+            });
+        }
+    }
+
+    @Override
+    public DialogState getDialogState() {
+        if (dialog == null) {
+            return null;
+        }
+        return dialog.getState();
+    }
+}

+ 95 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeHandlerTask.java

@@ -0,0 +1,95 @@
+package com.genersoft.iot.vmp.gb28181.task.impl;
+
+import com.genersoft.iot.vmp.conf.DynamicTask;
+import com.genersoft.iot.vmp.gb28181.bean.*;
+import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
+import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
+import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.annotation.Async;
+
+import javax.sip.DialogState;
+import java.util.List;
+
+/**
+ * 向已经订阅(移动位置)的上级发送MobilePosition消息
+ * @author lin
+ */
+public class MobilePositionSubscribeHandlerTask implements ISubscribeTask {
+
+    private Logger logger = LoggerFactory.getLogger(MobilePositionSubscribeHandlerTask.class);
+
+    private IRedisCatchStorage redisCatchStorage;
+    private IVideoManagerStorage storager;
+    private ISIPCommanderForPlatform sipCommanderForPlatform;
+    private SubscribeHolder subscribeHolder;
+    private ParentPlatform platform;
+
+    private String sn;
+    private String key;
+
+    public MobilePositionSubscribeHandlerTask(IRedisCatchStorage redisCatchStorage,
+                                              ISIPCommanderForPlatform sipCommanderForPlatform,
+                                              IVideoManagerStorage storager,
+                                              String platformId,
+                                              String sn,
+                                              String key,
+                                              SubscribeHolder subscribeInfo,
+                                              DynamicTask dynamicTask) {
+        this.redisCatchStorage = redisCatchStorage;
+        this.storager = storager;
+        this.platform = storager.queryParentPlatByServerGBId(platformId);
+        this.sn = sn;
+        this.key = key;
+        this.sipCommanderForPlatform = sipCommanderForPlatform;
+        this.subscribeHolder = subscribeInfo;
+    }
+
+    @Override
+    public void run() {
+
+        if (platform == null) {
+            return;
+        }
+        SubscribeInfo subscribe = subscribeHolder.getMobilePositionSubscribe(platform.getServerGBId());
+        if (subscribe != null) {
+
+//            if (!parentPlatform.isStatus()) {
+//                logger.info("发送订阅时发现平台已经离线:{}", platformId);
+//                return;
+//            }
+            // TODO 暂时只处理视频流的回复,后续增加对国标设备的支持
+            List<GbStream> gbStreams = storager.queryGbStreamListInPlatform(platform.getServerGBId());
+            if (gbStreams.size() == 0) {
+                logger.info("发送订阅时发现平台已经没有关联的直播流:{}", platform.getServerGBId());
+                return;
+            }
+            for (GbStream gbStream : gbStreams) {
+                String gbId = gbStream.getGbId();
+                GPSMsgInfo gpsMsgInfo = redisCatchStorage.getGpsMsgInfo(gbId);
+                if (gpsMsgInfo != null) { // 无最新位置不发送
+                    logger.info("无最新位置不发送");
+                    // 经纬度都为0不发送
+                    if (gpsMsgInfo.getLng() == 0 && gpsMsgInfo.getLat() == 0) {
+                        continue;
+                    }
+                    // 发送GPS消息
+                    sipCommanderForPlatform.sendNotifyMobilePosition(platform, gpsMsgInfo, subscribe);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void stop() {
+
+    }
+
+    @Override
+    public DialogState getDialogState() {
+        return null;
+    }
+}

+ 99 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeTask.java

@@ -0,0 +1,99 @@
+package com.genersoft.iot.vmp.gb28181.task.impl;
+
+import com.genersoft.iot.vmp.conf.DynamicTask;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
+import org.dom4j.Element;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.annotation.Async;
+
+import javax.sip.Dialog;
+import javax.sip.DialogState;
+import javax.sip.ResponseEvent;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * 移动位置订阅的定时更新
+ * @author lin
+ */
+public class MobilePositionSubscribeTask implements ISubscribeTask {
+    private final Logger logger = LoggerFactory.getLogger(MobilePositionSubscribeTask.class);
+    private  Device device;
+    private  ISIPCommander sipCommander;
+    private Dialog dialog;
+    private DynamicTask dynamicTask;
+    private String taskKey = "mobile-position-subscribe-timeout";
+
+    public MobilePositionSubscribeTask(Device device, ISIPCommander sipCommander, DynamicTask dynamicTask) {
+        this.device = device;
+        this.sipCommander = sipCommander;
+        this.dynamicTask = dynamicTask;
+    }
+
+    @Override
+    public void run() {
+        if (dynamicTask.get(taskKey) != null) {
+            dynamicTask.stop(taskKey);
+        }
+        sipCommander.mobilePositionSubscribe(device, dialog, eventResult -> {
+//            if (eventResult.dialog != null || eventResult.dialog.getState().equals(DialogState.CONFIRMED)) {
+//                dialog = eventResult.dialog;
+//            }
+            ResponseEvent event = (ResponseEvent) eventResult.event;
+            if (event.getResponse().getRawContent() != null) {
+                // 成功
+                logger.info("[移动位置订阅]成功: {}", device.getDeviceId());
+            }else {
+                // 成功
+                logger.info("[移动位置订阅]成功: {}", device.getDeviceId());
+            }
+        },eventResult -> {
+            dialog = null;
+            // 失败
+            logger.warn("[移动位置订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg);
+            dynamicTask.startDelay(taskKey, MobilePositionSubscribeTask.this, 2000);
+        });
+
+    }
+
+    @Override
+    public void stop() {
+        /**
+         * dialog 的各个状态
+         * EARLY-> Early state状态-初始请求发送以后,收到了一个临时响应消息
+         * CONFIRMED-> Confirmed Dialog状态-已确认
+         * COMPLETED-> Completed Dialog状态-已完成
+         * TERMINATED-> Terminated Dialog状态-终止
+         */
+        if (dynamicTask.get(taskKey) != null) {
+            dynamicTask.stop(taskKey);
+        }
+        if (dialog != null && dialog.getState().equals(DialogState.CONFIRMED)) {
+            logger.info("取消移动订阅时dialog状态为{}", dialog.getState());
+            device.setSubscribeCycleForMobilePosition(0);
+            sipCommander.mobilePositionSubscribe(device, dialog, eventResult -> {
+                ResponseEvent event = (ResponseEvent) eventResult.event;
+                if (event.getResponse().getRawContent() != null) {
+                    // 成功
+                    logger.info("[取消移动位置订阅]成功: {}", device.getDeviceId());
+                }else {
+                    // 成功
+                    logger.info("[取消移动位置订阅]成功: {}", device.getDeviceId());
+                }
+            },eventResult -> {
+                // 失败
+                logger.warn("[取消移动位置订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg);
+            });
+        }
+    }
+    @Override
+    public DialogState getDialogState() {
+        if (dialog == null) {
+            return null;
+        }
+        return dialog.getState();
+    }
+}

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

@@ -4,7 +4,6 @@ import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
 import com.genersoft.iot.vmp.gb28181.transmit.event.response.ISIPResponseProcessor;
 import com.genersoft.iot.vmp.gb28181.transmit.event.timeout.ITimeoutProcessor;
-import gov.nist.javax.sip.message.SIPRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -14,7 +13,6 @@ import org.springframework.stereotype.Component;
 import javax.sip.*;
 import javax.sip.header.CSeqHeader;
 import javax.sip.header.CallIdHeader;
-import javax.sip.header.Header;
 import javax.sip.message.Response;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -36,6 +34,7 @@ public class SIPProcessorObserver implements ISIPProcessorObserver {
     @Autowired
     private SipSubscribe sipSubscribe;
 
+
 //    @Autowired
 //    @Qualifier(value = "taskExecutor")
 //    private ThreadPoolTaskExecutor poolTaskExecutor;
@@ -63,7 +62,7 @@ public class SIPProcessorObserver implements ISIPProcessorObserver {
      * @param processor 处理程序
      */
     public void addTimeoutProcessor(ITimeoutProcessor processor) {
-        this.timeoutProcessor = processor;
+        timeoutProcessor = processor;
     }
 
     /**

+ 1 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/CheckForAllRecordsThread.java

@@ -29,6 +29,7 @@ public class CheckForAllRecordsThread extends Thread {
         this.recordInfo = recordInfo;
     }
 
+    @Override
     public void run() {
 
         String cacheKey = this.key;

+ 7 - 3
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java

@@ -35,7 +35,7 @@ public class DeferredResultHolder {
 
 	public static final String CALLBACK_CMD_PLAY = "CALLBACK_PLAY";
 
-	public static final String CALLBACK_CMD_PLAYBACK = "CALLBACK_PLAY";
+	public static final String CALLBACK_CMD_PLAYBACK = "CALLBACK_PLAYBACK";
 
 	public static final String CALLBACK_CMD_DOWNLOAD = "CALLBACK_DOWNLOAD";
 
@@ -65,12 +65,16 @@ public class DeferredResultHolder {
 	
 	public DeferredResult get(String key, String id) {
 		Map<String, DeferredResult> deferredResultMap = map.get(key);
-		if (deferredResultMap == null) return null;
+		if (deferredResultMap == null) {
+			return null;
+		}
 		return deferredResultMap.get(id);
 	}
 
 	public boolean exist(String key, String id){
-		if (key == null) return false;
+		if (key == null) {
+			return false;
+		}
 		Map<String, DeferredResult> deferredResultMap = map.get(key);
 		if (id == null) {
 			return deferredResultMap != null;

+ 21 - 15
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java

@@ -1,13 +1,14 @@
 package com.genersoft.iot.vmp.gb28181.transmit.cmd;
 
 import com.genersoft.iot.vmp.common.StreamInfo;
-import com.genersoft.iot.vmp.gb28181.bean.Device;
-import com.genersoft.iot.vmp.gb28181.bean.InviteStreamCallback;
+import com.genersoft.iot.vmp.gb28181.bean.*;
 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 javax.sip.Dialog;
+
 /**    
  * @description:设备能力接口,用于定义设备的控制、查询能力   
  * @author: swwheihei
@@ -22,7 +23,6 @@ public interface ISIPCommander {
 	 * @param channelId  预览通道
 	 * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
      * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
-     * @param moveSpeed  镜头移动速度
 	 */
 	boolean ptzdirectCmd(Device device,String channelId,int leftRight, int upDown);
 	
@@ -52,7 +52,6 @@ public interface ISIPCommander {
 	 * @param device  控制设备
 	 * @param channelId  预览通道
      * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
-     * @param zoomSpeed  镜头缩放速度
 	 */
 	boolean ptzZoomCmd(Device device,String channelId,int inOut, int moveSpeed);
 	
@@ -87,15 +86,15 @@ public interface ISIPCommander {
 	 * @param channelId		预览通道
 	 * @param cmdString		前端控制指令串
 	 */
-	boolean fronEndCmd(Device device, String channelId, String cmdString);
+	boolean fronEndCmd(Device device, String channelId, String cmdString, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent);
 
 	/**
 	 * 请求预览视频流
 	 * @param device  视频设备
 	 * @param channelId  预览通道
 	 */
-	void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
-	
+	void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent);
+
 	/**
 	 * 请求回放视频流
 	 * 
@@ -115,7 +114,9 @@ public interface ISIPCommander {
 	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
 	 * @param downloadSpeed 下载倍速参数
 	 */ 
-	void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, String startTime, String endTime, String downloadSpeed, InviteStreamCallback event, SipSubscribe.Event errorEvent);
+	void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
+						   String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
+						   SipSubscribe.Event errorEvent);
 
 	/**
 	 * 视频流停止
@@ -179,7 +180,6 @@ public interface ISIPCommander {
 	 * 报警布防/撤防命令
 	 * 
 	 * @param device  	视频设备
-	 * @param setGuard	true: SetGuard, false: ResetGuard
 	 */
 	boolean guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent);
 	
@@ -249,7 +249,7 @@ public interface ISIPCommander {
 	 * 
 	 * @param device 视频设备
 	 */
-	boolean catalogQuery(Device device, SipSubscribe.Event errorEvent);
+	boolean catalogQuery(Device device, int sn, SipSubscribe.Event errorEvent);
 	
 	/**
 	 * 查询录像信息
@@ -303,11 +303,9 @@ public interface ISIPCommander {
 	 * 订阅、取消订阅移动位置
 	 * 
 	 * @param device	视频设备
-	 * @param expires	订阅超时时间(值=0时为取消订阅)
-	 * @param interval	上报时间间隔
 	 * @return			true = 命令发送成功
 	 */
-	boolean mobilePositionSubscribe(Device device, int expires, int interval);
+	boolean mobilePositionSubscribe(Device device, Dialog dialog, SipSubscribe.Event okEvent , SipSubscribe.Event errorEvent);
 
 	/**
 	 * 订阅、取消订阅报警信息
@@ -315,7 +313,6 @@ public interface ISIPCommander {
 	 * @param expires		订阅过期时间(0 = 取消订阅)
 	 * @param startPriority	报警起始级别(可选)
 	 * @param endPriority	报警终止级别(可选)
-	 * @param alarmMethods	报警方式条件(可选)
 	 * @param alarmType		报警类型
 	 * @param startTime		报警发生起始时间(可选)
 	 * @param endTime		报警发生终止时间(可选)
@@ -328,7 +325,7 @@ public interface ISIPCommander {
 	 * @param device		视频设备
 	 * @return				true = 命令发送成功
 	 */
-	boolean catalogSubscribe(Device device, SipSubscribe.Event okEvent ,SipSubscribe.Event errorEvent);
+	boolean catalogSubscribe(Device device, Dialog dialog, SipSubscribe.Event okEvent ,SipSubscribe.Event errorEvent);
 
 	/**
 	 * 拉框控制命令
@@ -338,4 +335,13 @@ public interface ISIPCommander {
 	 * @param cmdString 前端控制指令串
 	 */
 	boolean dragZoomCmd(Device device, String channelId, String cmdString);
+
+
+	/**
+	 * 向设备发送报警NOTIFY消息, 用于互联结构下,此时将设备当成一个平级平台看待
+	 * @param device 设备
+	 * @param deviceAlarm 报警信息信息
+	 * @return
+	 */
+	boolean sendAlarmMessage(Device device, DeviceAlarm deviceAlarm);
 }

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

@@ -1,9 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.transmit.cmd;
 
-import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
-import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
-import com.genersoft.iot.vmp.gb28181.bean.RecordInfo;
-import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo;
+import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
 
@@ -46,6 +43,7 @@ public interface ISIPCommanderForPlatform {
      * @return
      */
     boolean catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size);
+    boolean catalogQuery(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag);
 
     /**
      * 向上级回复DeviceInfo查询信息
@@ -74,6 +72,14 @@ public interface ISIPCommanderForPlatform {
      */
     boolean sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo);
 
+    /**
+     * 向上级回复报警消息
+     * @param parentPlatform 平台信息
+     * @param deviceAlarm 报警信息信息
+     * @return
+     */
+    boolean sendAlarmMessage(ParentPlatform parentPlatform, DeviceAlarm deviceAlarm);
+
     /**
      * 回复catalog事件-增加/更新
      * @param parentPlatform

+ 1 - 55
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java

@@ -223,7 +223,7 @@ public class SIPRequestHeaderPlarformProvider {
 		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(Request.MESSAGE), Request.MESSAGE);
 		MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipFactory.createMessageFactory();
 		// 设置编码, 防止中文乱码
-		messageFactory.setDefaultContentEncodingCharset("gb2312");
+		messageFactory.setDefaultContentEncodingCharset(parentPlatform.getCharacterSet());
 		request = messageFactory.createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
 				toHeader, viaHeaders, maxForwards);
 		List<String> agentParam = new ArrayList<>();
@@ -235,58 +235,4 @@ public class SIPRequestHeaderPlarformProvider {
 		request.setContent(content, contentTypeHeader);
 		return request;
 	}
-
-//    public Request createNotifyRequest(ParentPlatform parentPlatform, String content, CallIdHeader callIdHeader, String viaTag, String fromTag, SubscribeInfo subscribeInfo) throws PeerUnavailableException, ParseException, InvalidArgumentException {
-//		Request request = null;
-//		// sipuri
-//		SipURI requestURI = sipFactory.createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort());
-//		// via
-//		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-//		ViaHeader viaHeader = sipFactory.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.getDevicePort());
-//		Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI);
-//		FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, fromTag);
-//		// to
-//		SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerGBDomain());
-//		Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI);
-//		ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress, subscribeInfo.getFromTag());
-//
-//		// Forwards
-//		MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
-//		// ceq
-//		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(Request.NOTIFY), Request.NOTIFY);
-//		MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipFactory.createMessageFactory();
-//		// 设置编码, 防止中文乱码
-//		messageFactory.setDefaultContentEncodingCharset("gb2312");
-//		request = messageFactory.createRequest(requestURI, Request.NOTIFY, callIdHeader, cSeqHeader, fromHeader,
-//				toHeader, viaHeaders, maxForwards);
-//		List<String> agentParam = new ArrayList<>();
-//		agentParam.add("wvp-pro");
-//		UserAgentHeader userAgentHeader = sipFactory.createHeaderFactory().createUserAgentHeader(agentParam);
-//		request.addHeader(userAgentHeader);
-//
-//		EventHeader event = sipFactory.createHeaderFactory().createEventHeader(subscribeInfo.getEventType());
-//		if (subscribeInfo.getEventId() != null) {
-//			event.setEventId(subscribeInfo.getEventId());
-//		}
-//
-//		request.addHeader(event);
-//
-//		SubscriptionStateHeader active = sipFactory.createHeaderFactory().createSubscriptionStateHeader("active");
-//		request.setHeader(active);
-//
-//		String sipAddress = sipConfig.getIp() + ":" + sipConfig.getPort();
-//		Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory()
-//				.createSipURI(parentPlatform.getDeviceGBId(), sipAddress));
-//		request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
-//
-//		ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
-//		request.setContent(content, contentTypeHeader);
-//		return request;
-//    }
 }

+ 9 - 5
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java

@@ -225,11 +225,16 @@ public class SIPRequestHeaderProvider {
 		return request;
 	}
 
-	public Request createInfoRequest(Device device, StreamInfo streamInfo, String content, Long cseq)
+	public Request createInfoRequest(Device device, StreamInfo streamInfo, String content)
 			throws PeerUnavailableException, ParseException, InvalidArgumentException {
 		Request request = null;
-		if (streamInfo == null) return null;
+		if (streamInfo == null) {
+			return null;
+		}
 		Dialog dialog = streamSession.getDialogByStream(streamInfo.getDeviceID(), streamInfo.getChannelId(), streamInfo.getStream());
+		if (dialog == null) {
+			return null;
+		}
 
 		SipURI requestLine = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(),
 				device.getHostAddress());
@@ -255,9 +260,8 @@ public class SIPRequestHeaderProvider {
 
 		// Forwards
 		MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
-		if (cseq == null) {
-			cseq = redisCatchStorage.getCSEQ(Request.INFO);
-		}
+
+		Long cseq = redisCatchStorage.getCSEQ(Request.INVITE);
 		// ceq
 		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory()
 				.createCSeqHeader(cseq, Request.INFO);

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

@@ -4,11 +4,8 @@ import com.alibaba.fastjson.JSONObject;
 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.UserSetup;
-import com.genersoft.iot.vmp.gb28181.bean.Device;
-import com.genersoft.iot.vmp.gb28181.bean.InviteStreamCallback;
-import com.genersoft.iot.vmp.gb28181.bean.InviteStreamInfo;
-import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
+import com.genersoft.iot.vmp.conf.UserSetting;
+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.cmd.ISIPCommander;
@@ -20,9 +17,10 @@ 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.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import gov.nist.javax.sip.SipProviderImpl;
 import gov.nist.javax.sip.SipStackImpl;
+import gov.nist.javax.sip.message.MessageFactoryImpl;
 import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.stack.SIPDialog;
 import org.slf4j.Logger;
@@ -35,8 +33,7 @@ import org.springframework.util.StringUtils;
 
 import javax.sip.*;
 import javax.sip.address.SipURI;
-import javax.sip.header.CallIdHeader;
-import javax.sip.header.ViaHeader;
+import javax.sip.header.*;
 import javax.sip.message.Request;
 import java.lang.reflect.Field;
 import java.text.ParseException;
@@ -56,6 +53,9 @@ public class SIPCommander implements ISIPCommander {
 	@Autowired
 	private SipConfig sipConfig;
 
+	@Autowired
+	private SipFactory sipFactory;
+
 	@Autowired
 	@Qualifier(value="tcpSipProvider")
 	private SipProviderImpl tcpSipProvider;
@@ -71,13 +71,13 @@ public class SIPCommander implements ISIPCommander {
 	private VideoStreamSessionManager streamSession;
 
 	@Autowired
-	private IVideoManagerStorager storager;
+	private IVideoManagerStorage storager;
 
 	@Autowired
 	private IRedisCatchStorage redisCatchStorage;
 
 	@Autowired
-	private UserSetup userSetup;
+	private UserSetting userSetting;
 
 	@Autowired
 	private ZLMHttpHookSubscribe subscribe;
@@ -229,13 +229,15 @@ public class SIPCommander implements ISIPCommander {
 		try {
 			String cmdStr= cmdString(leftRight, upDown, inOut, moveSpeed, zoomSpeed);
 			StringBuffer ptzXml = new StringBuffer(200);
-			ptzXml.append("<?xml version=\"1.0\" ?>\r\n");
+			String charset = device.getCharset();
+			ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			ptzXml.append("<Control>\r\n");
 			ptzXml.append("<CmdType>DeviceControl</CmdType>\r\n");
 			ptzXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
 			ptzXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
 			ptzXml.append("<PTZCmd>" + cmdStr + "</PTZCmd>\r\n");
 			ptzXml.append("<Info>\r\n");
+			ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
 			ptzXml.append("</Info>\r\n");
 			ptzXml.append("</Control>\r\n");
 			
@@ -270,13 +272,15 @@ public class SIPCommander implements ISIPCommander {
 			String cmdStr= frontEndCmdString(cmdCode, parameter1, parameter2, combineCode2);
 			logger.debug("控制字符串:" + cmdStr);
 			StringBuffer ptzXml = new StringBuffer(200);
-			ptzXml.append("<?xml version=\"1.0\" ?>\r\n");
+			String charset = device.getCharset();
+			ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			ptzXml.append("<Control>\r\n");
 			ptzXml.append("<CmdType>DeviceControl</CmdType>\r\n");
 			ptzXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
 			ptzXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
 			ptzXml.append("<PTZCmd>" + cmdStr + "</PTZCmd>\r\n");
 			ptzXml.append("<Info>\r\n");
+			ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
 			ptzXml.append("</Info>\r\n");
 			ptzXml.append("</Control>\r\n");
 			
@@ -301,16 +305,18 @@ public class SIPCommander implements ISIPCommander {
 	 * @param cmdString		前端控制指令串
 	 */
 	@Override
-	public boolean fronEndCmd(Device device, String channelId, String cmdString) {
+	public boolean fronEndCmd(Device device, String channelId, String cmdString, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) {
 		try {
 			StringBuffer ptzXml = new StringBuffer(200);
-			ptzXml.append("<?xml version=\"1.0\" ?>\r\n");
+			String charset = device.getCharset();
+			ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			ptzXml.append("<Control>\r\n");
 			ptzXml.append("<CmdType>DeviceControl</CmdType>\r\n");
 			ptzXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
 			ptzXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
 			ptzXml.append("<PTZCmd>" + cmdString + "</PTZCmd>\r\n");
 			ptzXml.append("<Info>\r\n");
+			ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
 			ptzXml.append("</Info>\r\n");
 			ptzXml.append("</Control>\r\n");
 			
@@ -320,7 +326,7 @@ public class SIPCommander implements ISIPCommander {
 					: udpSipProvider.getNewCallId();
 
 			Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), "z9hG4bK-ViaPtz-" + tm, "FromPtz" + tm, null, callIdHeader);
-			transmitRequest(device, request);
+			transmitRequest(device, request, errorEvent, okEvent);
 			return true;
 		} catch (SipException | ParseException | InvalidArgumentException e) {
 			e.printStackTrace();
@@ -334,13 +340,15 @@ public class SIPCommander implements ISIPCommander {
 	  * @param channelId  预览通道
 	  * @param event hook订阅
 	  * @param errorEvent sip错误订阅
-	  */
+	*/
 	@Override
 	public void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
-							  ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent) {
+							  ZLMHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) {
 		String streamId = ssrcInfo.getStream();
 		try {
-			if (device == null) return;
+			if (device == null) {
+				return;
+			}
 			String streamMode = device.getStreamMode().toUpperCase();
 
 			logger.info("{} 分配的ZLM为: {} [{}:{}]", streamId, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
@@ -365,7 +373,7 @@ public class SIPCommander implements ISIPCommander {
 			content.append("c=IN IP4 "+ mediaServerItem.getSdpIp() +"\r\n");
 			content.append("t=0 0\r\n");
 
-			if (userSetup.isSeniorSdp()) {
+			if (userSetting.isSeniorSdp()) {
 				if("TCP-PASSIVE".equals(streamMode)) {
 					content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
 				}else if ("TCP-ACTIVE".equals(streamMode)) {
@@ -428,8 +436,9 @@ public class SIPCommander implements ISIPCommander {
 				errorEvent.response(e);
 			}), e ->{
 				// 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
-				streamSession.put(device.getDeviceId(), channelId ,"play", streamId, ssrcInfo.getSsrc(), mediaServerItem.getId(), ((ResponseEvent)e.event).getClientTransaction());
+				streamSession.put(device.getDeviceId(), channelId ,"play", streamId, ssrcInfo.getSsrc(), mediaServerItem.getId(), ((ResponseEvent)e.event).getClientTransaction(), VideoStreamSessionManager.SessionType.play);
 				streamSession.put(device.getDeviceId(), channelId ,"play", e.dialog);
+				okEvent.response(e);
 			});
 
 			
@@ -465,7 +474,7 @@ public class SIPCommander implements ISIPCommander {
 
 			String streamMode = device.getStreamMode().toUpperCase();
 
-			if (userSetup.isSeniorSdp()) {
+			if (userSetting.isSeniorSdp()) {
 				if("TCP-PASSIVE".equals(streamMode)) {
 					content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
 				}else if ("TCP-ACTIVE".equals(streamMode)) {
@@ -537,7 +546,7 @@ public class SIPCommander implements ISIPCommander {
 
 	        transmitRequest(device, request, errorEvent, okEvent -> {
 				ResponseEvent responseEvent = (ResponseEvent) okEvent.event;
-	        	streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), responseEvent.getClientTransaction());
+	        	streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), responseEvent.getClientTransaction(), VideoStreamSessionManager.SessionType.playback);
 				streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), okEvent.dialog);
 			});
 			if (inviteStreamCallback != null) {
@@ -558,8 +567,9 @@ public class SIPCommander implements ISIPCommander {
 	 * @param downloadSpeed 下载倍速参数
 	 */ 
 	@Override
-	public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, String startTime, String endTime, String downloadSpeed, InviteStreamCallback event
-			, SipSubscribe.Event errorEvent) {
+	public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
+								  String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
+								  SipSubscribe.Event errorEvent) {
 		try {
 			logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
 
@@ -572,11 +582,9 @@ public class SIPCommander implements ISIPCommander {
 	        content.append("t="+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime)+" "
 					+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) +"\r\n");
 
-
-
 			String streamMode = device.getStreamMode().toUpperCase();
 
-			if (userSetup.isSeniorSdp()) {
+			if (userSetting.isSeniorSdp()) {
 				if("TCP-PASSIVE".equals(streamMode)) {
 					content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
 				}else if ("TCP-ACTIVE".equals(streamMode)) {
@@ -639,15 +647,20 @@ public class SIPCommander implements ISIPCommander {
 			logger.debug("录像回放添加订阅,订阅内容:" + subscribeKey.toString());
 			subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
 					(MediaServerItem mediaServerItemInUse, JSONObject json)->{
-						event.call(new InviteStreamInfo(mediaServerItem, json, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
+						hookEvent.call(new InviteStreamInfo(mediaServerItem, json, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
 						subscribe.removeSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey);
 					});
 
 	        Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null, callIdHeader, ssrcInfo.getSsrc());
+			if (inviteStreamCallback != null) {
+				inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
+			}
+	        transmitRequest(device, request, errorEvent, okEvent->{
+				ResponseEvent responseEvent = (ResponseEvent) okEvent.event;
+				streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), responseEvent.getClientTransaction(), VideoStreamSessionManager.SessionType.download);
+				streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), okEvent.dialog);
+			});
 
-	        ClientTransaction transaction = transmitRequest(device, request, errorEvent);
-	        streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), transaction);
-	        streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), transaction);
 
 		} catch ( SipException | ParseException | InvalidArgumentException e) {
 			e.printStackTrace();
@@ -683,7 +696,9 @@ public class SIPCommander implements ISIPCommander {
 			if (callId != null) {
 				dialog = streamSession.getDialogByCallId(deviceId, channelId, callId);
 			}else {
-				if (stream == null) return;
+				if (stream == null) {
+					return;
+				}
 				dialog = streamSession.getDialogByStream(deviceId, channelId, stream);
 			}
 			if (ssrcTransaction != null) {
@@ -718,7 +733,7 @@ public class SIPCommander implements ISIPCommander {
 			Request byeRequest = dialog.createRequest(Request.BYE);
 			SipURI byeURI = (SipURI) byeRequest.getRequestURI();
 			SIPRequest request = (SIPRequest)transaction.getRequest();
-			byeURI.setHost(request.getRemoteAddress().getHostName());
+			byeURI.setHost(request.getRemoteAddress().getHostAddress());
 			byeURI.setPort(request.getRemotePort());
 			ViaHeader viaHeader = (ViaHeader) byeRequest.getHeader(ViaHeader.NAME);
 			String protocol = viaHeader.getTransport().toUpperCase();
@@ -762,7 +777,8 @@ public class SIPCommander implements ISIPCommander {
 	public boolean audioBroadcastCmd(Device device) {
 		try {
 			StringBuffer broadcastXml = new StringBuffer(200);
-			broadcastXml.append("<?xml version=\"1.0\" ?>\r\n");
+			String charset = device.getCharset();
+			broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			broadcastXml.append("<Notify>\r\n");
 			broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
 			broadcastXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
@@ -787,7 +803,8 @@ public class SIPCommander implements ISIPCommander {
 	public void audioBroadcastCmd(Device device, SipSubscribe.Event errorEvent) {
 		try {
 			StringBuffer broadcastXml = new StringBuffer(200);
-			broadcastXml.append("<?xml version=\"1.0\" ?>\r\n");
+			String charset = device.getCharset();
+			broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			broadcastXml.append("<Notify>\r\n");
 			broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
 			broadcastXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
@@ -819,7 +836,8 @@ public class SIPCommander implements ISIPCommander {
 	public boolean recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent) {
 		try {
 			StringBuffer cmdXml = new StringBuffer(200);
-			cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
+			String charset = device.getCharset();
+			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			cmdXml.append("<Control>\r\n");
 			cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
@@ -854,7 +872,8 @@ public class SIPCommander implements ISIPCommander {
 	public boolean teleBootCmd(Device device) {
 		try {
 			StringBuffer cmdXml = new StringBuffer(200);
-			cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
+			String charset = device.getCharset();
+			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			cmdXml.append("<Control>\r\n");
 			cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
@@ -886,7 +905,8 @@ public class SIPCommander implements ISIPCommander {
 	public boolean guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent) {
 		try {
 			StringBuffer cmdXml = new StringBuffer(200);
-			cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
+			String charset = device.getCharset();
+			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			cmdXml.append("<Control>\r\n");
 			cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
@@ -917,7 +937,8 @@ public class SIPCommander implements ISIPCommander {
 	public boolean alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent) {
 		try {
 			StringBuffer cmdXml = new StringBuffer(200);
-			cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
+			String charset = device.getCharset();
+			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			cmdXml.append("<Control>\r\n");
 			cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
@@ -961,7 +982,8 @@ public class SIPCommander implements ISIPCommander {
 	public boolean iFrameCmd(Device device, String channelId) {
 		try {
 			StringBuffer cmdXml = new StringBuffer(200);
-			cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
+			String charset = device.getCharset();
+			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			cmdXml.append("<Control>\r\n");
 			cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
@@ -999,7 +1021,8 @@ public class SIPCommander implements ISIPCommander {
 	public boolean homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent) {
 		try {
 			StringBuffer cmdXml = new StringBuffer(200);
-			cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
+			String charset = device.getCharset();
+			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			cmdXml.append("<Control>\r\n");
 			cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
@@ -1067,7 +1090,8 @@ public class SIPCommander implements ISIPCommander {
 										String heartBeatInterval, String heartBeatCount, SipSubscribe.Event errorEvent) {
 		try {
 			StringBuffer cmdXml = new StringBuffer(200);
-			cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
+			String charset = device.getCharset();
+			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			cmdXml.append("<Control>\r\n");
 			cmdXml.append("<CmdType>DeviceConfig</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
@@ -1120,8 +1144,9 @@ public class SIPCommander implements ISIPCommander {
 	@Override
 	public boolean deviceStatusQuery(Device device, SipSubscribe.Event errorEvent) {
 		try {
+			String charset = device.getCharset();
 			StringBuffer catalogXml = new StringBuffer(200);
-			catalogXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
+			catalogXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			catalogXml.append("<Query>\r\n");
 			catalogXml.append("<CmdType>DeviceStatus</CmdType>\r\n");
 			catalogXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
@@ -1153,7 +1178,8 @@ public class SIPCommander implements ISIPCommander {
 	public boolean deviceInfoQuery(Device device) {
 		try {
 			StringBuffer catalogXml = new StringBuffer(200);
-			catalogXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
+			String charset = device.getCharset();
+			catalogXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			catalogXml.append("<Query>\r\n");
 			catalogXml.append("<CmdType>DeviceInfo</CmdType>\r\n");
 			catalogXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
@@ -1182,13 +1208,14 @@ public class SIPCommander implements ISIPCommander {
 	 * @param device 视频设备
 	 */ 
 	@Override
-	public boolean catalogQuery(Device device, SipSubscribe.Event errorEvent) {
+	public boolean catalogQuery(Device device, int sn, SipSubscribe.Event errorEvent) {
 		try {
 			StringBuffer catalogXml = new StringBuffer(200);
-			catalogXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
+			String charset = device.getCharset();
+			catalogXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			catalogXml.append("<Query>\r\n");
 			catalogXml.append("<CmdType>Catalog</CmdType>\r\n");
-			catalogXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
+			catalogXml.append("<SN>" + sn + "</SN>\r\n");
 			catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
 			catalogXml.append("</Query>\r\n");
 			
@@ -1224,7 +1251,8 @@ public class SIPCommander implements ISIPCommander {
 		}
 		try {
 			StringBuffer recordInfoXml = new StringBuffer(200);
-			recordInfoXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
+			String charset = device.getCharset();
+			recordInfoXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			recordInfoXml.append("<Query>\r\n");
 			recordInfoXml.append("<CmdType>RecordInfo</CmdType>\r\n");
 			recordInfoXml.append("<SN>" + sn + "</SN>\r\n");
@@ -1277,7 +1305,8 @@ public class SIPCommander implements ISIPCommander {
 								 String startTime, String endTime, SipSubscribe.Event errorEvent) {
 		try {
 			StringBuffer cmdXml = new StringBuffer(200);
-			cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
+			String charset = device.getCharset();
+			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			cmdXml.append("<Query>\r\n");
 			cmdXml.append("<CmdType>Alarm</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
@@ -1327,7 +1356,8 @@ public class SIPCommander implements ISIPCommander {
 	public boolean deviceConfigQuery(Device device, String channelId, String configType,  SipSubscribe.Event errorEvent) {
 		try {
 			StringBuffer cmdXml = new StringBuffer(200);
-			cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
+			String charset = device.getCharset();
+			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			cmdXml.append("<Query>\r\n");
 			cmdXml.append("<CmdType>ConfigDownload</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
@@ -1362,7 +1392,8 @@ public class SIPCommander implements ISIPCommander {
 	public boolean presetQuery(Device device, String channelId, SipSubscribe.Event errorEvent) {
 		try {
 			StringBuffer cmdXml = new StringBuffer(200);
-			cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
+			String charset = device.getCharset();
+			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			cmdXml.append("<Query>\r\n");
 			cmdXml.append("<CmdType>PresetQuery</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
@@ -1396,7 +1427,8 @@ public class SIPCommander implements ISIPCommander {
 	public boolean mobilePostitionQuery(Device device, SipSubscribe.Event errorEvent) {
 		try {
 			StringBuffer mobilePostitionXml = new StringBuffer(200);
-			mobilePostitionXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
+			String charset = device.getCharset();
+			mobilePostitionXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			mobilePostitionXml.append("<Query>\r\n");
 			mobilePostitionXml.append("<CmdType>MobilePosition</CmdType>\r\n");
 			mobilePostitionXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
@@ -1424,30 +1456,38 @@ public class SIPCommander implements ISIPCommander {
 	 * 订阅、取消订阅移动位置
 	 * 
 	 * @param device	视频设备
-	 * @param expires	订阅超时时间
-	 * @param interval	上报时间间隔
 	 * @return			true = 命令发送成功
 	 */
-	public boolean mobilePositionSubscribe(Device device, int expires, int interval) {
+	@Override
+	public boolean mobilePositionSubscribe(Device device, Dialog dialog, SipSubscribe.Event okEvent ,SipSubscribe.Event errorEvent) {
 		try {
 			StringBuffer subscribePostitionXml = new StringBuffer(200);
-			subscribePostitionXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
+			String charset = device.getCharset();
+			subscribePostitionXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			subscribePostitionXml.append("<Query>\r\n");
 			subscribePostitionXml.append("<CmdType>MobilePosition</CmdType>\r\n");
 			subscribePostitionXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
 			subscribePostitionXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			if (expires > 0) {
-				subscribePostitionXml.append("<Interval>" + String.valueOf(interval) + "</Interval>\r\n");
+			if (device.getSubscribeCycleForMobilePosition() > 0) {
+				subscribePostitionXml.append("<Interval>" + String.valueOf(device.getMobilePositionSubmissionInterval()) + "</Interval>\r\n");
 			}
 			subscribePostitionXml.append("</Query>\r\n");
 
-			String tm = Long.toString(System.currentTimeMillis());
-
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createSubscribeRequest(device, subscribePostitionXml.toString(), "z9hG4bK-viaPos-" + tm, "fromTagPos" + tm, null, expires, "presence" ,callIdHeader); //Position;id=" + tm.substring(tm.length() - 4));
-			transmitRequest(device, request);
+			Request request;
+			if (dialog != null) {
+				logger.info("发送移动位置订阅消息时 dialog的状态为: {}", dialog.getState());
+				request = dialog.createRequest(Request.SUBSCRIBE);
+				ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
+				request.setContent(subscribePostitionXml.toString(), contentTypeHeader);
+				ExpiresHeader expireHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForMobilePosition());
+				request.addHeader(expireHeader);
+			}else {
+				String tm = Long.toString(System.currentTimeMillis());
+				CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+						: udpSipProvider.getNewCallId();
+				request = headerProvider.createSubscribeRequest(device, subscribePostitionXml.toString(), "z9hG4bK-viaPos-" + tm, "fromTagPos" + tm, null, device.getSubscribeCycleForMobilePosition(), "presence" ,callIdHeader); //Position;id=" + tm.substring(tm.length() - 4));
+			}
+			transmitRequest(device, request, errorEvent, okEvent);
 
 			return true;
 
@@ -1470,10 +1510,12 @@ public class SIPCommander implements ISIPCommander {
 	 * @param endTime		报警发生终止时间(可选)
 	 * @return				true = 命令发送成功
 	 */
+	@Override
 	public boolean alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime) {
 		try {
 			StringBuffer cmdXml = new StringBuffer(200);
-			cmdXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
+			String charset = device.getCharset();
+			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			cmdXml.append("<Query>\r\n");
 			cmdXml.append("<CmdType>Alarm</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
@@ -1515,27 +1557,39 @@ public class SIPCommander implements ISIPCommander {
 	}
 
 	@Override
-	public boolean catalogSubscribe(Device device, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) {
+	public boolean catalogSubscribe(Device device, Dialog dialog, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) {
 		try {
 			StringBuffer cmdXml = new StringBuffer(200);
-			cmdXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
+			String charset = device.getCharset();
+			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			cmdXml.append("<Query>\r\n");
 			cmdXml.append("<CmdType>Catalog</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
 			cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
 			cmdXml.append("</Query>\r\n");
 
-			String tm = Long.toString(System.currentTimeMillis());
 
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
+			Request request;
+			if (dialog != null) {
+				logger.info("发送目录订阅消息时 dialog的状态为: {}", dialog.getState());
+				request = dialog.createRequest(Request.SUBSCRIBE);
+				ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
+				request.setContent(cmdXml.toString(), contentTypeHeader);
+				ExpiresHeader expireHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForMobilePosition());
+				request.addHeader(expireHeader);
+			}else {
+				String tm = Long.toString(System.currentTimeMillis());
 
-			// 有效时间默认为60秒以上
-			Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), "z9hG4bK-viaPos-" + tm,
-					"fromTagPos" + tm, null, device.getSubscribeCycleForCatalog(), "Catalog" ,
-					callIdHeader);
-			transmitRequest(device, request, errorEvent, okEvent);
+				CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+						: udpSipProvider.getNewCallId();
 
+				// 有效时间默认为60秒以上
+				request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), "z9hG4bK-viaPos-" + tm,
+						"fromTagPos" + tm, null, device.getSubscribeCycleForCatalog(), "Catalog" ,
+						callIdHeader);
+
+			}
+			transmitRequest(device, request, errorEvent, okEvent);
 			return true;
 
 		} catch ( NumberFormatException | ParseException | InvalidArgumentException	| SipException e) {
@@ -1548,7 +1602,8 @@ public class SIPCommander implements ISIPCommander {
 	public boolean dragZoomCmd(Device device, String channelId, String cmdString) {
 		try {
 			StringBuffer dragXml = new StringBuffer(200);
-			dragXml.append("<?xml version=\"1.0\" ?>\r\n");
+			String charset = device.getCharset();
+			dragXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
 			dragXml.append("<Control>\r\n");
 			dragXml.append("<CmdType>DeviceControl</CmdType>\r\n");
 			dragXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
@@ -1620,7 +1675,7 @@ public class SIPCommander implements ISIPCommander {
 			content.append("PAUSE RTSP/1.0\r\n");
 			content.append("CSeq: " + cseq + "\r\n");
 			content.append("PauseTime: now\r\n");
-			Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString(), cseq);
+			Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString());
 			if (request == null) {
 				return;
 			}
@@ -1651,8 +1706,10 @@ public class SIPCommander implements ISIPCommander {
 			content.append("PLAY RTSP/1.0\r\n");
 			content.append("CSeq: " + cseq + "\r\n");
 			content.append("Range: npt=now-\r\n");
-			Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString(), cseq);
-			if (request == null) return;
+			Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString());
+			if (request == null) {
+				return;
+			}
 			logger.info(request.toString());
 			ClientTransaction clientTransaction = null;
 			if ("TCP".equals(device.getTransport())) {
@@ -1680,8 +1737,10 @@ public class SIPCommander implements ISIPCommander {
 			content.append("CSeq: " + cseq + "\r\n");
 			content.append("Range: npt=" + Math.abs(seekTime) + "-\r\n");
 
-			Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString(), cseq);
-			if (request == null) return;
+			Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString());
+			if (request == null) {
+				return;
+			}
 			logger.info(request.toString());
 			ClientTransaction clientTransaction = null;
 			if ("TCP".equals(device.getTransport())) {
@@ -1708,8 +1767,10 @@ public class SIPCommander implements ISIPCommander {
 			content.append("PLAY RTSP/1.0\r\n");
 			content.append("CSeq: " + cseq + "\r\n");
 			content.append("Scale: " + String.format("%.1f",speed) + "\r\n");
-			Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString(), cseq);
-			if (request == null) return;
+			Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString());
+			if (request == null) {
+				return;
+			}
 			logger.info(request.toString());
 			ClientTransaction clientTransaction = null;
 			if ("TCP".equals(device.getTransport())) {
@@ -1724,4 +1785,103 @@ public class SIPCommander implements ISIPCommander {
 			e.printStackTrace();
 		}
 	}
+
+	@Override
+	public boolean sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) {
+		if (device == null) {
+			return false;
+		}
+		logger.info("[发送 报警通知] {}/{}->{},{}", device.getDeviceId(), deviceAlarm.getChannelId(),
+				deviceAlarm.getLongitude(), deviceAlarm.getLatitude());
+		try {
+			String characterSet = device.getCharset();
+			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 = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+					: udpSipProvider.getNewCallId();
+			String tm = Long.toString(System.currentTimeMillis());
+			Request request = headerProvider.createMessageRequest(device, deviceStatusXml.toString(), "z9hG4bK-ViaPtz-" + tm, "FromPtz" + tm, null, callIdHeader);
+			transmitRequest(device, request);
+
+
+		} catch (SipException | ParseException  e) {
+			e.printStackTrace();
+			return false;
+		} catch (InvalidArgumentException e) {
+			throw new RuntimeException(e);
+		}
+		return true;
+	}
+
+	private void sendNotify(Device device, String catalogXmlContent,
+							SubscribeInfo subscribeInfo, SipSubscribe.Event errorEvent,  SipSubscribe.Event okEvent )
+			throws NoSuchFieldException, IllegalAccessException, SipException, ParseException {
+		MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipFactory.createMessageFactory();
+		String characterSet = device.getCharset();
+		// 设置编码, 防止中文乱码
+		messageFactory.setDefaultContentEncodingCharset(characterSet);
+		Dialog dialog  = subscribeInfo.getDialog();
+		if (dialog == null || !dialog.getState().equals(DialogState.CONFIRMED)) {
+			return;
+		}
+		SIPRequest notifyRequest = (SIPRequest)dialog.createRequest(Request.NOTIFY);
+		ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
+		notifyRequest.setContent(catalogXmlContent, contentTypeHeader);
+
+		SubscriptionStateHeader subscriptionState = sipFactory.createHeaderFactory()
+				.createSubscriptionStateHeader(SubscriptionStateHeader.ACTIVE);
+		notifyRequest.addHeader(subscriptionState);
+
+		EventHeader event = sipFactory.createHeaderFactory().createEventHeader(subscribeInfo.getEventType());
+		if (subscribeInfo.getEventId() != null) {
+			event.setEventId(subscribeInfo.getEventId());
+		}
+		notifyRequest.addHeader(event);
+
+		SipURI sipURI = (SipURI) notifyRequest.getRequestURI();
+		if (subscribeInfo.getTransaction() != null) {
+			SIPRequest request = (SIPRequest) subscribeInfo.getTransaction().getRequest();
+			sipURI.setHost(request.getRemoteAddress().getHostAddress());
+			sipURI.setPort(request.getRemotePort());
+		}else {
+			sipURI.setHost(device.getIp());
+			sipURI.setPort(device.getPort());
+		}
+
+		ClientTransaction transaction = null;
+		if ("TCP".equals(device.getTransport())) {
+			transaction = tcpSipProvider.getNewClientTransaction(notifyRequest);
+		} else if ("UDP".equals(device.getTransport())) {
+			transaction = udpSipProvider.getNewClientTransaction(notifyRequest);
+		}
+		// 添加错误订阅
+		if (errorEvent != null) {
+			sipSubscribe.addErrorSubscribe(subscribeInfo.getCallId(), errorEvent);
+		}
+		// 添加订阅
+		if (okEvent != null) {
+			sipSubscribe.addOkSubscribe(subscribeInfo.getCallId(), okEvent);
+		}
+		if (transaction == null) {
+			logger.error("平台{}的Transport错误:{}",device.getDeviceId(), device.getTransport());
+			return;
+		}
+		dialog.sendRequest(transaction);
+
+	}
 }

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

@@ -32,6 +32,7 @@ import javax.sip.header.*;
 import javax.sip.message.Request;
 import java.lang.reflect.Field;
 import java.text.ParseException;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.UUID;
@@ -145,9 +146,9 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
     public String keepalive(ParentPlatform parentPlatform) {
         String callId = null;
         try {
-
+            String characterSet = parentPlatform.getCharacterSet();
             StringBuffer keepaliveXml = new StringBuffer(200);
-            keepaliveXml.append("<?xml version=\"1.0\"?>\r\n");
+            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");
@@ -215,56 +216,110 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
             return false;
         }
         try {
-            StringBuffer catalogXml = new StringBuffer(600);
-            catalogXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\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=\"1\">\r\n");
-            catalogXml.append("<Item>\r\n");
+            List<DeviceChannel> channels = new ArrayList<>();
             if (channel != null) {
+                channels.add(channel);
+            }
+            String catalogXml = getCatalogXml(channels, sn, parentPlatform, size);
+
+            // callid
+            CallIdHeader callIdHeader = parentPlatform.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                    : udpSipProvider.getNewCallId();
+
+            Request request = headerProviderPlarformProvider.createMessageRequest(parentPlatform, catalogXml.toString(), fromTag, callIdHeader);
+            transmitRequest(parentPlatform, request);
+
+        } catch (SipException | ParseException | InvalidArgumentException e) {
+            e.printStackTrace();
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean catalogQuery(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag) {
+        if ( parentPlatform ==null) {
+            return false;
+        }
+        sendCatalogResponse(channels, parentPlatform, sn, fromTag, 0);
+        return 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");
+        if (channels.size() > 0) {
+            for (DeviceChannel channel : channels) {
+                catalogXml.append("<Item>\r\n");
                 catalogXml.append("<DeviceID>" + channel.getChannelId() + "</DeviceID>\r\n");
                 catalogXml.append("<Name>" + channel.getName() + "</Name>\r\n");
-                catalogXml.append("<Manufacturer>" + channel.getManufacture() + "</Manufacturer>\r\n");
-                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("<Parental>" + channel.getParental() + "</Parental>\r\n");
                 if (channel.getParentId() != null) {
                     catalogXml.append("<ParentID>" + channel.getParentId() + "</ParentID>\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("<Longitude>" + channel.getLongitude() + "</Longitude>\r\n");
-                catalogXml.append("<Latitude>" + channel.getLatitude() + "</Latitude>\r\n");
-                catalogXml.append("<IPAddress>" + channel.getIpAddress() + "</IPAddress>\r\n");
-                catalogXml.append("<Port>" + channel.getPort() + "</Port>\r\n");
-                catalogXml.append("<Info>\r\n");
-                catalogXml.append("<PTZType>" + channel.getPTZType() + "</PTZType>\r\n");
-                catalogXml.append("</Info>\r\n");
-            }
+                if (channel.getChannelId().length() == 20) {
+                    if (Integer.parseInt(channel.getChannelId().substring(10, 13)) == 216) { // 虚拟组织增加BusinessGroupID字段
+                        catalogXml.append("<BusinessGroupID>" + channel.getParentId() + "</BusinessGroupID>\r\n");
+                    }
+                    catalogXml.append("<Manufacturer>" + channel.getManufacture() + "</Manufacturer>\r\n");
+                    catalogXml.append("<RegisterWay>" + channel.getRegisterWay() + "</RegisterWay>\r\n");
+                    catalogXml.append("<Status>" + (channel.getStatus() == 0?"OFF":"ON") + "</Status>\r\n");
+                    if (channel.getChannelType() != 2) { // 业务分组/虚拟组织/行政区划 不设置以下字段
+                        catalogXml.append("<Secrecy>" + channel.getSecrecy() + "</Secrecy>\r\n");
+                        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("<Longitude>" + channel.getLongitude() + "</Longitude>\r\n");
+                        catalogXml.append("<Latitude>" + channel.getLatitude() + "</Latitude>\r\n");
+                        catalogXml.append("<IPAddress>" + channel.getIpAddress() + "</IPAddress>\r\n");
+                        catalogXml.append("<Port>" + channel.getPort() + "</Port>\r\n");
+                        catalogXml.append("<Info>\r\n");
+                        catalogXml.append("<PTZType>" + channel.getPTZType() + "</PTZType>\r\n");
+                        catalogXml.append("</Info>\r\n");
+                    }
+                }
 
 
-            catalogXml.append("</Item>\r\n");
-            catalogXml.append("</DeviceList>\r\n");
-            catalogXml.append("</Response>\r\n");
+                catalogXml.append("</Item>\r\n");
+            }
+        }
+
+        catalogXml.append("</DeviceList>\r\n");
+        catalogXml.append("</Response>\r\n");
+        return catalogXml.toString();
+    }
 
+    private void sendCatalogResponse(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag, int index) {
+        if (index >= channels.size()) {
+            return;
+        }
+        try {
+            List<DeviceChannel> deviceChannels;
+            if (index + parentPlatform.getCatalogGroup() < channels.size()) {
+                deviceChannels = channels.subList(index, index + parentPlatform.getCatalogGroup());
+            }else {
+                deviceChannels = channels.subList(index, channels.size());
+            }
+            String catalogXml = getCatalogXml(deviceChannels, sn, parentPlatform, channels.size());
             // callid
             CallIdHeader callIdHeader = parentPlatform.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
                     : udpSipProvider.getNewCallId();
 
-            Request request = headerProviderPlarformProvider.createMessageRequest(parentPlatform, catalogXml.toString(), fromTag, callIdHeader);
-            transmitRequest(parentPlatform, request);
-
+            Request request = headerProviderPlarformProvider.createMessageRequest(parentPlatform, catalogXml, fromTag, callIdHeader);
+            transmitRequest(parentPlatform, request, null, eventResult -> {
+                int indexNext = index + parentPlatform.getCatalogGroup();
+                sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext);
+            });
         } catch (SipException | ParseException | InvalidArgumentException e) {
             e.printStackTrace();
-            return false;
         }
-        return true;
     }
 
     /**
@@ -280,8 +335,9 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
             return false;
         }
         try {
+            String characterSet = parentPlatform.getCharacterSet();
             StringBuffer deviceInfoXml = new StringBuffer(600);
-            deviceInfoXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
+            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");
@@ -319,8 +375,9 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
             return false;
         }
         try {
+            String characterSet = parentPlatform.getCharacterSet();
             StringBuffer deviceStatusXml = new StringBuffer(600);
-            deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
+            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");
@@ -348,10 +405,11 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         if (parentPlatform == null) {
             return false;
         }
-
+        logger.info("[发送 移动位置订阅] {}/{}->{},{}", parentPlatform.getServerGBId(), gpsMsgInfo.getId(), gpsMsgInfo.getLng(), gpsMsgInfo.getLat());
         try {
+            String characterSet = parentPlatform.getCharacterSet();
             StringBuffer deviceStatusXml = new StringBuffer(600);
-            deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
+            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");
@@ -368,7 +426,6 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                     : udpSipProvider.getNewCallId();
             callIdHeader.setCallId(subscribeInfo.getCallId());
 
-//
             sendNotify(parentPlatform, deviceStatusXml.toString(), subscribeInfo, eventResult -> {
                 logger.error("发送NOTIFY通知消息失败。错误:{} {}", eventResult.statusCode, eventResult.msg);
             }, null);
@@ -384,6 +441,48 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         return true;
     }
 
+    @Override
+    public boolean sendAlarmMessage(ParentPlatform parentPlatform, DeviceAlarm deviceAlarm) {
+        if (parentPlatform == null) {
+            return false;
+        }
+        logger.info("[发送 报警订阅] {}/{}->{},{}", parentPlatform.getServerGBId(), deviceAlarm.getChannelId(),
+                deviceAlarm.getLongitude(), deviceAlarm.getLatitude());
+        try {
+            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().equals("TCP") ? tcpSipProvider.getNewCallId()
+                    : udpSipProvider.getNewCallId();
+
+            String tm = Long.toString(System.currentTimeMillis());
+            Request request = headerProviderPlarformProvider.createMessageRequest(parentPlatform, deviceStatusXml.toString(), "FromPtz" + tm, callIdHeader);
+            transmitRequest(parentPlatform, request);
+
+        } catch (SipException | ParseException  e) {
+            e.printStackTrace();
+            return false;
+        } catch (InvalidArgumentException e) {
+            throw new RuntimeException(e);
+        }
+        return true;
+    }
+
     @Override
     public boolean sendNotifyForCatalogAddOrUpdate(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels, SubscribeInfo subscribeInfo, Integer index) {
         if (parentPlatform == null || deviceChannels == null || deviceChannels.size() == 0 || subscribeInfo == null) {
@@ -395,13 +494,21 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         if (index >= deviceChannels.size()) {
             return true;
         }
+        List<DeviceChannel> channels;
+        if (index + parentPlatform.getCatalogGroup() < deviceChannels.size()) {
+            channels = deviceChannels.subList(index, index + parentPlatform.getCatalogGroup());
+        }else {
+            channels = deviceChannels.subList(index, deviceChannels.size());
+        }
         try {
             Integer finalIndex = index;
-            String catalogXmlContent = getCatalogXmlContentForCatalogAddOrUpdate(parentPlatform, deviceChannels.get(index ), deviceChannels.size(), type, subscribeInfo);
+            String catalogXmlContent = getCatalogXmlContentForCatalogAddOrUpdate(parentPlatform, channels,
+                    deviceChannels.size(), type, subscribeInfo);
             sendNotify(parentPlatform, catalogXmlContent, subscribeInfo, eventResult -> {
                 logger.error("发送NOTIFY通知消息失败。错误:{} {}", eventResult.statusCode, eventResult.msg);
             }, (eventResult -> {
-                sendNotifyForCatalogAddOrUpdate(type, parentPlatform, deviceChannels, subscribeInfo, finalIndex + 1);
+                sendNotifyForCatalogAddOrUpdate(type, parentPlatform, deviceChannels, subscribeInfo,
+                        finalIndex + parentPlatform.getCatalogGroup());
             }));
         } catch (SipException | ParseException e) {
             e.printStackTrace();
@@ -418,10 +525,13 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                                    SubscribeInfo subscribeInfo, SipSubscribe.Event errorEvent,  SipSubscribe.Event okEvent )
             throws NoSuchFieldException, IllegalAccessException, SipException, ParseException {
 		MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipFactory.createMessageFactory();
+        String characterSet = parentPlatform.getCharacterSet();
  		// 设置编码, 防止中文乱码
-		messageFactory.setDefaultContentEncodingCharset("gb2312");
+		messageFactory.setDefaultContentEncodingCharset(characterSet);
         Dialog dialog  = subscribeInfo.getDialog();
-        if (dialog == null) return;
+        if (dialog == null || !dialog.getState().equals(DialogState.CONFIRMED)) {
+            return;
+        }
         SIPRequest notifyRequest = (SIPRequest)dialog.createRequest(Request.NOTIFY);
         ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
         notifyRequest.setContent(catalogXmlContent, contentTypeHeader);
@@ -435,11 +545,16 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
             event.setEventId(subscribeInfo.getEventId());
         }
         notifyRequest.addHeader(event);
-
         SipURI sipURI = (SipURI) notifyRequest.getRequestURI();
-        SIPRequest request = (SIPRequest) subscribeInfo.getTransaction().getRequest();
-        sipURI.setHost(request.getRemoteAddress().getHostName());
-        sipURI.setPort(request.getRemotePort());
+        if (subscribeInfo.getTransaction() != null) {
+            SIPRequest request = (SIPRequest) subscribeInfo.getTransaction().getRequest();
+            sipURI.setHost(request.getRemoteAddress().getHostAddress());
+            sipURI.setPort(request.getRemotePort());
+        }else {
+            sipURI.setHost(parentPlatform.getServerIP());
+            sipURI.setPort(parentPlatform.getServerPort());
+        }
+
         ClientTransaction transaction = null;
         if ("TCP".equals(parentPlatform.getTransport())) {
             transaction = tcpSipProvider.getNewClientTransaction(notifyRequest);
@@ -462,52 +577,48 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
 
     }
 
-//    private Request getCatalogNotifyRequestForCatalogAddOrUpdate(ParentPlatform parentPlatform, DeviceChannel channel, int size, String type,
-//                                            SubscribeInfo subscribeInfo) throws ParseException, InvalidArgumentException,
-//            PeerUnavailableException, NoSuchFieldException, IllegalAccessException {
-//        String catalogXmlContent = getCatalogXmlContentForCatalogAddOrUpdate(parentPlatform, channel, size, type, subscribeInfo);
-//
-//        CallIdHeader callIdHeader = parentPlatform.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-//                : udpSipProvider.getNewCallId();
-//        callIdHeader.setCallId(subscribeInfo.getCallId());
-//        String tm = Long.toString(System.currentTimeMillis());
-//
-//        Request request = headerProviderPlarformProvider.createNotifyRequest(parentPlatform, catalogXmlContent,
-//                callIdHeader, "z9hG4bK-" + UUID.randomUUID().toString().replace("-", ""),"FromRegister" + tm, subscribeInfo);
-//        return request;
-//    }
-
-    private  String getCatalogXmlContentForCatalogAddOrUpdate(ParentPlatform parentPlatform, DeviceChannel channel, int sumNum, String type, SubscribeInfo subscribeInfo) {
+    private  String getCatalogXmlContentForCatalogAddOrUpdate(ParentPlatform parentPlatform, List<DeviceChannel> channels, int sumNum, String type, SubscribeInfo subscribeInfo) {
         StringBuffer catalogXml = new StringBuffer(600);
-        if (parentPlatform.getServerGBId().equals(channel.getParentId())) {
-            channel.setParentId(parentPlatform.getDeviceGBId());
-        }
-        catalogXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
+
+        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=\"1\">\r\n");
-        catalogXml.append("<Item>\r\n");
-        catalogXml.append("<DeviceID>" + channel.getChannelId() + "</DeviceID>\r\n");
-        catalogXml.append("<Name>" + channel.getName() + "</Name>\r\n");
-        catalogXml.append("<Manufacturer>" + channel.getManufacture() + "</Manufacturer>\r\n");
-        catalogXml.append("<Model>" + channel.getModel() + "</Model>\r\n");
-        catalogXml.append("<Owner>0</Owner>\r\n");
-        catalogXml.append("<CivilCode>CivilCode</CivilCode>\r\n");
-        catalogXml.append("<Address>" + channel.getAddress() + "</Address>\r\n");
-        catalogXml.append("<Parental>" + channel.getParental() + "</Parental>\r\n");
-        if (channel.getParentId() != null) {
-            catalogXml.append("<ParentID>" + channel.getParentId() + "</ParentID>\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");
-        if (!"presence".equals(subscribeInfo.getEventType())) {
-            catalogXml.append("<Event>" + type + "</Event>\r\n");
-        }
-        catalogXml.append("</Item>\r\n");
+        catalogXml.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("<Name>" + channel.getName() + "</Name>\r\n");
+                catalogXml.append("<Manufacturer>" + channel.getManufacture() + "</Manufacturer>\r\n");
+                catalogXml.append("<Parental>" + channel.getParental() + "</Parental>\r\n");
+                if (channel.getParentId() != null) {
+                    catalogXml.append("<ParentID>" + channel.getParentId() + "</ParentID>\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");
+                if (channel.getChannelId().length() == 20 && Integer.parseInt(channel.getChannelId().substring(10, 13)) == 216) { // 虚拟组织增加BusinessGroupID字段
+                    catalogXml.append("<BusinessGroupID>" + channel.getParentId() + "</BusinessGroupID>\r\n");
+                }
+                if (channel.getChannelType() == 2) {  // 业务分组/虚拟组织/行政区划 不设置以下属性
+                    catalogXml.append("<Model>" + channel.getModel() + "</Model>\r\n");
+                    catalogXml.append("<Owner>0</Owner>\r\n");
+                    catalogXml.append("<CivilCode>CivilCode</CivilCode>\r\n");
+                    catalogXml.append("<Address>" + channel.getAddress() + "</Address>\r\n");
+                }
+                if (!"presence".equals(subscribeInfo.getEventType())) {
+                    catalogXml.append("<Event>" + type + "</Event>\r\n");
+                }
+                catalogXml.append("</Item>\r\n");
+            }
+        }
         catalogXml.append("</DeviceList>\r\n");
         catalogXml.append("</Notify>\r\n");
         return catalogXml.toString();
@@ -529,13 +640,20 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         if (index >= deviceChannels.size()) {
             return true;
         }
+        List<DeviceChannel> channels;
+        if (index + parentPlatform.getCatalogGroup() < deviceChannels.size()) {
+            channels = deviceChannels.subList(index, index + parentPlatform.getCatalogGroup());
+        }else {
+            channels = deviceChannels.subList(index, deviceChannels.size());
+        }
         try {
             Integer finalIndex = index;
-            String catalogXmlContent = getCatalogXmlContentForCatalogOther(parentPlatform, deviceChannels.get(index), type);
+            String catalogXmlContent = getCatalogXmlContentForCatalogOther(parentPlatform, channels, type);
             sendNotify(parentPlatform, catalogXmlContent, subscribeInfo, eventResult -> {
                 logger.error("发送NOTIFY通知消息失败。错误:{} {}", eventResult.statusCode, eventResult.msg);
             }, (eventResult -> {
-                sendNotifyForCatalogOther(type, parentPlatform, deviceChannels, subscribeInfo, finalIndex + 1);
+                sendNotifyForCatalogOther(type, parentPlatform, deviceChannels, subscribeInfo,
+                        finalIndex + parentPlatform.getCatalogGroup());
             }));
         } catch (SipException e) {
             e.printStackTrace();
@@ -550,22 +668,28 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         return true;
     }
 
-    private String getCatalogXmlContentForCatalogOther(ParentPlatform parentPlatform, DeviceChannel channel, String type) {
-        if (parentPlatform.getServerGBId().equals(channel.getParentId())) {
-            channel.setParentId(parentPlatform.getDeviceGBId());
-        }
+    private String getCatalogXmlContentForCatalogOther(ParentPlatform parentPlatform, List<DeviceChannel> channels, String type) {
+
+        String characterSet = parentPlatform.getCharacterSet();
         StringBuffer catalogXml = new StringBuffer(600);
-        catalogXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
+        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=\"1\">\r\n");
-        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("<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("</DeviceList>\r\n");
         catalogXml.append("</Notify>\r\n");
         return catalogXml.toString();
@@ -576,8 +700,9 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
             return false;
         }
         try {
+            String characterSet = parentPlatform.getCharacterSet();
             StringBuffer recordXml = new StringBuffer(600);
-            recordXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
+            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");
@@ -661,7 +786,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
 
                         SipURI byeURI = (SipURI) byeRequest.getRequestURI();
                         SIPRequest request = (SIPRequest) clientTransaction.getRequest();
-                        byeURI.setHost(request.getRemoteAddress().getHostName());
+                        byeURI.setHost(request.getRemoteAddress().getHostAddress());
                         byeURI.setPort(request.getRemotePort());
                         if ("TCP".equals(platform.getTransport())) {
                             clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest);

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

@@ -139,7 +139,9 @@ public abstract class SIPRequestProcessorParent {
 		serverTransaction.sendResponse(response);
 		if (statusCode >= 200 && !"NOTIFY".equals(evt.getRequest().getMethod())) {
 
-			if (serverTransaction.getDialog() != null) serverTransaction.getDialog().delete();
+			if (serverTransaction.getDialog() != null) {
+				serverTransaction.getDialog().delete();
+			}
 		}
 	}
 
@@ -149,7 +151,9 @@ public abstract class SIPRequestProcessorParent {
 		ServerTransaction serverTransaction = getServerTransaction(evt);
 		serverTransaction.sendResponse(response);
 		if (statusCode >= 200 && !"NOTIFY".equals(evt.getRequest().getMethod())) {
-			if (serverTransaction.getDialog() != null) serverTransaction.getDialog().delete();
+			if (serverTransaction.getDialog() != null) {
+				serverTransaction.getDialog().delete();
+			}
 		}
 	}
 
@@ -215,7 +219,9 @@ public abstract class SIPRequestProcessorParent {
 		return getRootElement(evt, "gb2312");
 	}
 	public Element getRootElement(RequestEvent evt, String charset) throws DocumentException {
-		if (charset == null) charset = "gb2312";
+		if (charset == null) {
+			charset = "gb2312";
+		}
 		Request request = evt.getRequest();
 		SAXReader reader = new SAXReader();
 		reader.setEncoding(charset);

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

@@ -72,7 +72,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
 	public void process(RequestEvent evt) {
 		Dialog dialog = evt.getDialog();
 		CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
-		if (dialog == null) return;
+		if (dialog == null) {
+			return;
+		}
 		if (dialog.getState()== DialogState.CONFIRMED) {
 			String platformGbId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser();
 			logger.info("ACK请求: platformGbId->{}", platformGbId);

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

@@ -2,7 +2,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 
 import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
-import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
+import com.genersoft.iot.vmp.gb28181.bean.InviteStreamType;
 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
 import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
@@ -13,8 +13,9 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorP
 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.MessageForPushChannel;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.SerializeUtils;
 import gov.nist.javax.sip.stack.SIPDialog;
 import org.slf4j.Logger;
@@ -50,7 +51,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 	private IRedisCatchStorage redisCatchStorage;
 
 	@Autowired
-	private IVideoManagerStorager storager;
+	private IVideoManagerStorage storager;
 
 	@Autowired
 	private ZLMRTPServerFactory zlmrtpServerFactory;
@@ -80,7 +81,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 			responseAck(evt, Response.OK);
 			Dialog dialog = evt.getDialog();
 			CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
-			if (dialog == null) return;
+			if (dialog == null) {
+				return;
+			}
 			if (dialog.getState().equals(DialogState.TERMINATED)) {
 				String platformGbId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser();
 				String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
@@ -99,10 +102,20 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 					redisCatchStorage.deleteSendRTPServer(platformGbId, channelId, callIdHeader.getCallId(), null);
 					int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId);
 					if (totalReaderCount <= 0) {
-						logger.info("收到bye: {}无其它观看者,通知设备停止推流", streamId);
-						if (sendRtpItem.isPlay()) {
+						logger.info("收到bye: {} 无其它观看者,通知设备停止推流", streamId);
+						if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) {
 							cmder.streamByeCmd(sendRtpItem.getDeviceId(), channelId, streamId, null);
 						}
+						if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) {
+							MessageForPushChannel messageForPushChannel = new MessageForPushChannel();
+							messageForPushChannel.setType(0);
+							messageForPushChannel.setGbId(sendRtpItem.getChannelId());
+							messageForPushChannel.setApp(sendRtpItem.getApp());
+							messageForPushChannel.setStream(sendRtpItem.getStreamId());
+							messageForPushChannel.setMediaServerId(sendRtpItem.getMediaServerId());
+							messageForPushChannel.setPlatFormId(sendRtpItem.getPlatformId());
+							redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel);
+						}
 					}
 				}
 				// 可能是设备主动停止

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

@@ -1,14 +1,12 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 
-import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
-import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.DynamicTask;
+import com.genersoft.iot.vmp.conf.UserSetting;
 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.SIPProcessorObserver;
-import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
@@ -16,19 +14,18 @@ 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.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.IPlayService;
+import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
 import com.genersoft.iot.vmp.service.bean.SSRCInfo;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.SerializeUtils;
-import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
 import gov.nist.javax.sdp.TimeDescriptionImpl;
 import gov.nist.javax.sdp.fields.TimeField;
-import gov.nist.javax.sip.address.AddressImpl;
-import gov.nist.javax.sip.address.SipUri;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.InitializingBean;
@@ -36,19 +33,14 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import javax.sdp.*;
-import javax.sip.InvalidArgumentException;
-import javax.sip.RequestEvent;
-import javax.sip.ServerTransaction;
-import javax.sip.SipException;
+import javax.sip.*;
 import javax.sip.address.SipURI;
 import javax.sip.header.CallIdHeader;
-import javax.sip.header.FromHeader;
 import javax.sip.message.Request;
 import javax.sip.message.Response;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
-import java.util.List;
 import java.util.Vector;
 
 /**
@@ -66,7 +58,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 	private SIPCommanderFroPlatform cmderFroPlatform;
 
 	@Autowired
-	private IVideoManagerStorager storager;
+	private IVideoManagerStorage storager;
 
 	@Autowired
 	private IRedisCatchStorage  redisCatchStorage;
@@ -95,6 +87,12 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 	@Autowired
 	private VideoStreamSessionManager sessionManager;
 
+	@Autowired
+	private UserSetting userSetting;
+
+	@Autowired
+	private ZLMMediaListManager mediaListManager;
+
 
 	@Override
 	public void afterPropertiesSet() throws Exception {
@@ -114,7 +112,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 		try {
 			Request request = evt.getRequest();
 			SipURI sipURI = (SipURI) request.getRequestURI();
-			String channelId = sipURI.getUser();
+			//从subject读取channelId,不再从request-line读取。 有些平台request-line是平台国标编码,不是设备国标编码。
+			//String channelId = sipURI.getUser();
+			String channelId = SipUtils.getChannelIdFromHeader(request);
 			String requesterId = SipUtils.getUserIdFromFromHeader(request);
 			CallIdHeader callIdHeader = (CallIdHeader)request.getHeader(CallIdHeader.NAME);
 			if (requesterId == null || channelId == null) {
@@ -149,12 +149,6 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 						responseAck(evt, Response.GONE);
 						return;
 					}
-					Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream());
-					if (!streamReady ) {
-						logger.info("[ app={}, stream={} ]通道离线,返回400",gbStream.getApp(), gbStream.getStream());
-						responseAck(evt, Response.BAD_REQUEST, "channel [" + gbStream.getGbId() + "] offline");
-						return;
-					}
 					responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在,发181,呼叫转接中
 				}else if (catalog != null) {
 					responseAck(evt, Response.BAD_REQUEST, "catalog channel can not play"); // 目录不支持点播
@@ -221,6 +215,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 								mediaTransmissionTCP = true;
 								if ("active".equals(setup)) {
 									tcpActive = true;
+									// 不支持tcp主动
+									responseAck(evt, Response.NOT_IMPLEMENTED, "tcp active not support"); // 目录不支持点播
+									return;
 								} else if ("passive".equals(setup)) {
 									tcpActive = false;
 								}
@@ -266,13 +263,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 						return;
 					}
 					sendRtpItem.setCallId(callIdHeader.getCallId());
-					sendRtpItem.setPlay("Play".equals(sessionName));
+					sendRtpItem.setPlayType("Play".equals(sessionName)?InviteStreamType.PLAY:InviteStreamType.PLAYBACK);
 					byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog());
 					sendRtpItem.setDialog(dialogByteArray);
 					byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction());
 					sendRtpItem.setTransaction(transactionByteArray);
-
-
 					Long finalStartTime = startTime;
 					Long finalStopTime = stopTime;
 					ZLMHttpHookSubscribe.Event hookEvent = (mediaServerItemInUSe, responseJSON)->{
@@ -308,7 +303,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 								mediaServerService.releaseSsrc(mediaServerItemInUSe.getId(), ssrc);
 								// 回复bye
 								cmderFroPlatform.streamByeCmd(platform, callIdHeader.getCallId());
-							}, 60);
+							}, 60*1000);
 							responseSdpAck(evt, content.toString(), platform);
 
 						} catch (SipException e) {
@@ -326,14 +321,16 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 							response = getMessageFactory().createResponse(event.statusCode, evt.getRequest());
 							ServerTransaction serverTransaction = getServerTransaction(evt);
 							serverTransaction.sendResponse(response);
-							if (serverTransaction.getDialog() != null) serverTransaction.getDialog().delete();
+							if (serverTransaction.getDialog() != null) {
+								serverTransaction.getDialog().delete();
+							}
 						} catch (ParseException | SipException | InvalidArgumentException e) {
 							e.printStackTrace();
 						}
 					});
 					sendRtpItem.setApp("rtp");
 					if ("Playback".equals(sessionName)) {
-						sendRtpItem.setPlay(false);
+						sendRtpItem.setPlayType(InviteStreamType.PLAYBACK);
 						SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, true);
 						sendRtpItem.setStreamId(ssrcInfo.getStream());
 						// 写入redis, 超时时回复
@@ -363,7 +360,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 								}
 							});
 					}else {
-						sendRtpItem.setPlay(true);
+						sendRtpItem.setPlayType(InviteStreamType.PLAY);
 						SsrcTransaction playTransaction = sessionManager.getSsrcTransaction(device.getDeviceId(), channelId, "play", null);
 						if (playTransaction != null) {
 							Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, "rtp", playTransaction.getStream());
@@ -394,48 +391,109 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 						}
 					}
 				}else if (gbStream != null) {
-					SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
-							gbStream.getApp(), gbStream.getStream(), channelId,
-							mediaTransmissionTCP);
 
-					if (tcpActive != null) {
-						sendRtpItem.setTcpActive(tcpActive);
-					}
-					if (sendRtpItem == null) {
-						logger.warn("服务器端口资源不足");
-						responseAck(evt, Response.BUSY_HERE);
-						return;
-					}
+					Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream());
+					if (!streamReady ) {
+						if ("proxy".equals(gbStream.getStreamType())) {
+							// TODO 控制启用以使设备上线
+							logger.info("[ app={}, stream={} ]通道离线,启用流后开始推流",gbStream.getApp(), gbStream.getStream());
+							responseAck(evt, Response.BAD_REQUEST, "channel [" + gbStream.getGbId() + "] offline");
+						}else if ("push".equals(gbStream.getStreamType())) {
+							if (!platform.isStartOfflinePush()) {
+								responseAck(evt, Response.TEMPORARILY_UNAVAILABLE, "channel unavailable");
+								return;
+							}
+							// 发送redis消息以使设备上线
+							logger.info("[ app={}, stream={} ]通道离线,发送redis信息控制设备开始推流",gbStream.getApp(), gbStream.getStream());
+							MessageForPushChannel messageForPushChannel = new MessageForPushChannel();
+							messageForPushChannel.setType(1);
+							messageForPushChannel.setGbId(gbStream.getGbId());
+							messageForPushChannel.setApp(gbStream.getApp());
+							messageForPushChannel.setStream(gbStream.getStream());
+							// TODO 获取低负载的节点
+							messageForPushChannel.setMediaServerId(gbStream.getMediaServerId());
+							messageForPushChannel.setPlatFormId(platform.getServerGBId());
+							messageForPushChannel.setPlatFormName(platform.getName());
+							redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel);
+							// 设置超时
+							dynamicTask.startDelay(callIdHeader.getCallId(), ()->{
+								logger.info("[ app={}, stream={} ] 等待设备开始推流超时", gbStream.getApp(), gbStream.getStream());
+								try {
+									mediaListManager.removedChannelOnlineEventLister(gbStream.getGbId());
+									responseAck(evt, Response.REQUEST_TIMEOUT); // 超时
+								} catch (SipException e) {
+									e.printStackTrace();
+								} catch (InvalidArgumentException e) {
+									e.printStackTrace();
+								} catch (ParseException e) {
+									e.printStackTrace();
+								}
+							}, userSetting.getPlatformPlayTimeout());
+							// 添加监听
+							MediaServerItem finalMediaServerItem = mediaServerItem;
+							int finalPort = port;
+							boolean finalMediaTransmissionTCP = mediaTransmissionTCP;
+							Boolean finalTcpActive = tcpActive;
+							mediaListManager.addChannelOnlineEventLister(gbStream.getGbId(), (app, stream)->{
+								SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(finalMediaServerItem, addressStr, finalPort, ssrc, requesterId,
+										app, stream, channelId, finalMediaTransmissionTCP);
+
+								if (sendRtpItem == null) {
+									logger.warn("服务器端口资源不足");
+									try {
+										responseAck(evt, Response.BUSY_HERE);
+									} catch (SipException e) {
+										e.printStackTrace();
+									} catch (InvalidArgumentException e) {
+										e.printStackTrace();
+									} catch (ParseException e) {
+										e.printStackTrace();
+									}
+									return;
+								}
+								if (finalTcpActive != null) {
+									sendRtpItem.setTcpActive(finalTcpActive);
+								}
+								sendRtpItem.setPlayType(InviteStreamType.PUSH);
+								// 写入redis, 超时时回复
+								sendRtpItem.setStatus(1);
+								sendRtpItem.setCallId(callIdHeader.getCallId());
+								byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog());
+								sendRtpItem.setDialog(dialogByteArray);
+								byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction());
+								sendRtpItem.setTransaction(transactionByteArray);
+								redisCatchStorage.updateSendRTPSever(sendRtpItem);
+								sendStreamAck(finalMediaServerItem, sendRtpItem, platform, evt);
 
-					// 写入redis, 超时时回复
-					sendRtpItem.setStatus(1);
-					sendRtpItem.setCallId(callIdHeader.getCallId());
-					byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog());
-					sendRtpItem.setDialog(dialogByteArray);
-					byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction());
-					sendRtpItem.setTransaction(transactionByteArray);
-					redisCatchStorage.updateSendRTPSever(sendRtpItem);
-					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("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");
-					content.append("a=sendonly\r\n");
-					content.append("a=rtpmap:96 PS/90000\r\n");
-					content.append("y="+ ssrc + "\r\n");
-					content.append("f=\r\n");
-
-					try {
-						responseSdpAck(evt, content.toString(), platform);
-					} catch (SipException e) {
-						e.printStackTrace();
-					} catch (InvalidArgumentException e) {
-						e.printStackTrace();
-					} catch (ParseException e) {
-						e.printStackTrace();
+							});
+						}
+					}else {
+						SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
+								gbStream.getApp(), gbStream.getStream(), channelId,
+								mediaTransmissionTCP);
+
+
+						if (sendRtpItem == null) {
+							logger.warn("服务器端口资源不足");
+							responseAck(evt, Response.BUSY_HERE);
+							return;
+						}
+						if (tcpActive != null) {
+							sendRtpItem.setTcpActive(tcpActive);
+						}
+						sendRtpItem.setPlayType(InviteStreamType.PUSH);
+						// 写入redis, 超时时回复
+						sendRtpItem.setStatus(1);
+						sendRtpItem.setCallId(callIdHeader.getCallId());
+						byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog());
+						sendRtpItem.setDialog(dialogByteArray);
+						byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction());
+						sendRtpItem.setTransaction(transactionByteArray);
+						redisCatchStorage.updateSendRTPSever(sendRtpItem);
+						sendStreamAck(mediaServerItem, sendRtpItem, platform, evt);
 					}
+
+
 				}
 
 			}
@@ -451,6 +509,39 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 		}
 	}
 
+	public void sendStreamAck(MediaServerItem mediaServerItem, SendRtpItem sendRtpItem, ParentPlatform platform, RequestEvent evt){
+
+		StringBuffer content = new StringBuffer(200);
+		content.append("v=0\r\n");
+		content.append("o="+ sendRtpItem.getChannelId() +" 0 0 IN IP4 "+ mediaServerItem.getSdpIp()+"\r\n");
+		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");
+		content.append("a=sendonly\r\n");
+		content.append("a=rtpmap:96 PS/90000\r\n");
+		if (sendRtpItem.isTcp()) {
+			content.append("a=connection:new\r\n");
+			if (!sendRtpItem.isTcpActive()) {
+				content.append("a=setup:active\r\n");
+			}else {
+				content.append("a=setup:passive\r\n");
+			}
+		}
+		content.append("y="+ sendRtpItem.getSsrc() + "\r\n");
+		content.append("f=\r\n");
+
+		try {
+			responseSdpAck(evt, content.toString(), platform);
+		} catch (SipException e) {
+			e.printStackTrace();
+		} catch (InvalidArgumentException e) {
+			e.printStackTrace();
+		} catch (ParseException e) {
+			e.printStackTrace();
+		}
+	}
+
 	public void inviteFromDeviceHandle(RequestEvent evt, String requesterId) throws InvalidArgumentException, ParseException, SipException, SdpException {
 
 		// 非上级平台请求,查询是否设备请求(通常为接收语音广播的设备)

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

@@ -1,10 +1,10 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 
+import com.alibaba.fastjson.JSONObject;
 import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.conf.SipConfig;
-import com.genersoft.iot.vmp.conf.UserSetup;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.*;
-import com.genersoft.iot.vmp.gb28181.event.DeviceOffLineDetector;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
@@ -12,12 +12,12 @@ 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;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
-import com.genersoft.iot.vmp.utils.GpsUtil;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.redis.RedisUtil;
 import org.dom4j.DocumentException;
 import org.dom4j.Element;
@@ -46,10 +46,10 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
     private final static Logger logger = LoggerFactory.getLogger(NotifyRequestProcessor.class);
 
 	@Autowired
-	private UserSetup userSetup;
+	private UserSetting userSetting;
 
 	@Autowired
-	private IVideoManagerStorager storager;
+	private IVideoManagerStorage storager;
 
 	@Autowired
 	private EventPublisher eventPublisher;
@@ -63,10 +63,6 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 	@Autowired
 	private EventPublisher publisher;
 
-	@Autowired
-	private DeviceOffLineDetector offLineDetector;
-
-
 	private String method = "NOTIFY";
 
 	@Autowired
@@ -109,11 +105,15 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 	 */
 	private void processNotifyMobilePosition(RequestEvent evt) {
 		try {
+			FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME);
+			String deviceId = SipUtils.getUserIdFromFromHeader(fromHeader);
+
 			// 回复 200 OK
 			Element rootElement = getRootElement(evt);
+
 			MobilePosition mobilePosition = new MobilePosition();
 			Element deviceIdElement = rootElement.element("DeviceID");
-			String deviceId = deviceIdElement.getTextTrim().toString();
+			String channelId = deviceIdElement.getTextTrim().toString();
 			Device device = redisCatchStorage.getDevice(deviceId);
 			if (device != null) {
 				if (!StringUtils.isEmpty(device.getName())) {
@@ -121,7 +121,9 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 				}
 			}
 			mobilePosition.setDeviceId(XmlUtil.getText(rootElement, "DeviceID"));
-			mobilePosition.setTime(XmlUtil.getText(rootElement, "Time"));
+			mobilePosition.setChannelId(channelId);
+			String time = XmlUtil.getText(rootElement, "Time");
+			mobilePosition.setTime(time);
 			mobilePosition.setLongitude(Double.parseDouble(XmlUtil.getText(rootElement, "Longitude")));
 			mobilePosition.setLatitude(Double.parseDouble(XmlUtil.getText(rootElement, "Latitude")));
 			if (NumericUtil.isDouble(XmlUtil.getText(rootElement, "Speed"))) {
@@ -139,17 +141,31 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 			} else {
 				mobilePosition.setAltitude(0.0);
 			}
+			logger.info("[收到 移动位置订阅]:{}/{}->{}.{}", mobilePosition.getDeviceId(), mobilePosition.getChannelId(),
+					mobilePosition.getLongitude(), mobilePosition.getLatitude());
 			mobilePosition.setReportSource("Mobile Position");
-			BaiduPoint bp = new BaiduPoint();
-			bp = GpsUtil.Wgs84ToBd09(String.valueOf(mobilePosition.getLongitude()), String.valueOf(mobilePosition.getLatitude()));
-			logger.info("百度坐标:" + bp.getBdLng() + ", " + bp.getBdLat());
-			mobilePosition.setGeodeticSystem("BD-09");
-			mobilePosition.setCnLng(bp.getBdLng());
-			mobilePosition.setCnLat(bp.getBdLat());
-			if (!userSetup.getSavePositionHistory()) {
+			// 默认来源坐标系为WGS-84处理
+			Double[] gcj02Point = Coordtransform.WGS84ToGCJ02(mobilePosition.getLongitude(), mobilePosition.getLatitude());
+			logger.info("GCJ02坐标:" + gcj02Point[0] + ", " + gcj02Point[1]);
+			mobilePosition.setGeodeticSystem("GCJ-02");
+			mobilePosition.setCnLng(gcj02Point[0] + "");
+			mobilePosition.setCnLat(gcj02Point[1] + "");
+			if (!userSetting.getSavePositionHistory()) {
 				storager.clearMobilePositionsByDeviceId(deviceId);
 			}
 			storager.insertMobilePosition(mobilePosition);
+			storager.updateChannelPotion(deviceId, channelId, mobilePosition.getLongitude(), mobilePosition.getLatitude() );
+			// 发送redis消息。 通知位置信息的变化
+			JSONObject jsonObject = new JSONObject();
+			jsonObject.put("time", time);
+			jsonObject.put("serial", deviceId);
+			jsonObject.put("code", channelId);
+			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);
 			responseAck(evt, Response.OK);
 		} catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
 			e.printStackTrace();
@@ -195,7 +211,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 			} else {
 				deviceAlarm.setLatitude(0.00);
 			}
-
+			logger.info("[收到Notify-Alarm]:{}/{}", device.getDeviceId(), deviceAlarm.getChannelId());
 			if (deviceAlarm.getAlarmMethod().equals("4")) {
 				MobilePosition mobilePosition = new MobilePosition();
 				mobilePosition.setDeviceId(deviceAlarm.getDeviceId());
@@ -203,13 +219,13 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 				mobilePosition.setLongitude(deviceAlarm.getLongitude());
 				mobilePosition.setLatitude(deviceAlarm.getLatitude());
 				mobilePosition.setReportSource("GPS Alarm");
-				BaiduPoint bp = new BaiduPoint();
-				bp = GpsUtil.Wgs84ToBd09(String.valueOf(mobilePosition.getLongitude()), String.valueOf(mobilePosition.getLatitude()));
-				logger.info("百度坐标:" + bp.getBdLng() + ", " + bp.getBdLat());
-				mobilePosition.setGeodeticSystem("BD-09");
-				mobilePosition.setCnLng(bp.getBdLng());
-				mobilePosition.setCnLat(bp.getBdLat());
-				if (!userSetup.getSavePositionHistory()) {
+				// 默认来源坐标系为WGS-84处理
+				Double[] gcj02Point = Coordtransform.WGS84ToGCJ02(mobilePosition.getLongitude(), mobilePosition.getLatitude());
+				logger.info("GCJ02坐标:" + gcj02Point[0] + ", " + gcj02Point[1]);
+				mobilePosition.setGeodeticSystem("GCJ-02");
+				mobilePosition.setCnLng(gcj02Point[0] + "");
+				mobilePosition.setCnLat(gcj02Point[1] + "");
+				if (!userSetting.getSavePositionHistory()) {
 					storager.clearMobilePositionsByDeviceId(deviceId);
 				}
 				storager.insertMobilePosition(mobilePosition);
@@ -218,7 +234,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 
 			// 回复200 OK
 			responseAck(evt, Response.OK);
-			if (offLineDetector.isOnline(deviceId)) {
+			if (redisCatchStorage.deviceIsOnline(deviceId)) {
 				publisher.deviceAlarmEventPublish(deviceAlarm);
 			}
 		} catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
@@ -261,7 +277,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 					Element eventElement = itemDevice.element("Event");
 					DeviceChannel channel = XmlUtil.channelContentHander(itemDevice);
 					channel.setDeviceId(device.getDeviceId());
-					logger.debug("收到来自设备【{}】的通道: {}【{}】", device.getDeviceId(), channel.getName(), channel.getChannelId());
+					logger.info("[收到 目录订阅]:{}/{}", device.getDeviceId(), channel.getChannelId());
 					switch (eventElement.getText().toUpperCase()) {
 						case CatalogEvent.ON: // 上线
 							logger.info("收到来自设备【{}】的通道【{}】上线通知", device.getDeviceId(), channel.getChannelId());
@@ -309,7 +325,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 
 				}
 
-				if (offLineDetector.isOnline(deviceId)) {
+				if (!redisCatchStorage.deviceIsOnline(deviceId)) {
 					publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_MESSAGE);
 				}
 			}
@@ -321,7 +337,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 	public void setCmder(SIPCommander cmder) {
 	}
 
-	public void setStorager(IVideoManagerStorager storager) {
+	public void setStorager(IVideoManagerStorage storager) {
 		this.storager = storager;
 	}
 
@@ -335,10 +351,6 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 	public void setDeferredResultHolder(DeferredResultHolder deferredResultHolder) {
 	}
 
-	public void setOffLineDetector(DeviceOffLineDetector offLineDetector) {
-		this.offLineDetector = offLineDetector;
-	}
-
 	public IRedisCatchStorage getRedisCatchStorage() {
 		return redisCatchStorage;
 	}

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

@@ -3,7 +3,6 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper;
-import com.genersoft.iot.vmp.gb28181.auth.RegisterLogicHandler;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.WvpSipDate;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
@@ -11,7 +10,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
 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.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import gov.nist.javax.sip.RequestEventExt;
 import gov.nist.javax.sip.address.AddressImpl;
 import gov.nist.javax.sip.address.SipUri;
@@ -42,166 +41,165 @@ import java.util.Locale;
 @Component
 public class RegisterRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
 
-	private Logger logger = LoggerFactory.getLogger(RegisterRequestProcessor.class);
-
-	public String method = "REGISTER";
-
-	@Autowired
-	private SipConfig sipConfig;
-
-	@Autowired
-	private RegisterLogicHandler handler;
-
-	@Autowired
-	private IRedisCatchStorage redisCatchStorage;
-
-	@Autowired
-	private IVideoManagerStorager storager;
-
-	@Autowired
-	private EventPublisher publisher;
-
-	@Autowired
-	private SIPProcessorObserver sipProcessorObserver;
-
-	@Override
-	public void afterPropertiesSet() throws Exception {
-		// 添加消息处理的订阅
-		sipProcessorObserver.addRequestProcessor(method, this);
-	}
-
-	/**
-	 * 收到注册请求 处理
- 	 * @param evt
-	 */
-	@Override
-	public void process(RequestEvent evt) {
-		try {
-			RequestEventExt evtExt = (RequestEventExt)evt;
-			String requestAddress = evtExt.getRemoteIpAddress() + ":" + evtExt.getRemotePort();
-			logger.info("[{}] 收到注册请求,开始处理", requestAddress);
-			Request request = evt.getRequest();
-			ExpiresHeader expiresHeader = (ExpiresHeader) request.getHeader(Expires.NAME);
-			Response response = null; 
-			boolean passwordCorrect = false;
-			// 注册标志  0:未携带授权头或者密码错误  1:注册成功   2:注销成功
-			int registerFlag = 0;
-			FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME);
-			AddressImpl address = (AddressImpl) fromHeader.getAddress();
-			SipUri uri = (SipUri) address.getURI();
-			String deviceId = uri.getUser();
-			Device deviceInRedis = redisCatchStorage.getDevice(deviceId);
-			Device device = storager.queryVideoDevice(deviceId);
-			if (deviceInRedis != null && device == null) {
-				// redis 存在脏数据
-				redisCatchStorage.clearCatchByDeviceId(deviceId);
-			}
-			AuthorizationHeader authorhead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
-			// 校验密码是否正确
-			if (authorhead != null) {
-				passwordCorrect = new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request,
-						sipConfig.getPassword());
-			}
-			if (StringUtils.isEmpty(sipConfig.getPassword())){
-				passwordCorrect = true;
-			}
-
-			// 未携带授权头或者密码错误 回复401
-			if (authorhead == null ) {
-
-				logger.info("[{}] 未携带授权头 回复401", requestAddress);
-				response = getMessageFactory().createResponse(Response.UNAUTHORIZED, request);
-				new DigestServerAuthenticationHelper().generateChallenge(getHeaderFactory(), response, sipConfig.getDomain());
-			}else {
-				if (!passwordCorrect){
-					// 注册失败
-					response = getMessageFactory().createResponse(Response.FORBIDDEN, request);
-					response.setReasonPhrase("wrong password");
-					logger.info("[{}] 密码/SIP服务器ID错误, 回复403", requestAddress);
-				}else {
-					// 携带授权头并且密码正确
-					response = getMessageFactory().createResponse(Response.OK, request);
-					// 添加date头
-					SIPDateHeader dateHeader = new SIPDateHeader();
-					// 使用自己修改的
-					WvpSipDate wvpSipDate = new WvpSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis());
-					dateHeader.setDate(wvpSipDate);
-					response.addHeader(dateHeader);
-
-
-					if (expiresHeader == null) {
-						response = getMessageFactory().createResponse(Response.BAD_REQUEST, request);
-						ServerTransaction serverTransaction = getServerTransaction(evt);
-						serverTransaction.sendResponse(response);
-						if (serverTransaction.getDialog() != null) serverTransaction.getDialog().delete();
-						return;
-					}
-					// 添加Contact头
-					response.addHeader(request.getHeader(ContactHeader.NAME));
-					// 添加Expires头
-					response.addHeader(request.getExpires());
-
-					// 获取到通信地址等信息
-					ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
-					String received = viaHeader.getReceived();
-					int rPort = viaHeader.getRPort();
-					// 解析本地地址替代
-					if (StringUtils.isEmpty(received) || rPort == -1) {
-						received = viaHeader.getHost();
-						rPort = viaHeader.getPort();
-					}
-					//
-
-					if (device == null) {
-						device = new Device();
-						device.setStreamMode("UDP");
-						device.setCharset("gb2312");
-						device.setDeviceId(deviceId);
-						device.setFirsRegister(true);
-					}else {
-						if (device.getOnline() == 0) {
-							device.setFirsRegister(true);
-						}
-					}
-					device.setIp(received);
-					device.setPort(rPort);
-					device.setHostAddress(received.concat(":").concat(String.valueOf(rPort)));
-					// 注销成功
-					if (expiresHeader.getExpires() == 0) {
-						registerFlag = 2;
-					}
-					// 注册成功
-					else {
-						device.setExpires(expiresHeader.getExpires());
-						registerFlag = 1;
-						// 判断TCP还是UDP
-						boolean isTcp = false;
-						ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
-						String transport = reqViaHeader.getTransport();
-						if (transport.equals("TCP")) {
-							isTcp = true;
-						}
-						device.setTransport(isTcp ? "TCP" : "UDP");
-					}
-				}
-			}
-
-			ServerTransaction serverTransaction = getServerTransaction(evt);
-			serverTransaction.sendResponse(response);
-			if (serverTransaction.getDialog() != null) serverTransaction.getDialog().delete();
-			// 注册成功
-			// 保存到redis
-			if (registerFlag == 1 ) {
-				logger.info("[{}] 注册成功! deviceId:" + device.getDeviceId(), requestAddress);
-				publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_REGISTER, expiresHeader.getExpires());
-			} else if (registerFlag == 2) {
-				logger.info("[{}] 注销成功! deviceId:" + device.getDeviceId(), requestAddress);
-				publisher.outlineEventPublish(device.getDeviceId(), VideoManagerConstants.EVENT_OUTLINE_UNREGISTER);
-			}
-		} catch (SipException | InvalidArgumentException | NoSuchAlgorithmException | ParseException e) {
-			e.printStackTrace();
-		}
-		
-	}
+    private final Logger logger = LoggerFactory.getLogger(RegisterRequestProcessor.class);
+
+    public String method = "REGISTER";
+
+    @Autowired
+    private SipConfig sipConfig;
+
+    @Autowired
+    private IRedisCatchStorage redisCatchStorage;
+
+    @Autowired
+    private IVideoManagerStorage storager;
+
+    @Autowired
+    private EventPublisher publisher;
+
+    @Autowired
+    private SIPProcessorObserver sipProcessorObserver;
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        // 添加消息处理的订阅
+        sipProcessorObserver.addRequestProcessor(method, this);
+    }
+
+    /**
+     * 收到注册请求 处理
+     *
+     * @param evt
+     */
+    @Override
+    public void process(RequestEvent evt) {
+        try {
+            RequestEventExt evtExt = (RequestEventExt) evt;
+            String requestAddress = evtExt.getRemoteIpAddress() + ":" + evtExt.getRemotePort();
+            logger.info("[{}] 收到注册请求,开始处理", requestAddress);
+            Request request = evt.getRequest();
+            ExpiresHeader expiresHeader = (ExpiresHeader) request.getHeader(Expires.NAME);
+            Response response = null;
+            boolean passwordCorrect = false;
+            // 注册标志  0:未携带授权头或者密码错误  1:注册成功   2:注销成功
+            int registerFlag = 0;
+            FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME);
+            AddressImpl address = (AddressImpl) fromHeader.getAddress();
+            SipUri uri = (SipUri) address.getURI();
+            String deviceId = uri.getUser();
+
+            AuthorizationHeader authHead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
+            if (authHead == null) {
+                logger.info("[{}] 未携带授权头 回复401", requestAddress);
+                response = getMessageFactory().createResponse(Response.UNAUTHORIZED, request);
+                new DigestServerAuthenticationHelper().generateChallenge(getHeaderFactory(), response, sipConfig.getDomain());
+                sendResponse(evt, response);
+                return;
+            }
+
+            // 校验密码是否正确
+            passwordCorrect = StringUtils.isEmpty(sipConfig.getPassword()) ||
+                    new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request, sipConfig.getPassword());
+            // 未携带授权头或者密码错误 回复401
+
+            if (!passwordCorrect) {
+                // 注册失败
+                response = getMessageFactory().createResponse(Response.FORBIDDEN, request);
+                response.setReasonPhrase("wrong password");
+                logger.info("[{}] 密码/SIP服务器ID错误, 回复403", requestAddress);
+                sendResponse(evt, response);
+                return;
+            }
+
+            Device deviceInRedis = redisCatchStorage.getDevice(deviceId);
+            Device device = storager.queryVideoDevice(deviceId);
+            if (deviceInRedis != null && device == null) {
+                // redis 存在脏数据
+                redisCatchStorage.clearCatchByDeviceId(deviceId);
+            }
+            // 携带授权头并且密码正确
+            response = getMessageFactory().createResponse(Response.OK, request);
+            // 添加date头
+            SIPDateHeader dateHeader = new SIPDateHeader();
+            // 使用自己修改的
+            WvpSipDate wvpSipDate = new WvpSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis());
+            dateHeader.setDate(wvpSipDate);
+            response.addHeader(dateHeader);
+
+            if (expiresHeader == null) {
+                response = getMessageFactory().createResponse(Response.BAD_REQUEST, request);
+                ServerTransaction serverTransaction = getServerTransaction(evt);
+                serverTransaction.sendResponse(response);
+                if (serverTransaction.getDialog() != null) {
+                    serverTransaction.getDialog().delete();
+                }
+                return;
+            }
+            // 添加Contact头
+            response.addHeader(request.getHeader(ContactHeader.NAME));
+            // 添加Expires头
+            response.addHeader(request.getExpires());
+
+            // 获取到通信地址等信息
+            ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
+            String received = viaHeader.getReceived();
+            int rPort = viaHeader.getRPort();
+            // 解析本地地址替代
+            if (StringUtils.isEmpty(received) || rPort == -1) {
+                received = viaHeader.getHost();
+                rPort = viaHeader.getPort();
+            }
+            if (device == null) {
+                device = new Device();
+                device.setStreamMode("UDP");
+                device.setCharset("GB2312");
+                device.setDeviceId(deviceId);
+                device.setFirsRegister(true);
+            } else {
+                device.setFirsRegister(device.getOnline() == 0);
+            }
+            device.setIp(received);
+            device.setPort(rPort);
+            device.setHostAddress(received.concat(":").concat(String.valueOf(rPort)));
+            if (expiresHeader.getExpires() == 0) {
+                // 注销成功
+                registerFlag = 2;
+            } else {
+                // 注册成功
+                device.setExpires(expiresHeader.getExpires());
+                registerFlag = 1;
+                // 判断TCP还是UDP
+                ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
+                String transport = reqViaHeader.getTransport();
+                device.setTransport("TCP".equals(transport) ? "TCP" : "UDP");
+            }
+
+            sendResponse(evt, response);
+            // 注册成功
+            // 保存到redis
+            if (registerFlag == 1) {
+                logger.info("[{}] 注册成功! deviceId:" + deviceId, requestAddress);
+                publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_REGISTER, expiresHeader.getExpires());
+            } else if (registerFlag == 2) {
+                logger.info("[{}] 注销成功! deviceId:" + deviceId, requestAddress);
+                publisher.outlineEventPublish(deviceId, VideoManagerConstants.EVENT_OUTLINE_UNREGISTER);
+            }
+        } 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();
+        }
+    }
 
 }

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

@@ -2,12 +2,12 @@ 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.UserSetup;
+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.GPSSubscribeTask;
+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.event.request.ISIPRequestProcessor;
@@ -15,8 +15,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorP
 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.IVideoManagerStorager;
-import com.genersoft.iot.vmp.utils.SerializeUtils;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import gov.nist.javax.sip.SipProviderImpl;
 import org.dom4j.DocumentException;
 import org.dom4j.Element;
@@ -30,31 +29,25 @@ import org.springframework.stereotype.Component;
 
 import javax.sip.*;
 import javax.sip.header.ExpiresHeader;
-import javax.sip.header.ToHeader;
 import javax.sip.message.Request;
 import javax.sip.message.Response;
 import java.text.ParseException;
 
 /**
  * SIP命令类型: SUBSCRIBE请求
+ * @author lin
  */
 @Component
 public class SubscribeRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
 
-	private Logger logger = LoggerFactory.getLogger(SubscribeRequestProcessor.class);
-	private String method = "SUBSCRIBE";
+	private final Logger logger = LoggerFactory.getLogger(SubscribeRequestProcessor.class);
+	private final String method = "SUBSCRIBE";
 
 	@Autowired
 	private SIPProcessorObserver sipProcessorObserver;
 
 	@Autowired
-	private IRedisCatchStorage redisCatchStorage;
-
-	@Autowired
-	private ISIPCommanderForPlatform sipCommanderForPlatform;
-
-	@Autowired
-	private IVideoManagerStorager storager;
+	private IVideoManagerStorage storager;
 
 	@Lazy
 	@Autowired
@@ -70,8 +63,7 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 	private DynamicTask dynamicTask;
 
 	@Autowired
-	private UserSetup userSetup;
-
+	private UserSetting userSetting;
 
 	@Autowired
 	private SubscribeHolder subscribeHolder;
@@ -85,7 +77,7 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 	/**   
 	 * 处理SUBSCRIBE请求  
 	 * 
-	 * @param evt
+	 * @param evt 事件
 	 */
 	@Override
 	public void process(RequestEvent evt) {
@@ -104,13 +96,12 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 			} else {
 				logger.info("接收到消息:" + cmd);
 
-				Response response = null;
-				response = getMessageFactory().createResponse(200, request);
+				Response response = getMessageFactory().createResponse(200, request);
 				if (response != null) {
 					ExpiresHeader expireHeader = getHeaderFactory().createExpiresHeader(30);
 					response.setExpires(expireHeader);
 				}
-				logger.info("response : " + response.toString());
+				logger.info("response : " + response);
 				ServerTransaction transaction = getServerTransaction(evt);
 				if (transaction != null) {
 					transaction.sendResponse(response);
@@ -120,13 +111,7 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 					logger.info("processRequest serverTransactionId is null.");
 				}
 			}
-		} catch (ParseException e) {
-			e.printStackTrace();
-		} catch (SipException e) {
-			e.printStackTrace();
-		} catch (InvalidArgumentException e) {
-			e.printStackTrace();
-		} catch (DocumentException e) {
+		} catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
 			e.printStackTrace();
 		}
 
@@ -135,42 +120,53 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 	/**
 	 * 处理移动位置订阅消息
 	 */
-	private void processNotifyMobilePosition(RequestEvent evt, Element rootElement) {
+	private void processNotifyMobilePosition(RequestEvent evt, Element rootElement) throws SipException {
 		String platformId = SipUtils.getUserIdFromFromHeader(evt.getRequest());
-		String deviceID = XmlUtil.getText(rootElement, "DeviceID");
+		String deviceId = XmlUtil.getText(rootElement, "DeviceID");
+		ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);
 		SubscribeInfo subscribeInfo = new SubscribeInfo(evt, platformId);
+		if (platform == null) {
+			return;
+		}
+		if (evt.getServerTransaction() == null) {
+			ServerTransaction serverTransaction = "TCP".equals(platform.getTransport()) ? tcpSipProvider.getNewServerTransaction(evt.getRequest())
+					: udpSipProvider.getNewServerTransaction(evt.getRequest());
+			subscribeInfo.setTransaction(serverTransaction);
+			Dialog dialog = serverTransaction.getDialog();
+			dialog.terminateOnBye(false);
+			subscribeInfo.setDialog(dialog);
+		}
 		String sn = XmlUtil.getText(rootElement, "SN");
-		String key = VideoManagerConstants.SIP_SUBSCRIBE_PREFIX + userSetup.getServerId() +  "_MobilePosition_" + platformId;
-		logger.info("接收到{}的MobilePosition订阅", platformId);
+		logger.info("[回复 移动位置订阅]: {}", platformId);
 		StringBuilder resultXml = new StringBuilder(200);
 		resultXml.append("<?xml version=\"1.0\" ?>\r\n")
 				.append("<Response>\r\n")
 				.append("<CmdType>MobilePosition</CmdType>\r\n")
-				.append("<SN>" + sn + "</SN>\r\n")
-				.append("<DeviceID>" + deviceID + "</DeviceID>\r\n")
+				.append("<SN>").append(sn).append("</SN>\r\n")
+				.append("<DeviceID>").append(deviceId).append("</DeviceID>\r\n")
 				.append("<Result>OK</Result>\r\n")
 				.append("</Response>\r\n");
 
 		if (subscribeInfo.getExpires() > 0) {
-			if (subscribeHolder.getMobilePositionSubscribe(platformId) != null) {
-				dynamicTask.stop(key);
+			// GPS上报时间间隔
+			String interval = XmlUtil.getText(rootElement, "Interval");
+			if (interval == null) {
+				subscribeInfo.setGpsInterval(5);
+			}else {
+				subscribeInfo.setGpsInterval(Integer.parseInt(interval));
 			}
-			String interval = XmlUtil.getText(rootElement, "Interval"); // GPS上报时间间隔
-			dynamicTask.startCron(key, new GPSSubscribeTask(redisCatchStorage, sipCommanderForPlatform, storager,  platformId, sn, key, subscribeHolder), Integer.parseInt(interval));
+
+			subscribeInfo.setSn(sn);
 			subscribeHolder.putMobilePositionSubscribe(platformId, subscribeInfo);
+
 		}else if (subscribeInfo.getExpires() == 0) {
-			dynamicTask.stop(key);
 			subscribeHolder.removeMobilePositionSubscribe(platformId);
 		}
 
 		try {
 			ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(platformId);
 			responseXmlAck(evt, resultXml.toString(), parentPlatform);
-		} catch (SipException e) {
-			e.printStackTrace();
-		} catch (InvalidArgumentException e) {
-			e.printStackTrace();
-		} catch (ParseException e) {
+		} catch (SipException | InvalidArgumentException | ParseException e) {
 			e.printStackTrace();
 		}
 	}
@@ -181,11 +177,14 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 
 	private void processNotifyCatalogList(RequestEvent evt, Element rootElement) throws SipException {
 		String platformId = SipUtils.getUserIdFromFromHeader(evt.getRequest());
-		String deviceID = XmlUtil.getText(rootElement, "DeviceID");
+		String deviceId = XmlUtil.getText(rootElement, "DeviceID");
 		ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);
+		if (platform == null){
+			return;
+		}
 		SubscribeInfo subscribeInfo = new SubscribeInfo(evt, platformId);
 		if (evt.getServerTransaction() == null) {
-			ServerTransaction serverTransaction = platform.getTransport().equals("TCP") ? tcpSipProvider.getNewServerTransaction(evt.getRequest())
+			ServerTransaction serverTransaction = "TCP".equals(platform.getTransport()) ? tcpSipProvider.getNewServerTransaction(evt.getRequest())
 					: udpSipProvider.getNewServerTransaction(evt.getRequest());
 			subscribeInfo.setTransaction(serverTransaction);
 			Dialog dialog = serverTransaction.getDialog();
@@ -193,14 +192,13 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 			subscribeInfo.setDialog(dialog);
 		}
 		String sn = XmlUtil.getText(rootElement, "SN");
-		String key = VideoManagerConstants.SIP_SUBSCRIBE_PREFIX + userSetup.getServerId() +  "_Catalog_" + platformId;
-		logger.info("接收到{}的Catalog订阅", platformId);
+		logger.info("[回复 目录订阅]: {}/{}", platformId, deviceId);
 		StringBuilder resultXml = new StringBuilder(200);
 		resultXml.append("<?xml version=\"1.0\" ?>\r\n")
 				.append("<Response>\r\n")
 				.append("<CmdType>Catalog</CmdType>\r\n")
-				.append("<SN>" + sn + "</SN>\r\n")
-				.append("<DeviceID>" + deviceID + "</DeviceID>\r\n")
+				.append("<SN>").append(sn).append("</SN>\r\n")
+				.append("<DeviceID>").append(deviceId).append("</DeviceID>\r\n")
 				.append("<Result>OK</Result>\r\n")
 				.append("</Response>\r\n");
 
@@ -213,11 +211,7 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 		try {
 			ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(platformId);
 			responseXmlAck(evt, resultXml.toString(), parentPlatform);
-		} catch (SipException e) {
-			e.printStackTrace();
-		} catch (InvalidArgumentException e) {
-			e.printStackTrace();
-		} catch (ParseException e) {
+		} catch (SipException | InvalidArgumentException | ParseException e) {
 			e.printStackTrace();
 		}
 	}

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

@@ -3,13 +3,16 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.DeviceNotFoundEvent;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
+import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
+import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
 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.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+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,6 +24,7 @@ import org.springframework.stereotype.Component;
 import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
 import javax.sip.SipException;
+import javax.sip.address.SipURI;
 import javax.sip.header.CSeqHeader;
 import javax.sip.header.CallIdHeader;
 import javax.sip.message.Response;
@@ -41,7 +45,7 @@ public class MessageRequestProcessor extends SIPRequestProcessorParent implement
     private SIPProcessorObserver sipProcessorObserver;
 
     @Autowired
-    private IVideoManagerStorager storage;
+    private IVideoManagerStorage storage;
 
     @Autowired
     private SipSubscribe sipSubscribe;
@@ -49,6 +53,9 @@ public class MessageRequestProcessor extends SIPRequestProcessorParent implement
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
 
+    @Autowired
+    private VideoStreamSessionManager sessionManager;
+
     @Override
     public void afterPropertiesSet() throws Exception {
         // 添加消息处理的订阅
@@ -64,6 +71,11 @@ public class MessageRequestProcessor extends SIPRequestProcessorParent implement
         logger.debug("接收到消息:" + evt.getRequest());
         String deviceId = SipUtils.getUserIdFromFromHeader(evt.getRequest());
         CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
+        // 先从会话内查找
+        SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, null, callIdHeader.getCallId());
+        if (ssrcTransaction != null) { // 兼容海康 媒体通知 消息from字段不是设备ID的问题
+            deviceId = ssrcTransaction.getDeviceId();
+        }
         // 查询设备是否存在
         CSeqHeader cseqHeader = (CSeqHeader) evt.getRequest().getHeader(CSeqHeader.NAME);
         String method = cseqHeader.getMethod();
@@ -71,6 +83,17 @@ public class MessageRequestProcessor extends SIPRequestProcessorParent implement
         // 查询上级平台是否存在
         ParentPlatform parentPlatform = storage.queryParentPlatByServerGBId(deviceId);
         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)) {
+                    parentPlatform = null;
+                }else {
+                    device = null;
+                }
+            }
             if (device == null && parentPlatform == null) {
                 // 不存在则回复404
                 responseAck(evt, Response.NOT_FOUND, "device "+ deviceId +" not found");

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

@@ -8,8 +8,7 @@ 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.gb28181.transmit.event.request.impl.message.query.QueryMessageHandler;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.SpringBeanFactory;
 import gov.nist.javax.sip.SipStackImpl;
 import org.dom4j.Element;
@@ -20,13 +19,12 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
 
-import javax.sip.ListeningPoint;
-import javax.sip.ObjectInUseException;
-import javax.sip.RequestEvent;
-import javax.sip.SipProvider;
+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 static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
@@ -41,7 +39,7 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent
     private ControlMessageHandler controlMessageHandler;
 
     @Autowired
-    private IVideoManagerStorager storager;
+    private IVideoManagerStorage storager;
 
     @Autowired
     private SIPCommander cmder;
@@ -106,7 +104,41 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent
         if (!StringUtils.isEmpty(getText(rootElement,"PTZCmd")) && !parentPlatform.getServerGBId().equals(targetGBId)) {
             String cmdString = getText(rootElement,"PTZCmd");
             Device deviceForPlatform = storager.queryVideoDeviceByPlatformIdAndChannelId(parentPlatform.getServerGBId(), channelId);
-            cmder.fronEndCmd(deviceForPlatform, channelId, cmdString);
+            if (deviceForPlatform == null) {
+                try {
+                    responseAck(evt, Response.NOT_FOUND);
+                    return;
+                } catch (SipException e) {
+                    e.printStackTrace();
+                } catch (InvalidArgumentException e) {
+                    e.printStackTrace();
+                } catch (ParseException e) {
+                    e.printStackTrace();
+                }
+            }
+            cmder.fronEndCmd(deviceForPlatform, channelId, cmdString, eventResult -> {
+                // 失败的回复
+                try {
+                    responseAck(evt, eventResult.statusCode, eventResult.msg);
+                } catch (SipException e) {
+                    e.printStackTrace();
+                } catch (InvalidArgumentException e) {
+                    e.printStackTrace();
+                } catch (ParseException e) {
+                    e.printStackTrace();
+                }
+            }, eventResult -> {
+                // 成功的回复
+                try {
+                    responseAck(evt, eventResult.statusCode);
+                } catch (SipException e) {
+                    e.printStackTrace();
+                } catch (InvalidArgumentException e) {
+                    e.printStackTrace();
+                } catch (ParseException e) {
+                    e.printStackTrace();
+                }
+            });
         }
     }
 }

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

@@ -1,17 +1,17 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd;
 
 import com.genersoft.iot.vmp.conf.SipConfig;
-import com.genersoft.iot.vmp.conf.UserSetup;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.*;
-import com.genersoft.iot.vmp.gb28181.event.DeviceOffLineDetector;
 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.service.IDeviceAlarmService;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
-import com.genersoft.iot.vmp.utils.GpsUtil;
+import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -20,7 +20,12 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 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 static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.*;
 
@@ -37,19 +42,19 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
     private EventPublisher publisher;
 
     @Autowired
-    private UserSetup userSetup;
+    private UserSetting userSetting;
 
     @Autowired
     private SipConfig sipConfig;
 
     @Autowired
-    private IVideoManagerStorager storager;
+    private IVideoManagerStorage storager;
 
     @Autowired
-    private IDeviceAlarmService deviceAlarmService;
+    private IRedisCatchStorage redisCatchStorage;
 
     @Autowired
-    private DeviceOffLineDetector offLineDetector;
+    private IDeviceAlarmService deviceAlarmService;
 
     @Override
     public void afterPropertiesSet() throws Exception {
@@ -58,11 +63,22 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
 
     @Override
     public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
-        if (!sipConfig.isAlarm()) {
-            return;
+        logger.info("收到来自设备[{}]的报警通知", device.getDeviceId());
+        // 回复200 OK
+        try {
+            responseAck(evt, Response.OK);
+        } catch (SipException e) {
+            throw new RuntimeException(e);
+        } catch (InvalidArgumentException e) {
+            throw new RuntimeException(e);
+        } catch (ParseException e) {
+            throw new RuntimeException(e);
         }
+
         Element deviceIdElement = rootElement.element("DeviceID");
         String channelId = deviceIdElement.getText().toString();
+
+
         DeviceAlarm deviceAlarm = new DeviceAlarm();
         deviceAlarm.setDeviceId(device.getDeviceId());
         deviceAlarm.setChannelId(channelId);
@@ -93,13 +109,13 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
                 mobilePosition.setLongitude(deviceAlarm.getLongitude());
                 mobilePosition.setLatitude(deviceAlarm.getLatitude());
                 mobilePosition.setReportSource("GPS Alarm");
-                BaiduPoint bp = new BaiduPoint();
-                bp = GpsUtil.Wgs84ToBd09(String.valueOf(mobilePosition.getLongitude()), String.valueOf(mobilePosition.getLatitude()));
-                logger.info("百度坐标:" + bp.getBdLng() + ", " + bp.getBdLat());
-                mobilePosition.setGeodeticSystem("BD-09");
-                mobilePosition.setCnLng(bp.getBdLng());
-                mobilePosition.setCnLat(bp.getBdLat());
-                if (!userSetup.getSavePositionHistory()) {
+                // 默认来源坐标系为WGS-84处理
+                Double[] gcj02Point = Coordtransform.WGS84ToGCJ02(mobilePosition.getLongitude(), mobilePosition.getLatitude());
+                logger.info("GCJ02坐标:" + gcj02Point[0] + ", " + gcj02Point[1]);
+                mobilePosition.setGeodeticSystem("GCJ-02");
+                mobilePosition.setCnLng(gcj02Point[0] + "");
+                mobilePosition.setCnLat(gcj02Point[1] + "");
+                if (!userSetting.getSavePositionHistory()) {
                     storager.clearMobilePositionsByDeviceId(device.getDeviceId());
                 }
                 storager.insertMobilePosition(mobilePosition);
@@ -110,17 +126,84 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
                 deviceAlarm.setAlarmType(getText(rootElement.element("Info"), "AlarmType"));
             }
         }
+
+        if (channelId.equals(sipConfig.getId())) {
+            // 发送给平台的报警信息。 发送redis通知
+            AlarmChannelMessage alarmChannelMessage = new AlarmChannelMessage();
+            alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod()));
+            alarmChannelMessage.setAlarmDescription(deviceAlarm.getAlarmDescription());
+            alarmChannelMessage.setGbId(channelId);
+            redisCatchStorage.sendAlarmMsg(alarmChannelMessage);
+
+            return;
+        }
+
         logger.debug("存储报警信息、报警分类");
         // 存储报警信息、报警分类
-        deviceAlarmService.add(deviceAlarm);
+        if (sipConfig.isAlarm()) {
+            deviceAlarmService.add(deviceAlarm);
+        }
+
 
-        if (offLineDetector.isOnline(device.getDeviceId())) {
+        if (redisCatchStorage.deviceIsOnline(device.getDeviceId())) {
             publisher.deviceAlarmEventPublish(deviceAlarm);
         }
     }
 
     @Override
-    public void handForPlatform(RequestEvent evt, ParentPlatform parentPlatform, Element element) {
+    public void handForPlatform(RequestEvent evt, ParentPlatform parentPlatform, Element rootElement) {
+        logger.info("收到来自平台[{}]的报警通知", parentPlatform.getServerGBId());
+        // 回复200 OK
+        try {
+            responseAck(evt, Response.OK);
+        } catch (SipException e) {
+            throw new RuntimeException(e);
+        } catch (InvalidArgumentException e) {
+            throw new RuntimeException(e);
+        } catch (ParseException e) {
+            throw new RuntimeException(e);
+        }
+        Element deviceIdElement = rootElement.element("DeviceID");
+        String channelId = deviceIdElement.getText().toString();
+
+
+        DeviceAlarm deviceAlarm = new DeviceAlarm();
+        deviceAlarm.setDeviceId(parentPlatform.getServerGBId());
+        deviceAlarm.setChannelId(channelId);
+        deviceAlarm.setAlarmPriority(getText(rootElement, "AlarmPriority"));
+        deviceAlarm.setAlarmMethod(getText(rootElement, "AlarmMethod"));
+        deviceAlarm.setAlarmTime(getText(rootElement, "AlarmTime"));
+        if (getText(rootElement, "AlarmDescription") == null) {
+            deviceAlarm.setAlarmDescription("");
+        } else {
+            deviceAlarm.setAlarmDescription(getText(rootElement, "AlarmDescription"));
+        }
+        if (NumericUtil.isDouble(getText(rootElement, "Longitude"))) {
+            deviceAlarm.setLongitude(Double.parseDouble(getText(rootElement, "Longitude")));
+        } else {
+            deviceAlarm.setLongitude(0.00);
+        }
+        if (NumericUtil.isDouble(getText(rootElement, "Latitude"))) {
+            deviceAlarm.setLatitude(Double.parseDouble(getText(rootElement, "Latitude")));
+        } else {
+            deviceAlarm.setLatitude(0.00);
+        }
+
+        if (!StringUtils.isEmpty(deviceAlarm.getAlarmMethod())) {
+
+            if (deviceAlarm.getAlarmMethod().equals("5")) {
+                deviceAlarm.setAlarmType(getText(rootElement.element("Info"), "AlarmType"));
+            }
+        }
 
+        if (channelId.equals(parentPlatform.getDeviceGBId())) {
+            // 发送给平台的报警信息。 发送redis通知
+            AlarmChannelMessage alarmChannelMessage = new AlarmChannelMessage();
+            alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod()));
+            alarmChannelMessage.setAlarmDescription(deviceAlarm.getAlarmDescription());
+            alarmChannelMessage.setGbId(channelId);
+            redisCatchStorage.sendAlarmMsg(alarmChannelMessage);
+            return;
+        }
     }
 }

+ 41 - 42
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/CatalogNotifyMessageHandler.java

@@ -1,18 +1,13 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd;
 
-import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.gb28181.bean.*;
-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.notify.NotifyMessageHandler;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
-import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 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;
@@ -23,26 +18,23 @@ import javax.sip.SipException;
 import javax.sip.header.FromHeader;
 import javax.sip.message.Response;
 import java.text.ParseException;
+import java.util.ArrayList;
 import java.util.List;
 
 @Component
 public class CatalogNotifyMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
 
-    private Logger logger = LoggerFactory.getLogger(CatalogNotifyMessageHandler.class);
     private final String cmdType = "Catalog";
 
     @Autowired
     private NotifyMessageHandler notifyMessageHandler;
 
     @Autowired
-    private IVideoManagerStorager storager;
+    private IVideoManagerStorage storage;
 
     @Autowired
     private SIPCommanderFroPlatform cmderFroPlatform;
 
-    @Autowired
-    private SipConfig config;
-
     @Override
     public void afterPropertiesSet() throws Exception {
         notifyMessageHandler.addHandler(cmdType, this);
@@ -64,12 +56,13 @@ public class CatalogNotifyMessageHandler extends SIPRequestProcessorParent imple
             Element snElement = rootElement.element("SN");
             String sn = snElement.getText();
             // 准备回复通道信息
-            List<ChannelReduce> channelReduces = storager.queryChannelListInParentPlatform(parentPlatform.getServerGBId());
+            List<DeviceChannelInPlatform> deviceChannels = storage.queryChannelListInParentPlatform(parentPlatform.getServerGBId());
             // 查询关联的直播通道
-            List<GbStream> gbStreams = storager.queryGbStreamListInPlatform(parentPlatform.getServerGBId());
-            int size = channelReduces.size() + gbStreams.size();
+            List<GbStream> gbStreams = storage.queryGbStreamListInPlatform(parentPlatform.getServerGBId());
+
+            List<DeviceChannel> allChannels = new ArrayList<>();
             // 回复目录信息
-            List<PlatformCatalog> catalogs =  storager.queryCatalogInPlatform(parentPlatform.getServerGBId());
+            List<PlatformCatalog> catalogs =  storage.queryCatalogInPlatform(parentPlatform.getServerGBId());
             if (catalogs.size() > 0) {
                 for (PlatformCatalog catalog : catalogs) {
                     if (catalog.getParentId().equals(catalog.getPlatformId())) {
@@ -86,27 +79,34 @@ public class CatalogNotifyMessageHandler extends SIPRequestProcessorParent imple
                     deviceChannel.setParental(1);
                     deviceChannel.setParentId(catalog.getParentId());
                     deviceChannel.setRegisterWay(1);
-                    deviceChannel.setCivilCode(config.getDomain().substring(0, config.getDomain().length() - 2));
+                    if (catalog.getParentId() != null && catalog.getParentId().length() <= 10) {
+                        deviceChannel.setCivilCode(catalog.getParentId());
+                    }else {
+                        deviceChannel.setCivilCode(parentPlatform.getAdministrativeDivision());
+                    }
+                    deviceChannel.setCivilCode(parentPlatform.getAdministrativeDivision());
                     deviceChannel.setModel("live");
                     deviceChannel.setOwner("wvp-pro");
                     deviceChannel.setSecrecy("0");
-                    cmderFroPlatform.catalogQuery(deviceChannel, parentPlatform, sn, fromHeader.getTag(), size);
-                    // 防止发送过快
-                    Thread.sleep(100);
+                    allChannels.add(deviceChannel);
                 }
             }
             // 回复级联的通道
-            if (channelReduces.size() > 0) {
-                for (ChannelReduce channelReduce : channelReduces) {
-                    if (channelReduce.getCatalogId().equals(parentPlatform.getServerGBId())) {
-                        channelReduce.setCatalogId(parentPlatform.getDeviceGBId());
+            if (deviceChannels.size() > 0) {
+                for (DeviceChannelInPlatform channel : deviceChannels) {
+                    if (channel.getCatalogId().equals(parentPlatform.getServerGBId())) {
+                        channel.setCatalogId(parentPlatform.getDeviceGBId());
                     }
-                    DeviceChannel deviceChannel = storager.queryChannel(channelReduce.getDeviceId(), channelReduce.getChannelId());
+                    DeviceChannel deviceChannel = storage.queryChannel(channel.getDeviceId(), channel.getChannelId());
                     deviceChannel.setParental(0);
-                    deviceChannel.setParentId(channelReduce.getCatalogId());
-                    cmderFroPlatform.catalogQuery(deviceChannel, parentPlatform, sn, fromHeader.getTag(), size);
-                    // 防止发送过快
-                    Thread.sleep(100);
+                    deviceChannel.setParentId(channel.getCatalogId());
+                    if (channel.getCatalogId() != null &&  channel.getCatalogId().length() <= 10) {
+                        channel.setCivilCode(channel.getCatalogId());
+                    }else {
+                        deviceChannel.setCivilCode(parentPlatform.getAdministrativeDivision());
+                    }
+
+                    allChannels.add(deviceChannel);
                 }
             }
             // 回复直播的通道
@@ -122,30 +122,29 @@ public class CatalogNotifyMessageHandler extends SIPRequestProcessorParent imple
                     deviceChannel.setLatitude(gbStream.getLatitude());
                     deviceChannel.setDeviceId(parentPlatform.getDeviceGBId());
                     deviceChannel.setManufacture("wvp-pro");
-                    deviceChannel.setStatus(gbStream.isStatus()?1:0);
+//                    deviceChannel.setStatus(gbStream.isStatus()?1:0);
+                    deviceChannel.setStatus(1);
     				deviceChannel.setParentId(gbStream.getCatalogId());
                     deviceChannel.setRegisterWay(1);
-                    deviceChannel.setCivilCode(config.getDomain().substring(0, config.getDomain().length() - 2));
+                    if (gbStream.getCatalogId() != null && gbStream.getCatalogId().length() <= 10) {
+                        deviceChannel.setCivilCode(gbStream.getCatalogId());
+                    }else {
+                        deviceChannel.setCivilCode(parentPlatform.getAdministrativeDivision());
+                    }
                     deviceChannel.setModel("live");
                     deviceChannel.setOwner("wvp-pro");
                     deviceChannel.setParental(0);
                     deviceChannel.setSecrecy("0");
-                    cmderFroPlatform.catalogQuery(deviceChannel, parentPlatform, sn, fromHeader.getTag(), size);
-                    // 防止发送过快
-                    Thread.sleep(100);
+                    allChannels.add(deviceChannel);
                 }
             }
-            if (size == 0) {
+            if (allChannels.size() > 0) {
+                cmderFroPlatform.catalogQuery(allChannels, parentPlatform, sn, fromHeader.getTag());
+            }else {
                 // 回复无通道
-                cmderFroPlatform.catalogQuery(null, parentPlatform, sn, fromHeader.getTag(), size);
+                cmderFroPlatform.catalogQuery(null, parentPlatform, sn, fromHeader.getTag(), 0);
             }
-        } catch (SipException e) {
-            e.printStackTrace();
-        } catch (InvalidArgumentException e) {
-            e.printStackTrace();
-        } catch (ParseException e) {
-            e.printStackTrace();
-        } catch (InterruptedException e) {
+        } catch (SipException | InvalidArgumentException | ParseException e) {
             e.printStackTrace();
         }
 

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

@@ -7,9 +7,8 @@ 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.service.IDeviceService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -17,7 +16,6 @@ import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
-import springfox.documentation.service.Header;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
@@ -39,7 +37,7 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
     private EventPublisher publisher;
 
     @Autowired
-    private IVideoManagerStorager videoManagerStorager;
+    private IVideoManagerStorage videoManagerStorager;
 
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
@@ -72,7 +70,9 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
                     videoManagerStorager.updateDevice(device);
                     redisCatchStorage.updateDevice(device);
                 }
-                publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
+                if (!redisCatchStorage.deviceIsOnline(device.getDeviceId())) {
+                    publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
+                }
             }
         } catch (SipException e) {
             e.printStackTrace();

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

@@ -62,7 +62,12 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i
         if (NotifyType.equals("121")){
             logger.info("媒体播放完毕,通知关流");
             String channelId =getText(rootElement, "DeviceID");
-            redisCatchStorage.stopPlayback(device.getDeviceId(), channelId, null, callIdHeader.getCallId());
+//            redisCatchStorage.stopPlayback(device.getDeviceId(), channelId, null, callIdHeader.getCallId());
+//            redisCatchStorage.stopDownload(device.getDeviceId(), channelId, null, callIdHeader.getCallId());
+            StreamInfo streamInfo = redisCatchStorage.queryDownload(device.getDeviceId(), channelId, null, callIdHeader.getCallId());
+            // 设置进度100%
+            streamInfo.setProgress(1);
+            redisCatchStorage.startDownload(streamInfo, callIdHeader.getCallId());
             cmder.streamByeCmd(device.getDeviceId(), channelId, null, callIdHeader.getCallId());
             // TODO 如果级联播放,需要给上级发送此通知
 

+ 12 - 11
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.genersoft.iot.vmp.conf.UserSetup;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.BaiduPoint;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.MobilePosition;
@@ -8,8 +8,9 @@ import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 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.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.GpsUtil;
 import org.dom4j.DocumentException;
 import org.dom4j.Element;
@@ -38,10 +39,10 @@ public class MobilePositionNotifyMessageHandler extends SIPRequestProcessorParen
     private NotifyMessageHandler notifyMessageHandler;
 
     @Autowired
-    private UserSetup userSetup;
+    private UserSetting userSetting;
 
     @Autowired
-    private IVideoManagerStorager storager;
+    private IVideoManagerStorage storager;
 
     @Override
     public void afterPropertiesSet() throws Exception {
@@ -79,13 +80,13 @@ public class MobilePositionNotifyMessageHandler extends SIPRequestProcessorParen
                 mobilePosition.setAltitude(0.0);
             }
             mobilePosition.setReportSource("Mobile Position");
-            BaiduPoint bp = new BaiduPoint();
-            bp = GpsUtil.Wgs84ToBd09(String.valueOf(mobilePosition.getLongitude()), String.valueOf(mobilePosition.getLatitude()));
-            logger.info("百度坐标:" + bp.getBdLng() + ", " + bp.getBdLat());
-            mobilePosition.setGeodeticSystem("BD-09");
-            mobilePosition.setCnLng(bp.getBdLng());
-            mobilePosition.setCnLat(bp.getBdLat());
-            if (!userSetup.getSavePositionHistory()) {
+            // 默认来源坐标系为WGS-84处理
+            Double[] gcj02Point = Coordtransform.WGS84ToGCJ02(mobilePosition.getLongitude(), mobilePosition.getLatitude());
+            logger.info("GCJ02坐标:" + gcj02Point[0] + ", " + gcj02Point[1]);
+            mobilePosition.setGeodeticSystem("GCJ-02");
+            mobilePosition.setCnLng(gcj02Point[0] + "");
+            mobilePosition.setCnLat(gcj02Point[1] + "");
+            if (!userSetting.getSavePositionHistory()) {
                 storager.clearMobilePositionsByDeviceId(device.getDeviceId());
             }
             storager.insertMobilePosition(mobilePosition);

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

@@ -2,17 +2,13 @@ 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.GbStream;
 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.IVideoManagerStorager;
-import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -23,10 +19,8 @@ import org.springframework.stereotype.Component;
 import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
 import javax.sip.SipException;
-import javax.sip.header.FromHeader;
 import javax.sip.message.Response;
 import java.text.ParseException;
-import java.util.List;
 
 @Component
 public class AlarmQueryMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
@@ -38,7 +32,7 @@ public class AlarmQueryMessageHandler extends SIPRequestProcessorParent implemen
     private QueryMessageHandler queryMessageHandler;
 
     @Autowired
-    private IVideoManagerStorager storager;
+    private IVideoManagerStorage storager;
 
     @Autowired
     private SIPCommanderFroPlatform cmderFroPlatform;

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

@@ -8,8 +8,7 @@ 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.IVideoManagerStorager;
-import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -23,6 +22,7 @@ import javax.sip.SipException;
 import javax.sip.header.FromHeader;
 import javax.sip.message.Response;
 import java.text.ParseException;
+import java.util.ArrayList;
 import java.util.List;
 
 @Component
@@ -35,7 +35,7 @@ public class CatalogQueryMessageHandler extends SIPRequestProcessorParent implem
     private QueryMessageHandler queryMessageHandler;
 
     @Autowired
-    private IVideoManagerStorager storager;
+    private IVideoManagerStorage storager;
 
     @Autowired
     private SIPCommanderFroPlatform cmderFroPlatform;
@@ -46,6 +46,9 @@ public class CatalogQueryMessageHandler extends SIPRequestProcessorParent implem
     @Autowired
     private EventPublisher publisher;
 
+    @Autowired
+    private IVideoManagerStorage storage;
+
     @Override
     public void afterPropertiesSet() throws Exception {
         queryMessageHandler.addHandler(cmdType, this);
@@ -67,81 +70,92 @@ public class CatalogQueryMessageHandler extends SIPRequestProcessorParent implem
             Element snElement = rootElement.element("SN");
             String sn = snElement.getText();
             // 准备回复通道信息
-            List<ChannelReduce> channelReduces = storager.queryChannelListInParentPlatform(parentPlatform.getServerGBId());
+            List<DeviceChannelInPlatform> deviceChannelInPlatforms = storager.queryChannelListInParentPlatform(parentPlatform.getServerGBId());
             // 查询关联的直播通道
             List<GbStream> gbStreams = storager.queryGbStreamListInPlatform(parentPlatform.getServerGBId());
             // 回复目录信息
             List<PlatformCatalog> catalogs =  storager.queryCatalogInPlatform(parentPlatform.getServerGBId());
-            int size = catalogs.size() + channelReduces.size() + gbStreams.size();
+
+            List<DeviceChannel> allChannels = new ArrayList<>();
             if (catalogs.size() > 0) {
                 for (PlatformCatalog catalog : catalogs) {
-                    if (catalog.getParentId().equals(parentPlatform.getServerGBId())) {
+                    if (catalog.getParentId().equals(catalog.getPlatformId())) {
                         catalog.setParentId(parentPlatform.getDeviceGBId());
                     }
                     DeviceChannel deviceChannel = new DeviceChannel();
+                    // 通道的类型,0->国标通道 1->直播流通道 2->业务分组/虚拟组织/行政区划
+                    deviceChannel.setChannelType(2);
                     deviceChannel.setChannelId(catalog.getId());
                     deviceChannel.setName(catalog.getName());
-                    deviceChannel.setLongitude(0.0);
-                    deviceChannel.setLatitude(0.0);
                     deviceChannel.setDeviceId(parentPlatform.getDeviceGBId());
                     deviceChannel.setManufacture("wvp-pro");
                     deviceChannel.setStatus(1);
                     deviceChannel.setParental(1);
                     deviceChannel.setParentId(catalog.getParentId());
                     deviceChannel.setRegisterWay(1);
-                    deviceChannel.setCivilCode(config.getDomain().substring(0, config.getDomain().length() - 2));
-                    deviceChannel.setModel("live");
-                    deviceChannel.setOwner("wvp-pro");
-                    deviceChannel.setSecrecy("0");
-                    cmderFroPlatform.catalogQuery(deviceChannel, parentPlatform, sn, fromHeader.getTag(), size);
-                    // 防止发送过快
-                    Thread.sleep(100);
+                    if (catalog.getParentId() != null &&  catalog.getParentId().length() < 10) {
+                        deviceChannel.setCivilCode(catalog.getParentId());
+                    }else {
+                        deviceChannel.setCivilCode(parentPlatform.getAdministrativeDivision());
+                    }
+                    allChannels.add(deviceChannel);
                 }
             }
             // 回复级联的通道
-            if (channelReduces.size() > 0) {
-                for (ChannelReduce channelReduce : channelReduces) {
-                    if (channelReduce.getCatalogId().equals(parentPlatform.getServerGBId())) {
-                        channelReduce.setCatalogId(parentPlatform.getDeviceGBId());
+            if (deviceChannelInPlatforms.size() > 0) {
+                for (DeviceChannelInPlatform channel : deviceChannelInPlatforms) {
+                    if (channel.getCatalogId().equals(parentPlatform.getServerGBId())) {
+                        channel.setCatalogId(parentPlatform.getDeviceGBId());
                     }
-                    DeviceChannel deviceChannel = storager.queryChannel(channelReduce.getDeviceId(), channelReduce.getChannelId());
+                    DeviceChannel deviceChannel = storage.queryChannel(channel.getDeviceId(), channel.getChannelId());
+                    // 通道的类型,0->国标通道 1->直播流通道 2->业务分组/虚拟组织/行政区划
+                    deviceChannel.setChannelType(0);
                     deviceChannel.setParental(0);
-                    deviceChannel.setParentId(channelReduce.getCatalogId());
-                    cmderFroPlatform.catalogQuery(deviceChannel, parentPlatform, sn, fromHeader.getTag(), size);
-                    // 防止发送过快
-                    Thread.sleep(100);
+                    deviceChannel.setParentId(channel.getCatalogId());
+                    if (channel.getCatalogId() != null && channel.getCatalogId().length() < 10) {
+                        deviceChannel.setCivilCode(channel.getCatalogId());
+                    }else {
+                        deviceChannel.setCivilCode(parentPlatform.getAdministrativeDivision());
+                    }
+                    allChannels.add(deviceChannel);
                 }
             }
             // 回复直播的通道
             if (gbStreams.size() > 0) {
                 for (GbStream gbStream : gbStreams) {
                     if (gbStream.getCatalogId().equals(parentPlatform.getServerGBId())) {
-                        gbStream.setCatalogId(parentPlatform.getDeviceGBId());
+                        gbStream.setCatalogId(null);
                     }
                     DeviceChannel deviceChannel = new DeviceChannel();
+                    // 通道的类型,0->国标通道 1->直播流通道 2->业务分组/虚拟组织/行政区划
+                    deviceChannel.setChannelType(1);
                     deviceChannel.setChannelId(gbStream.getGbId());
                     deviceChannel.setName(gbStream.getName());
                     deviceChannel.setLongitude(gbStream.getLongitude());
                     deviceChannel.setLatitude(gbStream.getLatitude());
                     deviceChannel.setDeviceId(parentPlatform.getDeviceGBId());
                     deviceChannel.setManufacture("wvp-pro");
-                    deviceChannel.setStatus(gbStream.isStatus()?1:0);
+//                    deviceChannel.setStatus(gbStream.isStatus()?1:0);
+                    deviceChannel.setStatus(1);
                     deviceChannel.setParentId(gbStream.getCatalogId());
                     deviceChannel.setRegisterWay(1);
-                    deviceChannel.setCivilCode(config.getDomain().substring(0, config.getDomain().length() - 2));
+                    if (gbStream.getCatalogId() != null && gbStream.getCatalogId().length() < 10) {
+                        deviceChannel.setCivilCode(gbStream.getCatalogId());
+                    }else {
+                        deviceChannel.setCivilCode(parentPlatform.getAdministrativeDivision());
+                    }
                     deviceChannel.setModel("live");
                     deviceChannel.setOwner("wvp-pro");
                     deviceChannel.setParental(0);
                     deviceChannel.setSecrecy("0");
-
-                    cmderFroPlatform.catalogQuery(deviceChannel, parentPlatform, sn, fromHeader.getTag(), size);
-                    // 防止发送过快
-                    Thread.sleep(100);
+                    allChannels.add(deviceChannel);
                 }
             }
-            if (size == 0) {
+            if (allChannels.size() > 0) {
+                cmderFroPlatform.catalogQuery(allChannels, parentPlatform, sn, fromHeader.getTag());
+            }else {
                 // 回复无通道
-                cmderFroPlatform.catalogQuery(null, parentPlatform, sn, fromHeader.getTag(), size);
+                cmderFroPlatform.catalogQuery(null, parentPlatform, sn, fromHeader.getTag(), 0);
             }
         } catch (SipException e) {
             e.printStackTrace();
@@ -149,8 +163,6 @@ public class CatalogQueryMessageHandler extends SIPRequestProcessorParent implem
             e.printStackTrace();
         } catch (ParseException e) {
             e.printStackTrace();
-        } catch (InterruptedException e) {
-            e.printStackTrace();
         }
 
     }

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

@@ -8,7 +8,7 @@ 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.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -33,7 +33,7 @@ public class DeviceStatusQueryMessageHandler extends SIPRequestProcessorParent i
     private QueryMessageHandler queryMessageHandler;
 
     @Autowired
-    private IVideoManagerStorager storager;
+    private IVideoManagerStorage storager;
 
     @Autowired
     private SIPCommanderFroPlatform cmderFroPlatform;

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

@@ -4,16 +4,14 @@ import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 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.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.query.QueryMessageHandler;
 import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.storager.dao.dto.ChannelSourceInfo;
-import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -39,7 +37,7 @@ public class RecordInfoQueryMessageHandler extends SIPRequestProcessorParent imp
     private QueryMessageHandler queryMessageHandler;
 
     @Autowired
-    private IVideoManagerStorager storager;
+    private IVideoManagerStorage storager;
 
     @Autowired
     private SIPCommanderFroPlatform cmderFroPlatform;

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

@@ -1,22 +1,19 @@
 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.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.DeviceOffLineDetector;
+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.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.gb28181.utils.Coordtransform;
 import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
 import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
-import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
+import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.dom4j.DocumentException;
 import org.dom4j.Element;
 import org.slf4j.Logger;
@@ -24,6 +21,7 @@ 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.StringUtils;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
@@ -32,12 +30,9 @@ import javax.sip.message.Response;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
 
-import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
-
 @Component
 public class CatalogResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
 
@@ -50,7 +45,7 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
     private ResponseMessageHandler responseMessageHandler;
 
     @Autowired
-    private IVideoManagerStorager storager;
+    private IVideoManagerStorage storager;
 
     @Autowired
     private DeferredResultHolder deferredResultHolder;
@@ -58,15 +53,19 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
     @Autowired
     private CatalogDataCatch catalogDataCatch;
 
-    @Autowired
-    private DeviceOffLineDetector offLineDetector;
-
     @Autowired
     private SipConfig config;
 
     @Autowired
     private EventPublisher publisher;
 
+    //by brewswang
+    @Autowired
+    private UserSetting userSetting;
+
+    @Autowired
+    private IRedisCatchStorage redisCatchStorage;
+
     @Override
     public void afterPropertiesSet() throws Exception {
         responseMessageHandler.addHandler(cmdType, this);
@@ -80,23 +79,17 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
             rootElement = getRootElement(evt, device.getCharset());
             Element deviceListElement = rootElement.element("DeviceList");
             Element sumNumElement = rootElement.element("SumNum");
-            if (sumNumElement == null || deviceListElement == null) {
+            Element snElement = rootElement.element("SN");
+            if (snElement == null || sumNumElement == null || deviceListElement == null) {
                 responseAck(evt, Response.BAD_REQUEST, "xml error");
                 return;
             }
             int sumNum = Integer.parseInt(sumNumElement.getText());
+
             if (sumNum == 0) {
                 // 数据已经完整接收
                 storager.cleanChannelsForDevice(device.getDeviceId());
-                RequestMessage msg = new RequestMessage();
-                msg.setKey(key);
-                WVPResult<Object> result = new WVPResult<>();
-                result.setCode(0);
-                result.setData(device);
-                msg.setData(result);
-                result.setMsg("更新成功,共0条");
-                deferredResultHolder.invokeAllResult(msg);
-                catalogDataCatch.del(key);
+                catalogDataCatch.setChannelSyncEnd(device.getDeviceId(), null);
             }else {
                 Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
                 if (deviceListIterator != null) {
@@ -108,36 +101,31 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
                         if (channelDeviceElement == null) {
                             continue;
                         }
+                        //by brewswang
+//                        if (NumericUtil.isDouble(XmlUtil.getText(itemDevice, "Longitude"))) {//如果包含位置信息,就更新一下位置
+//                            processNotifyMobilePosition(evt, itemDevice);
+//                        }
                         DeviceChannel deviceChannel = XmlUtil.channelContentHander(itemDevice);
                         deviceChannel.setDeviceId(device.getDeviceId());
-                        logger.debug("收到来自设备【{}】的通道: {}【{}】", device.getDeviceId(), deviceChannel.getName(), deviceChannel.getChannelId());
+
                         channelList.add(deviceChannel);
                     }
-
-                    catalogDataCatch.put(key, sumNum, device, channelList);
-                    if (catalogDataCatch.get(key).size() == sumNum) {
+                    int sn = Integer.parseInt(snElement.getText());
+                    catalogDataCatch.put(device.getDeviceId(), sn, sumNum, device, channelList);
+                    logger.info("收到来自设备【{}】的通道: {}个,{}/{}", device.getDeviceId(), channelList.size(), catalogDataCatch.get(device.getDeviceId()) == null ? 0 :catalogDataCatch.get(device.getDeviceId()).size(), sumNum);
+                    if (catalogDataCatch.get(device.getDeviceId()).size() == sumNum) {
                         // 数据已经完整接收
-                        boolean resetChannelsResult = storager.resetChannels(device.getDeviceId(), catalogDataCatch.get(key));
-                        RequestMessage msg = new RequestMessage();
-                        msg.setKey(key);
-                        WVPResult<Object> result = new WVPResult<>();
-                        result.setCode(0);
-                        result.setData(device);
-                        if (resetChannelsResult || sumNum ==0) {
-                            result.setMsg("更新成功,共" + sumNum + "条,已更新" + catalogDataCatch.get(key).size() + "条");
+                        boolean resetChannelsResult = storager.resetChannels(device.getDeviceId(), catalogDataCatch.get(device.getDeviceId()));
+                        if (!resetChannelsResult) {
+                            String errorMsg = "接收成功,写入失败,共" + sumNum + "条,已接收" + catalogDataCatch.get(device.getDeviceId()).size() + "条";
+                            catalogDataCatch.setChannelSyncEnd(device.getDeviceId(), errorMsg);
                         }else {
-                            result.setMsg("接收成功,写入失败,共" + sumNum + "条,已接收" + catalogDataCatch.get(key).size() + "条");
+                            catalogDataCatch.setChannelSyncEnd(device.getDeviceId(), null);
                         }
-                        msg.setData(result);
-                        deferredResultHolder.invokeAllResult(msg);
-                        catalogDataCatch.del(key);
                     }
                 }
                 // 回复200 OK
                 responseAck(evt, Response.OK);
-                if (offLineDetector.isOnline(device.getDeviceId())) {
-                    publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_MESSAGE);
-                }
             }
         } catch (DocumentException e) {
             e.printStackTrace();
@@ -154,4 +142,93 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
     public void handForPlatform(RequestEvent evt, ParentPlatform parentPlatform, Element rootElement) {
 
     }
+
+    /**
+     * 处理设备位置的更新
+     *
+     * @param evt, itemDevice
+     */
+    private void processNotifyMobilePosition(RequestEvent evt, Element itemDevice) {
+        try {
+            // 回复 200 OK
+            Element rootElement = getRootElement(evt);
+            MobilePosition mobilePosition = new MobilePosition();
+            Element deviceIdElement = rootElement.element("DeviceID");
+            String deviceId = deviceIdElement.getTextTrim().toString();
+            Device device = redisCatchStorage.getDevice(deviceId);
+            if (device != null) {
+                if (!StringUtils.isEmpty(device.getName())) {
+                    mobilePosition.setDeviceName(device.getName());
+                }
+            }
+            mobilePosition.setDeviceId(XmlUtil.getText(rootElement, "DeviceID"));
+
+            String time = XmlUtil.getText(itemDevice, "Time");
+            if(time==null){
+                time =  XmlUtil.getText(itemDevice, "EndTime");
+            }
+            mobilePosition.setTime(time);
+            String longitude = XmlUtil.getText(itemDevice, "Longitude");
+            if(longitude!=null) {
+                mobilePosition.setLongitude(Double.parseDouble(longitude));
+            }
+            String latitude = XmlUtil.getText(itemDevice, "Latitude");
+            if(latitude!=null) {
+                mobilePosition.setLatitude(Double.parseDouble(latitude));
+            }
+            if (NumericUtil.isDouble(XmlUtil.getText(itemDevice, "Speed"))) {
+                mobilePosition.setSpeed(Double.parseDouble(XmlUtil.getText(itemDevice, "Speed")));
+            } else {
+                mobilePosition.setSpeed(0.0);
+            }
+            if (NumericUtil.isDouble(XmlUtil.getText(itemDevice, "Direction"))) {
+                mobilePosition.setDirection(Double.parseDouble(XmlUtil.getText(itemDevice, "Direction")));
+            } else {
+                mobilePosition.setDirection(0.0);
+            }
+            if (NumericUtil.isDouble(XmlUtil.getText(itemDevice, "Altitude"))) {
+                mobilePosition.setAltitude(Double.parseDouble(XmlUtil.getText(itemDevice, "Altitude")));
+            } else {
+                mobilePosition.setAltitude(0.0);
+            }
+            mobilePosition.setReportSource("Mobile Position");
+            // 默认来源坐标系为WGS-84处理
+            Double[] gcj02Point = Coordtransform.WGS84ToGCJ02(mobilePosition.getLongitude(), mobilePosition.getLatitude());
+            logger.info("GCJ02坐标:" + gcj02Point[0] + ", " + gcj02Point[1]);
+            mobilePosition.setGeodeticSystem("GCJ-02");
+            mobilePosition.setCnLng(gcj02Point[0] + "");
+            mobilePosition.setCnLat(gcj02Point[1] + "");
+            if (!userSetting.getSavePositionHistory()) {
+                storager.clearMobilePositionsByDeviceId(deviceId);
+            }
+            storager.insertMobilePosition(mobilePosition);
+            responseAck(evt, Response.OK);
+        } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public SyncStatus getChannelSyncProgress(String deviceId) {
+        if (catalogDataCatch.get(deviceId) == null) {
+            return null;
+        }else {
+            return catalogDataCatch.getSyncStatus(deviceId);
+        }
+    }
+
+    public boolean isSyncRunning(String deviceId) {
+        if (catalogDataCatch.get(deviceId) == null) {
+            return false;
+        }else {
+            return catalogDataCatch.isSyncRunning(deviceId);
+        }
+    }
+
+    public void setChannelSyncReady(Device device, int sn) {
+        catalogDataCatch.addReady(device, sn);
+    }
+
+    public void setChannelSyncEnd(String deviceId, String errorMsg) {
+        catalogDataCatch.setChannelSyncEnd(deviceId, errorMsg);
+    }
 }

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

@@ -4,14 +4,14 @@ import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
-import com.genersoft.iot.vmp.gb28181.event.DeviceOffLineDetector;
 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;
 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.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.dom4j.DocumentException;
 import org.dom4j.Element;
 import org.slf4j.Logger;
@@ -39,13 +39,13 @@ public class DeviceInfoResponseMessageHandler extends SIPRequestProcessorParent
     private ResponseMessageHandler responseMessageHandler;
 
     @Autowired
-    private IVideoManagerStorager storager;
+    private IVideoManagerStorage storager;
 
     @Autowired
-    private DeferredResultHolder deferredResultHolder;
+    private IRedisCatchStorage redisCatchStorage;
 
     @Autowired
-    private DeviceOffLineDetector offLineDetector;
+    private DeferredResultHolder deferredResultHolder;
 
     @Autowired
     private SipConfig config;
@@ -82,7 +82,7 @@ public class DeviceInfoResponseMessageHandler extends SIPRequestProcessorParent
             deferredResultHolder.invokeAllResult(msg);
             // 回复200 OK
             responseAck(evt, Response.OK);
-            if (offLineDetector.isOnline(device.getDeviceId())) {
+            if (redisCatchStorage.deviceIsOnline(device.getDeviceId())) {
                 publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_MESSAGE);
             }
         } catch (DocumentException e) {

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

@@ -4,7 +4,6 @@ import com.alibaba.fastjson.JSONObject;
 import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
-import com.genersoft.iot.vmp.gb28181.event.DeviceOffLineDetector;
 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;
@@ -12,6 +11,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.storager.IRedisCatchStorage;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -34,8 +34,6 @@ public class DeviceStatusResponseMessageHandler extends SIPRequestProcessorParen
     @Autowired
     private ResponseMessageHandler responseMessageHandler;
 
-    @Autowired
-    private DeviceOffLineDetector offLineDetector;
 
     @Autowired
     private DeferredResultHolder deferredResultHolder;
@@ -43,6 +41,9 @@ public class DeviceStatusResponseMessageHandler extends SIPRequestProcessorParen
     @Autowired
     private EventPublisher publisher;
 
+    @Autowired
+    private IRedisCatchStorage redisCatchStorage;
+
     @Override
     public void afterPropertiesSet() throws Exception {
         responseMessageHandler.addHandler(cmdType, this);
@@ -74,10 +75,8 @@ public class DeviceStatusResponseMessageHandler extends SIPRequestProcessorParen
         msg.setData(json);
         deferredResultHolder.invokeAllResult(msg);
 
-        if (offLineDetector.isOnline(device.getDeviceId())) {
+        if (redisCatchStorage.deviceIsOnline(device.getDeviceId())) {
             publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_MESSAGE);
-        } else {
-
         }
     }
 

+ 12 - 11
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.genersoft.iot.vmp.conf.UserSetup;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.BaiduPoint;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.MobilePosition;
@@ -8,8 +8,9 @@ import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 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.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.GpsUtil;
 import org.dom4j.DocumentException;
 import org.dom4j.Element;
@@ -38,10 +39,10 @@ public class MobilePositionResponseMessageHandler extends SIPRequestProcessorPar
     private ResponseMessageHandler responseMessageHandler;
 
     @Autowired
-    private UserSetup userSetup;
+    private UserSetting userSetting;
 
     @Autowired
-    private IVideoManagerStorager storager;
+    private IVideoManagerStorage storager;
 
     @Override
     public void afterPropertiesSet() throws Exception {
@@ -79,13 +80,13 @@ public class MobilePositionResponseMessageHandler extends SIPRequestProcessorPar
                 mobilePosition.setAltitude(0.0);
             }
             mobilePosition.setReportSource("Mobile Position");
-            BaiduPoint bp = new BaiduPoint();
-            bp = GpsUtil.Wgs84ToBd09(String.valueOf(mobilePosition.getLongitude()), String.valueOf(mobilePosition.getLatitude()));
-            logger.info("百度坐标:" + bp.getBdLng() + ", " + bp.getBdLat());
-            mobilePosition.setGeodeticSystem("BD-09");
-            mobilePosition.setCnLng(bp.getBdLng());
-            mobilePosition.setCnLat(bp.getBdLat());
-            if (!userSetup.getSavePositionHistory()) {
+            // 默认来源坐标系为WGS-84处理
+            Double[] gcj02Point = Coordtransform.WGS84ToGCJ02(mobilePosition.getLongitude(), mobilePosition.getLatitude());
+            logger.info("GCJ02坐标:" + gcj02Point[0] + ", " + gcj02Point[1]);
+            mobilePosition.setGeodeticSystem("GCJ-02");
+            mobilePosition.setCnLng(gcj02Point[0] + "");
+            mobilePosition.setCnLat(gcj02Point[1] + "");
+            if (!userSetting.getSavePositionHistory()) {
                 storager.clearMobilePositionsByDeviceId(device.getDeviceId());
             }
             storager.insertMobilePosition(mobilePosition);

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

@@ -81,7 +81,7 @@ public class InviteResponseProcessor extends SIPResponseProcessorAbstract {
 				}
 				requestURI.setPort(event.getRemotePort());
 				reqAck.setRequestURI(requestURI);
-				logger.info("向 " + event.getRemoteIpAddress() + ":" + event.getRemotePort() + "回复ack");
+				logger.info("[回复ack] {}-> {}:{} ",requestURI, event.getRemoteIpAddress(), event.getRemotePort());
 
 				dialog.sendAck(reqAck);
 

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

@@ -7,7 +7,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.event.response.SIPResponseProcessorAbstract;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -33,7 +33,7 @@ public class RegisterResponseProcessor extends SIPResponseProcessorAbstract {
 	private ISIPCommanderForPlatform sipCommanderForPlatform;
 
 	@Autowired
-	private IVideoManagerStorager storager;
+	private IVideoManagerStorage storager;
 
 	@Autowired
 	private IRedisCatchStorage redisCatchStorage;
@@ -90,10 +90,12 @@ public class RegisterResponseProcessor extends SIPResponseProcessorAbstract {
 			redisCatchStorage.delPlatformCatchInfo(platformGBId);
 			// 取回Expires设置,避免注销过程中被置为0
 			ParentPlatform parentPlatformTmp = storager.queryParentPlatByServerGBId(platformGBId);
-			parentPlatformTmp.setStatus("注册".equals(action));
-			redisCatchStorage.updatePlatformRegister(parentPlatformTmp);
-			redisCatchStorage.updatePlatformKeepalive(parentPlatformTmp);
-			parentPlatformCatch.setParentPlatform(parentPlatformTmp);
+			if (parentPlatformTmp != null) {
+				parentPlatformTmp.setStatus("注册".equals(action));
+				redisCatchStorage.updatePlatformRegister(parentPlatformTmp);
+				redisCatchStorage.updatePlatformKeepalive(parentPlatformTmp);
+				parentPlatformCatch.setParentPlatform(parentPlatformTmp);
+			}
 			redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
 			storager.updateParentPlatformStatus(platformGBId, "注册".equals(action));
 			if ("注销".equals(action)) {

+ 126 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/utils/Coordtransform.java

@@ -0,0 +1,126 @@
+package com.genersoft.iot.vmp.gb28181.utils;
+
+/**
+ * 坐标转换
+ * 一个提供了百度坐标(BD09)、国测局坐标(火星坐标,GCJ02)、和WGS84坐标系之间的转换的工具类
+ * 参考https://github.com/wandergis/coordtransform 写的Java版本
+ * @author Xinconan
+ * @date 2016-03-18
+ * @url https://github.com/xinconan/coordtransform
+ */
+public class Coordtransform {
+
+	private static double x_PI = 3.14159265358979324 * 3000.0 / 180.0;
+	private static double PI = 3.1415926535897932384626;
+	private static double a = 6378245.0;
+	private static double ee = 0.00669342162296594323;
+	
+	/**
+	 * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换
+     * 即 百度 转 谷歌、高德
+	 * @param bd_lon
+	 * @param bd_lat
+	 * @return Double[lon,lat]
+	 */
+	public static Double[] BD09ToGCJ02(Double bd_lon,Double bd_lat){
+		double x = bd_lon - 0.0065;
+		double y = bd_lat - 0.006;
+		double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_PI);
+		double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_PI);
+		Double[] arr = new Double[2];
+		arr[0] = z * Math.cos(theta);
+		arr[1] = z * Math.sin(theta);
+		return arr;
+	}
+	
+	/**
+	 * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换
+     * 即谷歌、高德 转 百度
+	 * @param gcj_lon
+	 * @param gcj_lat
+	 * @return Double[lon,lat]
+	 */
+	public static Double[] GCJ02ToBD09(Double gcj_lon,Double gcj_lat){
+		double z = Math.sqrt(gcj_lon * gcj_lon + gcj_lat * gcj_lat) + 0.00002 * Math.sin(gcj_lat * x_PI);
+		double theta = Math.atan2(gcj_lat, gcj_lon) + 0.000003 * Math.cos(gcj_lon * x_PI);
+		Double[] arr = new Double[2];
+		arr[0] = z * Math.cos(theta) + 0.0065;
+		arr[1] = z * Math.sin(theta) + 0.006;
+		return arr;
+	}
+	
+	/**
+	 * WGS84转GCJ02
+	 * @param wgs_lon
+	 * @param wgs_lat
+	 * @return Double[lon,lat]
+	 */
+	public static Double[] WGS84ToGCJ02(Double wgs_lon,Double wgs_lat){
+		if(outOfChina(wgs_lon, wgs_lat)){
+			return new Double[]{wgs_lon,wgs_lat};
+		}
+		double dlat = transformlat(wgs_lon - 105.0, wgs_lat - 35.0);
+		double dlng = transformlng(wgs_lon - 105.0, wgs_lat - 35.0);
+		double radlat = wgs_lat / 180.0 * PI;
+		double magic = Math.sin(radlat);
+        magic = 1 - ee * magic * magic;
+        double sqrtmagic = Math.sqrt(magic);
+        dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);
+        dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);
+        Double[] arr = new Double[2];
+        arr[0] = wgs_lon + dlng;
+        arr[1] = wgs_lat + dlat;
+		return arr;
+	}
+	
+	/**
+	 * GCJ02转WGS84
+	 * @param gcj_lon
+	 * @param gcj_lat
+	 * @return Double[lon,lat]
+	 */
+	public static Double[] GCJ02ToWGS84(Double gcj_lon,Double gcj_lat){
+		if(outOfChina(gcj_lon, gcj_lat)){
+			return new Double[]{gcj_lon,gcj_lat};
+		}
+		double dlat = transformlat(gcj_lon - 105.0, gcj_lat - 35.0);
+		double dlng = transformlng(gcj_lon - 105.0, gcj_lat - 35.0);
+		double radlat = gcj_lat / 180.0 * PI;
+		double magic = Math.sin(radlat);
+        magic = 1 - ee * magic * magic;
+        double sqrtmagic = Math.sqrt(magic);
+        dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);
+        dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);
+        double mglat = gcj_lat + dlat;
+        double mglng = gcj_lon + dlng;
+        return new Double[]{gcj_lon * 2 - mglng, gcj_lat * 2 - mglat};
+	}
+	
+	private static Double transformlat(double lng, double lat) {
+        double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
+        ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
+        ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0;
+        ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0;
+        return ret;
+    }
+	
+	private static Double transformlng(double lng,double lat) {
+        double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
+        ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
+        ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0;
+        ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0;
+        return ret;
+    }
+	
+	/**
+	 * outOfChina
+	 * @描述: 判断是否在国内,不在国内则不做偏移
+	 * @param lng
+	 * @param lat
+	 * @return {boolean}
+	 */
+	private static boolean outOfChina(Double lng,Double lat) {
+        return (lng < 72.004 || lng > 137.8347) || ((lat < 0.8293 || lat > 55.8271) || false);
+    };
+	
+}

+ 12 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java

@@ -2,8 +2,10 @@ package com.genersoft.iot.vmp.gb28181.utils;
 
 import gov.nist.javax.sip.address.AddressImpl;
 import gov.nist.javax.sip.address.SipUri;
+import gov.nist.javax.sip.header.Subject;
 
 import javax.sip.header.FromHeader;
+import javax.sip.header.Header;
 import javax.sip.message.Request;
 
 /**
@@ -18,6 +20,16 @@ public class SipUtils {
         FromHeader fromHeader = (FromHeader)request.getHeader(FromHeader.NAME);
         return getUserIdFromFromHeader(fromHeader);
     }
+    /**
+     * 从subject读取channelId
+     * */
+    public static String getChannelIdFromHeader(Request request) {
+        Header subject = request.getHeader("subject");
+        if (subject == null) {
+            return null;
+        }
+        return ((Subject) subject).getSubject().split(":")[0];
+    }
 
     public static String getUserIdFromFromHeader(FromHeader fromHeader) {
         AddressImpl address = (AddressImpl)fromHeader.getAddress();

+ 66 - 13
src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java

@@ -20,8 +20,8 @@ import java.util.*;
 
 /**
  * 基于dom4j的工具包
- * 
- * 
+ *
+ *
  */
 public class XmlUtil {
     /**
@@ -31,9 +31,9 @@ public class XmlUtil {
 
     /**
      * 解析XML为Document对象
-     * 
+     *
      * @param xml 被解析的XMl
-     * 
+     *
      * @return Document
      */
     public static Element parseXml(String xml) {
@@ -51,7 +51,7 @@ public class XmlUtil {
 
     /**
      * 获取element对象的text的值
-     * 
+     *
      * @param em  节点的对象
      * @param tag 节点的tag
      * @return 节点
@@ -62,12 +62,12 @@ public class XmlUtil {
         }
         Element e = em.element(tag);
         //
-        return null == e ? null : e.getText();
+        return null == e ? null : e.getText().trim();
     }
 
     /**
      * 递归解析xml节点,适用于 多节点数据
-     * 
+     *
      * @param node     node
      * @param nodeName nodeName
      * @return List<Map<String, Object>>
@@ -106,7 +106,7 @@ public class XmlUtil {
 
     /**
      * xml转json
-     * 
+     *
      * @param element
      * @param json
      */
@@ -204,13 +204,66 @@ public class XmlUtil {
         deviceChannel.setCivilCode(XmlUtil.getText(itemDevice, "CivilCode"));
         deviceChannel.setBlock(XmlUtil.getText(itemDevice, "Block"));
         deviceChannel.setAddress(XmlUtil.getText(itemDevice, "Address"));
+        String businessGroupID = XmlUtil.getText(itemDevice, "BusinessGroupID");
         if (XmlUtil.getText(itemDevice, "Parental") == null
-                || XmlUtil.getText(itemDevice, "Parental") == "") {
-            deviceChannel.setParental(0);
+                || XmlUtil.getText(itemDevice, "Parental").equals("")) {
+            if (deviceChannel.getChannelId().length() <= 10
+                    || (deviceChannel.getChannelId().length() == 20 && (
+                            Integer.parseInt(deviceChannel.getChannelId().substring(10, 13)) == 215
+                                    || Integer.parseInt(deviceChannel.getChannelId().substring(10, 13)) == 216
+                            )
+                        )
+            ) {
+                deviceChannel.setParental(1);
+            }else {
+                deviceChannel.setParental(0);
+            }
         } else {
-            deviceChannel.setParental(Integer.parseInt(XmlUtil.getText(itemDevice, "Parental")));
+            // 由于海康会错误的发送65535作为这里的取值,所以这里除非是0否则认为是1
+            deviceChannel.setParental(Integer.parseInt(XmlUtil.getText(itemDevice, "Parental")) == 1?1:0);
+        }
+        /**
+         * 行政区划展示设备树与业务分组展示设备树是两种不同的模式
+         * 行政区划展示设备树 各个目录之间主要靠deviceId做关联,摄像头通过CivilCode指定其属于那个行政区划;都是不超过十位的编号; 结构如下:
+         * 河北省
+         *    --> 石家庄市
+         *          --> 摄像头
+         *          --> 正定县
+         *                  --> 摄像头
+         *                  --> 摄像头
+         *
+         * 业务分组展示设备树是顶级是业务分组,其下的虚拟组织靠BusinessGroupID指定其所属的业务分组;摄像头通过ParentId来指定其所属于的虚拟组织:
+         * 业务分组
+         *    --> 虚拟组织
+         *         --> 摄像头
+         *         --> 虚拟组织
+         *             --> 摄像头
+         *             --> 摄像头
+         */
+        String parentId = XmlUtil.getText(itemDevice, "ParentID");
+        if (parentId != null) {
+            if (parentId.contains("/")) {
+                String lastParentId = parentId.substring(parentId.lastIndexOf("/") + 1);
+                deviceChannel.setParentId(lastParentId);
+            }else {
+                deviceChannel.setParentId(parentId);
+            }
+        }else {
+            if (deviceChannel.getChannelId().length() <= 10) { // 此时为行政区划, 上下级行政区划使用DeviceId关联
+                deviceChannel.setParentId(deviceChannel.getChannelId().substring(0, deviceChannel.getChannelId().length() - 2));
+            }else if (deviceChannel.getChannelId().length() == 20) {
+                if (Integer.parseInt(deviceChannel.getChannelId().substring(10, 13)) == 216) { // 虚拟组织
+                    deviceChannel.setParentId(businessGroupID);
+                }else if (deviceChannel.getCivilCode() != null) {
+                    // 设备, 无parentId的20位是使用CivilCode表示上级的设备,
+                    // 注:215 业务分组是需要有parentId的
+                    deviceChannel.setParentId(deviceChannel.getCivilCode());
+                }
+            }else {
+                deviceChannel.setParentId(deviceChannel.getDeviceId());
+            }
         }
-        deviceChannel.setParentId(XmlUtil.getText(itemDevice, "ParentID"));
+
         if (XmlUtil.getText(itemDevice, "SafetyWay") == null
                 || XmlUtil.getText(itemDevice, "SafetyWay") == "") {
             deviceChannel.setSafetyWay(0);
@@ -269,4 +322,4 @@ public class XmlUtil {
         deviceChannel.setHasAudio(true); // 默认含有音频,播放时再检查是否有音频及是否AAC
         return deviceChannel;
     }
-}
+}

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

@@ -0,0 +1,139 @@
+package com.genersoft.iot.vmp.media.zlm;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import okhttp3.*;
+import okhttp3.logging.HttpLoggingInterceptor;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+@Component
+public class AssistRESTfulUtils {
+
+    private final static Logger logger = LoggerFactory.getLogger(AssistRESTfulUtils.class);
+
+    public interface RequestCallback{
+        void run(JSONObject response);
+    }
+
+    private OkHttpClient getClient(){
+        OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
+        if (logger.isDebugEnabled()) {
+            HttpLoggingInterceptor logging = new HttpLoggingInterceptor(message -> {
+                logger.debug("http请求参数:" + message);
+            });
+            logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
+            // OkHttp進行添加攔截器loggingInterceptor
+            httpClientBuilder.addInterceptor(logging);
+        }
+        return httpClientBuilder.build();
+    }
+
+
+    public JSONObject sendGet(MediaServerItem mediaServerItem, String api, Map<String, Object> param, RequestCallback callback) {
+        OkHttpClient client = getClient();
+
+        if (mediaServerItem == null) {
+            return null;
+        }
+        if (StringUtils.isEmpty(mediaServerItem.getRecordAssistPort())) {
+            logger.warn("未启用Assist服务");
+            return null;
+        }
+        StringBuffer stringBuffer = new StringBuffer();
+        stringBuffer.append(String.format("http://%s:%s/%s",  mediaServerItem.getIp(), mediaServerItem.getRecordAssistPort(), api));
+        JSONObject responseJSON = null;
+
+        if (param != null && param.keySet().size() > 0) {
+            stringBuffer.append("?");
+            int index = 1;
+            for (String key : param.keySet()){
+                if (param.get(key) != null) {
+                    stringBuffer.append(key + "=" + param.get(key));
+                    if (index < param.size()) {
+                        stringBuffer.append("&");
+                    }
+                }
+                index++;
+            }
+        }
+
+        String url = stringBuffer.toString();
+        Request request = new Request.Builder()
+                .get()
+                .url(url)
+                .build();
+            if (callback == null) {
+                try {
+                    Response response = client.newCall(request).execute();
+                    if (response.isSuccessful()) {
+                        ResponseBody responseBody = response.body();
+                        if (responseBody != null) {
+                            String responseStr = responseBody.string();
+                            responseJSON = JSON.parseObject(responseStr);
+                        }
+                    }else {
+                        response.close();
+                        Objects.requireNonNull(response.body()).close();
+                    }
+                } catch (ConnectException e) {
+                    logger.error(String.format("连接Assist失败: %s, %s", e.getCause().getMessage(), e.getMessage()));
+                    logger.info("请检查media配置并确认Assist已启动...");
+                }catch (IOException e) {
+                    logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage()));
+                }
+            }else {
+                client.newCall(request).enqueue(new Callback(){
+
+                    @Override
+                    public void onResponse(@NotNull Call call, @NotNull Response response){
+                        if (response.isSuccessful()) {
+                            try {
+                                String responseStr = Objects.requireNonNull(response.body()).string();
+                                callback.run(JSON.parseObject(responseStr));
+                            } catch (IOException e) {
+                                logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage()));
+                            }
+
+                        }else {
+                            response.close();
+                            Objects.requireNonNull(response.body()).close();
+                        }
+                    }
+
+                    @Override
+                    public void onFailure(@NotNull Call call, @NotNull IOException e) {
+                        logger.error(String.format("连接Assist失败: %s, %s", e.getCause().getMessage(), e.getMessage()));
+                        logger.info("请检查media配置并确认Assist已启动...");
+                    }
+                });
+            }
+
+
+
+        return responseJSON;
+    }
+
+
+    public JSONObject fileDuration(MediaServerItem mediaServerItem, String app, String stream, RequestCallback callback){
+        Map<String, Object> param = new HashMap<>();
+        param.put("app",app);
+        param.put("stream",stream);
+        param.put("recordIng",true);
+        return sendGet(mediaServerItem, "api/record/file/duration",param, callback);
+    }
+
+}

+ 26 - 29
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java

@@ -2,12 +2,10 @@ package com.genersoft.iot.vmp.media.zlm;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.UUID;
 
 import com.alibaba.fastjson.JSON;
 import com.genersoft.iot.vmp.common.StreamInfo;
-import com.genersoft.iot.vmp.conf.MediaConfig;
-import com.genersoft.iot.vmp.conf.UserSetup;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
 import com.genersoft.iot.vmp.gb28181.bean.GbStream;
@@ -17,9 +15,8 @@ import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.media.zlm.dto.*;
 import com.genersoft.iot.vmp.service.*;
-import com.genersoft.iot.vmp.service.bean.SSRCInfo;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -54,7 +51,7 @@ public class ZLMHttpHookListener {
 	private IPlayService playService;
 
 	@Autowired
-	private IVideoManagerStorager storager;
+	private IVideoManagerStorage storager;
 
 	@Autowired
 	private IRedisCatchStorage redisCatchStorage;
@@ -81,14 +78,11 @@ public class ZLMHttpHookListener {
 	private ZLMHttpHookSubscribe subscribe;
 
 	@Autowired
-	private UserSetup userSetup;
+	private UserSetting userSetting;
 
 	@Autowired
 	private VideoStreamSessionManager sessionManager;
 
-	@Autowired
-	private ZLMRESTfulUtils zlmresTfulUtils;
-
 	/**
 	 * 服务器定时上报时间,上报间隔可配置,默认10s上报一次
 	 *
@@ -192,6 +186,12 @@ public class ZLMHttpHookListener {
 		ret.put("code", 0);
 		ret.put("msg", "success");
 		ret.put("enable_hls", true);
+		if (json.getInteger("originType") == 1
+				|| json.getInteger("originType") == 2
+				|| json.getInteger("originType") == 3) {
+			ret.put("enable_audio", true);
+		}
+
 		String mediaServerId = json.getString("mediaServerId");
 		ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_publish, json);
 		if (subscribe != null) {
@@ -206,9 +206,9 @@ public class ZLMHttpHookListener {
 	 	String app = json.getString("app");
 	 	String stream = json.getString("stream");
 		if ("rtp".equals(app)) {
-			ret.put("enable_mp4", userSetup.getRecordSip());
+			ret.put("enable_mp4", userSetting.getRecordSip());
 		}else {
-			ret.put("enable_mp4", userSetup.isRecordPushLive());
+			ret.put("enable_mp4", userSetting.isRecordPushLive());
 		}
 		List<SsrcTransaction> ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, stream);
 		if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) {
@@ -218,7 +218,16 @@ public class ZLMHttpHookListener {
 			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 new ResponseEntity<String>(ret.toString(), HttpStatus.OK);
 	}
 	
@@ -327,7 +336,6 @@ public class ZLMHttpHookListener {
 			if (mediaInfo != null) {
 				subscribe.response(mediaInfo, json);
 			}
-
 		}
 		// 流消失移除redis play
 		String app = item.getApp();
@@ -388,7 +396,7 @@ public class ZLMHttpHookListener {
 								}
 							}
 							if (gbStreams.size() > 0) {
-								eventPublisher.catalogEventPublishForStream(null, gbStreams, CatalogEvent.ON);
+//								eventPublisher.catalogEventPublishForStream(null, gbStreams, CatalogEvent.ON);
 							}
 
 						}else {
@@ -400,14 +408,14 @@ public class ZLMHttpHookListener {
 							}
 							GbStream gbStream = storager.getGbStream(app, streamId);
 							if (gbStream != null) {
-								eventPublisher.catalogEventPublishForStream(null, gbStream, CatalogEvent.OFF);
+//								eventPublisher.catalogEventPublishForStream(null, gbStream, CatalogEvent.OFF);
 							}
 							zlmMediaListManager.removeMedia(app, streamId);
 						}
 						if (type != null) {
 							// 发送流变化redis消息
 							JSONObject jsonObject = new JSONObject();
-							jsonObject.put("serverId", userSetup.getServerId());
+							jsonObject.put("serverId", userSetting.getServerId());
 							jsonObject.put("app", app);
 							jsonObject.put("stream", streamId);
 							jsonObject.put("register", regist);
@@ -444,6 +452,7 @@ public class ZLMHttpHookListener {
 		if ("rtp".equals(app)){
 			ret.put("close", true);
 			StreamInfo streamInfoForPlayCatch = redisCatchStorage.queryPlayByStreamId(streamId);
+			SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, null, streamId);
 			if (streamInfoForPlayCatch != null) {
 				// 如果在给上级推流,也不停止。
 				if (redisCatchStorage.isChannelSendingRTP(streamInfoForPlayCatch.getChannelId())) {
@@ -481,18 +490,6 @@ public class ZLMHttpHookListener {
 				streamProxyService.del(app, streamId);
 				String url = streamProxyItem.getUrl() != null?streamProxyItem.getUrl():streamProxyItem.getSrc_url();
 				logger.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除",  app, streamId, url);
-
-			}else if (streamProxyItem != null && streamProxyItem.isEnable()) {
-				MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
-				if(null!=mediaServerItem){
-					JSONObject jsonObject = zlmresTfulUtils.closeStreams(mediaServerItem,streamProxyItem.getApp(), streamProxyItem.getStream());
-					if (jsonObject.getInteger("code") == 0) {
-						streamProxyItem.setEnable(false);
-						storager.updateStreamProxy(streamProxyItem);
-					}
-				}else {
-					ret.put("close", false);
-				}
 			}else {
 				ret.put("close", false);
 			}
@@ -512,7 +509,7 @@ public class ZLMHttpHookListener {
 		}
 		String mediaServerId = json.getString("mediaServerId");
 		MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
-		if (userSetup.isAutoApplyPlay() && mediaInfo != null && mediaInfo.isRtpEnable()) {
+		if (userSetting.isAutoApplyPlay() && mediaInfo != null && mediaInfo.isRtpEnable()) {
 			String app = json.getString("app");
 			String streamId = json.getString("stream");
 			if ("rtp".equals(app)) {

+ 15 - 8
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookSubscribe.java

@@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.media.zlm;
 import com.alibaba.fastjson.JSONObject;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
 
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
@@ -31,6 +32,7 @@ public class ZLMHttpHookSubscribe {
         on_server_keepalive
     }
 
+    @FunctionalInterface
     public interface Event{
         void response(MediaServerItem mediaServerItem, JSONObject response);
     }
@@ -38,12 +40,7 @@ public class ZLMHttpHookSubscribe {
     private Map<HookType, Map<JSONObject, ZLMHttpHookSubscribe.Event>> allSubscribes = new ConcurrentHashMap<>();
 
     public void addSubscribe(HookType type, JSONObject hookResponse, ZLMHttpHookSubscribe.Event event) {
-        Map<JSONObject, Event> eventMap = allSubscribes.get(type);
-        if (eventMap == null) {
-            eventMap = new HashMap<JSONObject, Event>();
-            allSubscribes.put(type,eventMap);
-        }
-        eventMap.put(hookResponse, event);
+        allSubscribes.computeIfAbsent(type, k -> new ConcurrentHashMap<>()).put(hookResponse, event);
     }
 
     public ZLMHttpHookSubscribe.Event getSubscribe(HookType type, JSONObject hookResponse) {
@@ -80,21 +77,31 @@ public class ZLMHttpHookSubscribe {
 
         Set<Map.Entry<JSONObject, Event>> entries = eventMap.entrySet();
         if (entries.size() > 0) {
-            for (Map.Entry<JSONObject, Event> entry : entries) {
+            List<Map.Entry<JSONObject, ZLMHttpHookSubscribe.Event>> entriesToRemove = new ArrayList<>();
+            for (Map.Entry<JSONObject, ZLMHttpHookSubscribe.Event> entry : entries) {
                 JSONObject key = entry.getKey();
                 Boolean result = null;
                 for (String s : key.keySet()) {
                     if (result == null) {
                         result = key.getString(s).equals(hookResponse.getString(s));
                     }else {
-                        if (key.getString(s) == null) continue;
+                        if (key.getString(s) == null) {
+                            continue;
+                        }
                         result = result && key.getString(s).equals(hookResponse.getString(s));
                     }
                 }
                 if (null != result && result){
+                    entriesToRemove.add(entry);
+                }
+            }
+
+            if (!CollectionUtils.isEmpty(entriesToRemove)) {
+                for (Map.Entry<JSONObject, ZLMHttpHookSubscribe.Event> entry : entriesToRemove) {
                     entries.remove(entry);
                 }
             }
+
         }
     }
 

+ 54 - 17
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java

@@ -1,20 +1,18 @@
 package com.genersoft.iot.vmp.media.zlm;
 
 import com.alibaba.fastjson.JSONObject;
-import com.genersoft.iot.vmp.conf.UserSetup;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.GbStream;
-import com.genersoft.iot.vmp.media.zlm.dto.MediaItem;
-import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
-import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
-import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
+import com.genersoft.iot.vmp.media.zlm.dto.*;
 import com.genersoft.iot.vmp.service.IStreamProxyService;
 import com.genersoft.iot.vmp.service.IStreamPushService;
 import com.genersoft.iot.vmp.service.bean.ThirdPartyGB;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.storager.dao.GbStreamMapper;
 import com.genersoft.iot.vmp.storager.dao.PlatformGbStreamMapper;
 import com.genersoft.iot.vmp.storager.dao.StreamPushMapper;
+import org.checkerframework.checker.units.qual.C;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -22,6 +20,7 @@ import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
 
 import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -37,7 +36,7 @@ public class ZLMMediaListManager {
     private IRedisCatchStorage redisCatchStorage;
 
     @Autowired
-    private IVideoManagerStorager storager;
+    private IVideoManagerStorage storager;
 
     @Autowired
     private GbStreamMapper gbStreamMapper;
@@ -58,7 +57,9 @@ public class ZLMMediaListManager {
     private ZLMHttpHookSubscribe subscribe;
 
     @Autowired
-    private UserSetup userSetup;
+    private UserSetting userSetting;
+
+    private Map<String, ChannelOnlineEvent> channelOnlineEvents = new ConcurrentHashMap<>();
 
 
     public void updateMediaList(MediaServerItem mediaServerItem) {
@@ -66,7 +67,9 @@ public class ZLMMediaListManager {
 
         // 使用异步的当时更新媒体流列表
         zlmresTfulUtils.getMediaList(mediaServerItem, (mediaList ->{
-            if (mediaList == null) return;
+            if (mediaList == null) {
+                return;
+            }
             String dataStr = mediaList.getString("data");
 
             Integer code = mediaList.getInteger("code");
@@ -109,7 +112,7 @@ public class ZLMMediaListManager {
         // 查找此直播流是否存在redis预设gbId
         StreamPushItem transform = streamPushService.transform(mediaItem);
         // 从streamId取出查询关键值
-        Pattern pattern = Pattern.compile(userSetup.getThirdPartyGBIdReg());
+        Pattern pattern = Pattern.compile(userSetting.getThirdPartyGBIdReg());
         Matcher matcher = pattern.matcher(mediaItem.getStream());// 指定要匹配的字符串
         String queryKey = null;
         if (matcher.find()) { //此处find()每次被调用后,会偏移到下一个匹配
@@ -128,21 +131,44 @@ public class ZLMMediaListManager {
             if (gbStreams.size() > 0) {
                 for (GbStream gbStream : gbStreams) {
                     // 出现使用相同国标Id的视频流时,使用新流替换旧流,
-                    gbStreamMapper.del(gbStream.getApp(), gbStream.getStream());
-                    if (!gbStream.isStatus()) {
-                        streamPushMapper.del(gbStream.getApp(), gbStream.getStream());
+                    if (queryKey != null && gbStream.getApp().equals(mediaItem.getApp())) {
+                        Matcher matcherForStream = pattern.matcher(gbStream.getStream());
+                        String queryKeyForStream = null;
+                        if (matcherForStream.find()) { //此处find()每次被调用后,会偏移到下一个匹配
+                            queryKeyForStream = matcherForStream.group();
+                        }
+                        if (queryKeyForStream == null || !queryKeyForStream.equals(queryKey)) {
+                            // 此时不是同一个流
+                            gbStreamMapper.del(gbStream.getApp(), gbStream.getStream());
+                            if (!gbStream.isStatus()) {
+                                streamPushMapper.del(gbStream.getApp(), gbStream.getStream());
+                            }
+                        }
                     }
                 }
             }
-            StreamProxyItem streamProxyItem = gbStreamMapper.selectOne(transform.getApp(), transform.getStream());
-            if (streamProxyItem != null) {
-                transform.setGbStreamId(streamProxyItem.getGbStreamId());
+            //            StreamProxyItem streamProxyItem = gbStreamMapper.selectOne(transform.getApp(), transform.getStream());
+            List<GbStream> gbStreamList = gbStreamMapper.selectByGBId(transform.getGbId());
+            if (gbStreamList != null && gbStreamList.size() == 1) {
+                transform.setGbStreamId(gbStreamList.get(0).getGbStreamId());
+                transform.setPlatformId(gbStreamList.get(0).getPlatformId());
+                transform.setCatalogId(gbStreamList.get(0).getCatalogId());
+                transform.setGbId(gbStreamList.get(0).getGbId());
                 gbStreamMapper.update(transform);
+                streamPushMapper.del(gbStreamList.get(0).getApp(), gbStreamList.get(0).getStream());
             }else {
                 transform.setCreateStamp(System.currentTimeMillis());
                 gbStreamMapper.add(transform);
             }
+            if (transform != null) {
+                if (channelOnlineEvents.get(transform.getGbId()) != null)  {
+                    channelOnlineEvents.get(transform.getGbId()).run(transform.getApp(), transform.getStream());
+                    channelOnlineEvents.remove(transform.getGbId());
+                }
+            }
         }
+
+
         storager.updateMedia(transform);
         return transform;
     }
@@ -152,7 +178,9 @@ public class ZLMMediaListManager {
         //使用异步更新推流
         zlmresTfulUtils.getMediaList(mediaServerItem, app, streamId, "rtmp", json->{
 
-            if (json == null) return;
+            if (json == null) {
+                return;
+            }
             String dataStr = json.getString("data");
 
             Integer code = json.getInteger("code");
@@ -180,11 +208,20 @@ public class ZLMMediaListManager {
         if (streamProxyItem == null) {
             result = storager.removeMedia(app, streamId);
         }else {
+            // TODO 暂不设置为离线
             result =storager.mediaOutline(app, streamId);
         }
         return result;
     }
 
+    public void addChannelOnlineEventLister(String key, ChannelOnlineEvent callback) {
+        this.channelOnlineEvents.put(key,callback);
+    }
+
+    public void removedChannelOnlineEventLister(String key) {
+        this.channelOnlineEvents.remove(key);
+    }
+
 
 
 //    public void clearAllSessions() {

+ 5 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java

@@ -269,6 +269,11 @@ public class ZLMRESTfulUtils {
         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("rtp_type", rtp_type);
         return sendPost(mediaServerItem, "addStreamProxy",param, null);
     }

+ 14 - 6
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java

@@ -23,7 +23,9 @@ public class ZLMRTPServerFactory {
     private int[] portRangeArray = new int[2];
 
     public int getFreePort(MediaServerItem mediaServerItem, int startPort, int endPort, List<Integer> usedFreelist) {
-        if (endPort <= startPort) return -1;
+        if (endPort <= startPort) {
+            return -1;
+        }
         if (usedFreelist == null) {
             usedFreelist = new ArrayList<>();
         }
@@ -81,14 +83,20 @@ public class ZLMRTPServerFactory {
         return result;
     }
 
-    public int createRTPServer(MediaServerItem mediaServerItem, String streamId) {
-
-        Map<String, Object> param = new HashMap<>();
+    public int createRTPServer(MediaServerItem mediaServerItem, String streamId, int ssrc) {
         int result = -1;
+        // 查询此rtp server 是否已经存在
+        JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId);
+        if (rtpInfo != null && rtpInfo.getInteger("code") == 0 && rtpInfo.getBoolean("exist")) {
+            result = rtpInfo.getInteger("local_port");
+            return result;
+        }
+        Map<String, Object> param = new HashMap<>();
         // 推流端口设置0则使用随机端口
         param.put("enable_tcp", 1);
         param.put("stream_id", streamId);
         param.put("port", 0);
+        param.put("ssrc", ssrc);
         JSONObject openRtpServerResultJson = zlmresTfulUtils.openRtpServer(mediaServerItem, param);
 
         if (openRtpServerResultJson != null) {
@@ -244,7 +252,7 @@ public class ZLMRTPServerFactory {
             logger.error("RTP推流失败: 请检查ZLM服务");
         } else if (jsonObject.getInteger("code") == 0) {
             result= true;
-            logger.info("RTP推流成功[ {}/{} ],本地推流端口:{}" ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"));
+            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));
         }
@@ -301,7 +309,7 @@ public class ZLMRTPServerFactory {
             result= true;
             logger.info("停止RTP推流成功");
         } else {
-            logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"),JSONObject.toJSON(param));
+            logger.error("停止RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"),JSONObject.toJSON(param));
         }
         return result;
     }

+ 21 - 19
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java

@@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.media.zlm;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.conf.MediaConfig;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
@@ -51,6 +52,9 @@ public class ZLMRunner implements CommandLineRunner {
     @Autowired
     private MediaConfig mediaConfig;
 
+    @Autowired
+    private DynamicTask dynamicTask;
+
     @Qualifier("taskExecutor")
     @Autowired
     private ThreadPoolTaskExecutor taskExecutor;
@@ -67,7 +71,7 @@ public class ZLMRunner implements CommandLineRunner {
         }
         mediaServerService.syncCatchFromDatabase();
         // 订阅 zlm启动事件, 新的zlm也会从这里进入系统
-        hookSubscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_server_started,null,
+        hookSubscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_server_started,new JSONObject(),
                 (MediaServerItem mediaServerItem, JSONObject response)->{
             ZLMServerConfig zlmServerConfig = JSONObject.toJavaObject(response, ZLMServerConfig.class);
             if (zlmServerConfig !=null ) {
@@ -79,7 +83,7 @@ public class ZLMRunner implements CommandLineRunner {
         });
 
         // 订阅 zlm保活事件, 当zlm离线时做业务的处理
-        hookSubscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_server_keepalive,null,
+        hookSubscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_server_keepalive,new JSONObject(),
                 (MediaServerItem mediaServerItem, JSONObject response)->{
                     String mediaServerId = response.getString("mediaServerId");
                     if (mediaServerId !=null ) {
@@ -97,27 +101,25 @@ public class ZLMRunner implements CommandLineRunner {
             all.add(mediaConfig.getMediaSerItem());
         }
         for (MediaServerItem mediaServerItem : all) {
-            if (startGetMedia == null) startGetMedia = new HashMap<>();
+            if (startGetMedia == null) {
+                startGetMedia = new HashMap<>();
+            }
             startGetMedia.put(mediaServerItem.getId(), true);
             taskExecutor.execute(()->{
                 connectZlmServer(mediaServerItem);
             });
         }
-        Timer timer = new Timer();
-        // 10分钟后未连接到则不再去主动连接, TODO 并对重启前使用此在zlm的通道发送bye
-        timer.schedule(new TimerTask() {
-            @Override
-            public void run() {
+        String taskKey = "zlm-connect-timeout";
+        dynamicTask.startDelay(taskKey, ()->{
             if (startGetMedia != null) {
                 Set<String> allZlmId = startGetMedia.keySet();
                 for (String id : allZlmId) {
-                    logger.error("[ {} ]]主动连接失败,不再主动连接", id);
+                    logger.error("[ {} ]]主动连接失败,不再尝试连接", id);
                 }
                 startGetMedia = null;
             }
-            //  TODO 清理数据库中与redis不匹配的zlm
-            }
-        }, 60 * 1000 * 10);
+        //  TODO 清理数据库中与redis不匹配的zlm
+        }, 6 * 1000 );
     }
 
     @Async
@@ -139,12 +141,12 @@ public class ZLMRunner implements CommandLineRunner {
         if ( startGetMedia.get(mediaServerItem.getId()) == null || !startGetMedia.get(mediaServerItem.getId())) {
             return null;
         }
-        JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig(mediaServerItem);
-        ZLMServerConfig ZLMServerConfig = null;
-        if (responseJSON != null) {
-            JSONArray data = responseJSON.getJSONArray("data");
+        JSONObject responseJson = zlmresTfulUtils.getMediaServerConfig(mediaServerItem);
+        ZLMServerConfig zlmServerConfig = null;
+        if (responseJson != null) {
+            JSONArray data = responseJson.getJSONArray("data");
             if (data != null && data.size() > 0) {
-                ZLMServerConfig = JSON.parseObject(JSON.toJSONString(data.get(0)), ZLMServerConfig.class);
+                zlmServerConfig = JSON.parseObject(JSON.toJSONString(data.get(0)), ZLMServerConfig.class);
             }
         } else {
             logger.error("[ {} ]-[ {}:{} ]第{}次主动连接失败, 2s后重试",
@@ -159,9 +161,9 @@ public class ZLMRunner implements CommandLineRunner {
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
-            ZLMServerConfig = getMediaServerConfig(mediaServerItem, index += 1);
+            zlmServerConfig = getMediaServerConfig(mediaServerItem, index += 1);
         }
-        return ZLMServerConfig;
+        return zlmServerConfig;
 
     }
 

+ 6 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ChannelOnlineEvent.java

@@ -0,0 +1,6 @@
+package com.genersoft.iot.vmp.media.zlm.dto;
+
+public interface ChannelOnlineEvent {
+
+    void run(String app, String stream);
+}

+ 0 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor