648540858 2 лет назад
Родитель
Сommit
b4048fbe80
100 измененных файлов с 1870 добавлено и 645 удалено
  1. 3 2
      README.md
  2. 1 1
      doc/_content/ability/gis.md
  3. 21 2
      doc/_content/introduction/deployment.md
  4. 14 11
      pom.xml
  5. 12 0
      sql/2.6.6-2.6.7更新.sql
  6. 3 0
      src/main/resources/db/migration/V2.6.7_20230201__初始化.sql
  7. 0 2
      src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java
  8. 8 4
      src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java
  9. 1 3
      src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java
  10. 23 0
      src/main/java/com/genersoft/iot/vmp/conf/GlobalExceptionHandler.java
  11. 0 3
      src/main/java/com/genersoft/iot/vmp/conf/GlobalResponseAdvice.java
  12. 15 7
      src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java
  13. 12 1
      src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java
  14. 6 3
      src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java
  15. 39 0
      src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
  16. 0 64
      src/main/java/com/genersoft/iot/vmp/conf/druid/DruidConfiguration.java
  17. 0 24
      src/main/java/com/genersoft/iot/vmp/conf/druid/EnableDruidSupport.java
  18. 9 11
      src/main/java/com/genersoft/iot/vmp/conf/security/AnonymousAuthenticationEntryPoint.java
  19. 9 6
      src/main/java/com/genersoft/iot/vmp/conf/security/DefaultUserDetailsServiceImpl.java
  20. 0 24
      src/main/java/com/genersoft/iot/vmp/conf/security/InvalidSessionHandler.java
  21. 84 0
      src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java
  22. 138 0
      src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java
  23. 11 2
      src/main/java/com/genersoft/iot/vmp/conf/security/LoginSuccessHandler.java
  24. 13 3
      src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java
  25. 56 66
      src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java
  26. 53 0
      src/main/java/com/genersoft/iot/vmp/conf/security/dto/JwtUser.java
  27. 9 0
      src/main/java/com/genersoft/iot/vmp/conf/security/dto/LoginUser.java
  28. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java
  29. 23 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
  30. 11 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java
  31. 10 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatformCatch.java
  32. 11 2
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java
  33. 19 8
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java
  34. 1 4
      src/main/java/com/genersoft/iot/vmp/gb28181/event/device/RequestTimeoutEventImpl.java
  35. 2 2
      src/main/java/com/genersoft/iot/vmp/gb28181/task/SipRunner.java
  36. 1 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  37. 4 7
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
  38. 14 9
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java
  39. 30 19
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  40. 48 16
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
  41. 2 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java
  42. 33 21
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
  43. 14 6
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java
  44. 50 6
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java
  45. 7 7
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java
  46. 32 29
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java
  47. 13 5
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java
  48. 2 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java
  49. 3 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MobilePositionNotifyMessageHandler.java
  50. 12 5
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceInfoQueryMessageHandler.java
  51. 2 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java
  52. 2 5
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceStatusResponseMessageHandler.java
  53. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/MobilePositionResponseMessageHandler.java
  54. 7 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java
  55. 8 6
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java
  56. 5 5
      src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java
  57. 27 2
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
  58. 2 2
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
  59. 4 1
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
  60. 400 3
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerConfig.java
  61. 9 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java
  62. 3 2
      src/main/java/com/genersoft/iot/vmp/service/IDeviceService.java
  63. 8 1
      src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java
  64. 12 4
      src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
  65. 12 2
      src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java
  66. 29 0
      src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
  67. 52 12
      src/main/java/com/genersoft/iot/vmp/service/impl/PlatformChannelServiceImpl.java
  68. 97 18
      src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java
  69. 73 16
      src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
  70. 1 1
      src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java
  71. 45 9
      src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java
  72. 2 1
      src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGbPlayMsgListener.java
  73. 2 1
      src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGpsMsgListener.java
  74. 2 1
      src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamResponseListener.java
  75. 2 1
      src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamStatusListMsgListener.java
  76. 2 1
      src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamStatusMsgListener.java
  77. 2 1
      src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisStreamMsgListener.java
  78. 8 1
      src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java
  79. 53 25
      src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java
  80. 21 7
      src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java
  81. 7 3
      src/main/java/com/genersoft/iot/vmp/storager/dao/ParentPlatformMapper.java
  82. 9 6
      src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
  83. 20 4
      src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
  84. 0 1
      src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java
  85. 7 1
      src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java
  86. 4 0
      src/main/java/com/genersoft/iot/vmp/vmanager/bean/WVPResult.java
  87. 1 1
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/MobilePosition/MobilePositionController.java
  88. 1 1
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/SseController/SseController.java
  89. 11 17
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/alarm/AlarmController.java
  90. 0 4
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceConfig.java
  91. 1 1
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java
  92. 5 2
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java
  93. 1 1
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/gbStream/GbStreamController.java
  94. 1 1
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/media/MediaController.java
  95. 9 54
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/platform/PlatformController.java
  96. 1 1
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
  97. 1 1
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java
  98. 7 9
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/ptz/PtzController.java
  99. 8 11
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java
  100. 0 0
      src/main/java/com/genersoft/iot/vmp/vmanager/log/LogController.java

+ 3 - 2
README.md

@@ -99,15 +99,16 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
 - [X] 支持接口鉴权
 - [X] 云端录像,推流/代理/国标视频均可以录制在云端服务器,支持预览和下载
 - [X] 支持打包可执行jar和war
+- [X] 支持跨域请求,支持前后端分离部署
  
 
 # 遇到问题如何解决
 国标最麻烦的地方在于设备的兼容性,所以需要大量的设备来测试,目前作者手里的设备有限,再加上作者水平有限,所以遇到问题在所难免;
-1. 查看wiki,仔细的阅读可以帮你避免几乎所有的问题
+1. 查看文档网站,仔细的阅读可以帮你避免几乎所有的问题
 2. 搜索issues,这里有大部分的答案
 3. 加QQ群(901799015),这里有大量热心的小伙伴,但是前提新希望你已经仔细阅读了wiki和搜索了issues。
 4. 你可以请作者为你解答,但是我不是免费的。
-5. 你可以把遇到问题的设备寄给我,可以更容易的复现问题。
+5. 你可以把遇到问题的设备寄给我,可以更容易的兼容设备和解决问题。
 
 # 使用帮助
 QQ群: 901799015, ZLM使用文档[https://github.com/ZLMediaKit/ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit)  

+ 1 - 1
doc/_content/ability/gis.md

@@ -14,7 +14,7 @@ WVP提供了简单的电子地图用于设备的定位以及移动设备的轨
 PS: 目前的底图仅用用作演示和学习,商用情况请自行购买授权使用。
 
 ### 更换底图以及底图配置
-目前WVP支持使用了更换底图,配置文件在web_src/static/js/mapConfig.js,请修改后重新编译前端文件。
+目前WVP支持使用了更换底图,配置文件在web_src/static/js/config.js,请修改后重新编译前端文件。
 ```javascript
 window.mapParam = {
   // 开启/关闭地图功能

+ 21 - 2
doc/_content/introduction/deployment.md

@@ -27,13 +27,32 @@
 ```shell
 nohup java -jar wvp-pro-*.jar &
 ```
-war包:  
+**war包:**  
 下载Tomcat后将war包放入webapps中,启动Tomcat以解压war包,停止Tomcat后,删除ROOT目录以及war包,将解压后的war包目录重命名为ROOT,将配置文件中的Server.port配置为与Tomcat端口一致
 然后启动Tomcat。  
 **启动ZLM**
 ```shell
 nohup ./MediaServer -d -m 3 &
 ```
-
+### 前后端分离部署
+前后端部署目前在最新的版本已经支持,请使用3月15日之后的版本部署
+前端编译后的文件在`src/main/resources/static`中,将此目录下的文件部署。
+前后端分离部署最大的问题是跨域的解决,之前版本使用cookie完成登录流程,而cookie是不可以在复杂跨域中使用的。所以当前版本使用JWT生成的TOKEN作为认证凭据,
+部署前端后需要在wvp中配置前端访问的地址以完成跨域流程。   
+**配置前端服务器**
+1. 假如你的服务有公网域名为xxx.com,公网IP为11.11.11.11, 那么你可以在wvp中这样配置:
+```yaml
+user-settings:
+  # 跨域配置,配置你访问前端页面的地址即可, 可以配置多个
+  allowed-origins:
+    - http://xxx.com:8008
+    - http://11.11.11.11:8008
+```
+配置不是必须的,你使用哪个ip/域名访问就配置哪个即可。修改配置后重启wvp以使配置生效。
+2. 在`src/main/resources/static/static/js/config.js`下配置服务器的地址,也就是wvp服务的地址
+```javascript
+window.baseUrl = "http://xxx.com:18080"
+```
+`这里的地址是需要客户电脑能访问到的,因为请求是客户端电脑发起,与代理不同`  
 [接入设备](./_content/ability/device.md)
 

+ 14 - 11
pom.xml

@@ -11,7 +11,7 @@
 
 	<groupId>com.genersoft</groupId>
 	<artifactId>wvp-pro</artifactId>
-	<version>2.6.7</version>
+	<version>2.6.8</version>
 	<name>web video platform</name>
 	<description>国标28181视频平台</description>
 	<packaging>${project.packaging}</packaging>
@@ -123,11 +123,9 @@
 			<artifactId>spring-boot-starter-security</artifactId>
 		</dependency>
 
-		<!-- druid数据库连接池 -->
 		<dependency>
-			<groupId>com.alibaba</groupId>
-			<artifactId>druid-spring-boot-starter</artifactId>
-			<version>1.2.11</version>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-jdbc</artifactId>
 		</dependency>
 
 		<!-- mysql数据库 -->
@@ -216,8 +214,6 @@
 			<version>4.10.0</version>
 		</dependency>
 
-
-
 		<!-- okhttp-digest -->
 		<dependency>
 			<groupId>io.github.rburgst</groupId>
@@ -226,10 +222,17 @@
 		</dependency>
 
 		<!-- https://mvnrepository.com/artifact/net.sf.kxml/kxml2 -->
+<!--		<dependency>-->
+<!--			<groupId>net.sf.kxml</groupId>-->
+<!--			<artifactId>kxml2</artifactId>-->
+<!--			<version>2.3.0</version>-->
+<!--		</dependency>-->
+
+		<!-- jwt实现 -->
 		<dependency>
-			<groupId>net.sf.kxml</groupId>
-			<artifactId>kxml2</artifactId>
-			<version>2.3.0</version>
+			<groupId>org.bitbucket.b_c</groupId>
+			<artifactId>jose4j</artifactId>
+			<version>0.9.3</version>
 		</dependency>
 
 		<!--反向代理-->
@@ -289,7 +292,7 @@
 			<plugin>
 				<groupId>org.springframework.boot</groupId>
 				<artifactId>spring-boot-maven-plugin</artifactId>
-				<version>2.3.5.RELEASE</version>
+				<version>2.7.2</version>
 				<configuration>
 					<includeSystemScope>true</includeSystemScope>
 				</configuration>

+ 12 - 0
sql/2.6.6-2.6.7更新.sql

@@ -0,0 +1,12 @@
+alter table device
+    add asMessageChannel int default 0;
+
+alter table parent_platform
+    add asMessageChannel int default 0;
+
+alter table device
+    add mediaServerId varchar(50) default null;
+
+
+
+

+ 3 - 0
src/main/resources/db/migration/V2.6.7_20230201__初始化.sql

@@ -32,6 +32,7 @@ CREATE TABLE `device` (
                           `transport` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
                           `streamMode` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
                           `online` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
+                          `mediaServerId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
                           `registerTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
                           `keepaliveTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
                           `ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
@@ -47,6 +48,7 @@ CREATE TABLE `device` (
                           `mobilePositionSubmissionInterval` int DEFAULT '5',
                           `subscribeCycleForAlarm` int DEFAULT NULL,
                           `ssrcCheck` int DEFAULT '0',
+                          `asMessageChannel` int DEFAULT '0',
                           `geoCoordSys` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
                           `treeType` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
                           `custom_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
@@ -329,6 +331,7 @@ CREATE TABLE `parent_platform` (
                                    `catalogId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
                                    `ptz` int DEFAULT NULL,
                                    `rtcp` int DEFAULT NULL,
+                                   `asMessageChannel` int DEFAULT '0',
                                    `status` bit(1) DEFAULT NULL,
                                    `startOfflinePush` int DEFAULT '0',
                                    `administrativeDivision` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,

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

@@ -1,6 +1,5 @@
 package com.genersoft.iot.vmp;
 
-import com.genersoft.iot.vmp.conf.druid.EnableDruidSupport;
 import com.genersoft.iot.vmp.utils.GitUtil;
 import com.genersoft.iot.vmp.utils.SpringBeanFactory;
 import org.slf4j.Logger;
@@ -25,7 +24,6 @@ import java.util.Collections;
 @ServletComponentScan("com.genersoft.iot.vmp.conf")
 @SpringBootApplication
 @EnableScheduling
-@EnableDruidSupport
 public class VManageBootstrap extends SpringBootServletInitializer {
 
 	private final static Logger logger = LoggerFactory.getLogger(VManageBootstrap.class);

+ 8 - 4
src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java

@@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.common;
 import io.swagger.v3.oas.annotations.media.Schema;
 
 import java.io.Serializable;
+import java.util.Objects;
 
 @Schema(description = "流信息")
 public class StreamInfo implements Serializable, Cloneable{
@@ -168,7 +169,7 @@ public class StreamInfo implements Serializable, Cloneable{
     }
 
     public void setRtmp(String host, int port, int sslPort, String app, String stream, String callIdParam) {
-        String file = String.format("%s/%s/%s", app, stream, callIdParam);
+        String file = String.format("%s/%s%s", app, stream, callIdParam);
         if (port > 0) {
             this.rtmp = new StreamURL("rtmp", host, port, file);
         }
@@ -178,7 +179,7 @@ public class StreamInfo implements Serializable, Cloneable{
     }
 
     public void setRtsp(String host, int port, int sslPort, String app, String stream, String callIdParam) {
-        String file = String.format("%s/%s/%s", app, stream, callIdParam);
+        String file = String.format("%s/%s%s", app, stream, callIdParam);
         if (port > 0) {
             this.rtsp = new StreamURL("rtsp", host, port, file);
         }
@@ -236,8 +237,11 @@ public class StreamInfo implements Serializable, Cloneable{
         }
     }
 
-    public void setRtc(String host, int port, int sslPort, String app, String stream, String callIdParam, boolean isPlay) {
-        String file = String.format("index/api/webrtc?app=%s&stream=%s&type=%s%s", app, stream, isPlay?"play":"push", callIdParam);
+    public void setRtc(String host, int port, int sslPort, String app, String stream, String callIdParam) {
+        if (callIdParam != null) {
+            callIdParam = Objects.equals(callIdParam, "") ? callIdParam : callIdParam.replace("?", "&");
+        }
+        String file = String.format("index/api/webrtc?app=%s&stream=%s&type=play%s", app, stream, callIdParam);
         if (port > 0) {
             this.rtc = new StreamURL("http", host, port, file);
         }

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

@@ -66,9 +66,7 @@ public class ApiAccessFilter extends OncePerRequestFilter {
             logDto.setUri(servletRequest.getRequestURI());
             logDto.setCreateTime(DateUtil.getNow());
             logService.add(logDto);
-//            logger.warn("[Api Access]  [{}] [{}] [{}] [{}] [{}] {}ms",
-//                    uriName, servletRequest.getMethod(), servletRequest.getRequestURI(), servletRequest.getRemoteAddr(), HttpStatus.valueOf(servletResponse.getStatus()),
-//                    System.currentTimeMillis() - start);
+
 
         }
     }

+ 23 - 0
src/main/java/com/genersoft/iot/vmp/conf/GlobalExceptionHandler.java

@@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.ResponseStatus;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
@@ -32,6 +33,28 @@ public class GlobalExceptionHandler {
         return WVPResult.fail(ErrorCode.ERROR500.getCode(), e.getMessage());
     }
 
+    /**
+     * 默认异常处理
+     * @param e 异常
+     * @return 统一返回结果
+     */
+    @ExceptionHandler(IllegalStateException.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    public WVPResult<String> exceptionHandler(IllegalStateException e) {
+        return WVPResult.fail(ErrorCode.ERROR400);
+    }
+
+    /**
+     * 默认异常处理
+     * @param e 异常
+     * @return 统一返回结果
+     */
+    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    public WVPResult<String> exceptionHandler(HttpRequestMethodNotSupportedException e) {
+        return WVPResult.fail(ErrorCode.ERROR400);
+    }
+
 
     /**
      * 自定义异常处理, 处理controller中返回的错误

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

@@ -10,14 +10,11 @@ import org.springframework.context.annotation.Bean;
 import org.springframework.core.MethodParameter;
 import org.springframework.http.MediaType;
 import org.springframework.http.converter.HttpMessageConverter;
-import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
 import org.springframework.http.server.ServerHttpRequest;
 import org.springframework.http.server.ServerHttpResponse;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
 import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
 
-import java.util.List;
-
 /**
  * 全局统一返回结果
  * @author lin

+ 15 - 7
src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java

@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.conf;
 
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.IMediaServerService;
+import org.apache.catalina.connector.ClientAbortException;
 import org.apache.http.HttpHost;
 import org.apache.http.HttpRequest;
 import org.apache.http.HttpResponse;
@@ -169,13 +170,14 @@ public class ProxyServletConfig {
         protected String rewriteQueryStringFromRequest(HttpServletRequest servletRequest, String queryString) {
             String queryStr = super.rewriteQueryStringFromRequest(servletRequest, queryString);
             MediaServerItem mediaInfo = getMediaInfoByUri(servletRequest.getRequestURI());
-            String remoteHost = String.format("http://%s:%s", mediaInfo.getIp(), mediaInfo.getHttpPort());
-            if (mediaInfo != null) {
-                if (!ObjectUtils.isEmpty(queryStr)) {
-                    queryStr += "&remoteHost=" + remoteHost;
-                }else {
-                    queryStr = "remoteHost=" + remoteHost;
-                }
+            if (mediaInfo == null) {
+                return null;
+            }
+            String remoteHost = String.format("http://%s:%s", mediaInfo.getStreamIp(), mediaInfo.getRecordAssistPort());
+            if (!ObjectUtils.isEmpty(queryStr)) {
+                queryStr += "&remoteHost=" + remoteHost;
+            }else {
+                queryStr = "remoteHost=" + remoteHost;
             }
             return queryStr;
         }
@@ -192,6 +194,12 @@ public class ProxyServletConfig {
             } catch (IOException ioException) {
                 if (ioException instanceof ConnectException) {
                     logger.error("录像服务 连接失败");
+                }else if (ioException instanceof ClientAbortException) {
+                    /**
+                     * TODO 使用这个代理库实现代理在遇到代理视频文件时,如果是206结果,会遇到报错蛋市目前功能正常,
+                     * TODO 暂时去除异常处理。后续使用其他代理框架修改测试
+                     */
+
                 }else {
                     logger.error("录像服务 代理失败: ", e);
                 }

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

@@ -4,7 +4,6 @@ package com.genersoft.iot.vmp.conf;
 import org.junit.jupiter.api.Order;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
-import org.springframework.util.ObjectUtils;
 
 @Component
 @ConfigurationProperties(prefix = "sip", ignoreInvalidFields = true)
@@ -13,6 +12,8 @@ public class SipConfig {
 
 	private String ip;
 
+	private String showIp;
+
 	private Integer port;
 
 	private String domain;
@@ -96,4 +97,14 @@ public class SipConfig {
 		this.alarm = alarm;
 	}
 
+	public String getShowIp() {
+		if (this.showIp == null) {
+			return this.ip;
+		}
+		return showIp;
+	}
+
+	public void setShowIp(String showIp) {
+		this.showIp = showIp;
+	}
 }

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

@@ -40,17 +40,20 @@ public class SipPlatformRunner implements CommandLineRunner {
         List<ParentPlatform> parentPlatforms = storager.queryEnableParentPlatformList(true);
 
         for (ParentPlatform parentPlatform : parentPlatforms) {
+
+            ParentPlatformCatch parentPlatformCatchOld = redisCatchStorage.queryPlatformCatchInfo(parentPlatform.getServerGBId());
+
             // 更新缓存
             ParentPlatformCatch parentPlatformCatch = new ParentPlatformCatch();
             parentPlatformCatch.setParentPlatform(parentPlatform);
             parentPlatformCatch.setId(parentPlatform.getServerGBId());
             redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
-            // 设置所有平台离线
-            platformService.offline(parentPlatform, true);
             // 取消订阅
-            sipCommanderForPlatform.unregister(parentPlatform, null, (eventResult)->{
+            sipCommanderForPlatform.unregister(parentPlatform, parentPlatformCatchOld.getSipTransactionInfo(), null, (eventResult)->{
                 platformService.login(parentPlatform);
             });
+            // 设置所有平台离线
+            platformService.offline(parentPlatform, true);
         }
     }
 }

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

@@ -50,15 +50,22 @@ public class UserSetting {
     private Boolean pushStreamAfterAck = Boolean.FALSE;
 
     private Boolean sipLog = Boolean.FALSE;
+    private Boolean sendToPlatformsWhenIdLost = Boolean.FALSE;
+
+    private Boolean refuseChannelStatusChannelFormNotify = Boolean.FALSE;
 
     private String serverId = "000000";
 
+    private String recordPath = null;
+
     private String thirdPartyGBIdReg = "[\\s\\S]*";
 
     private String broadcastForPlatform = "UDP";
 
     private List<String> interfaceAuthenticationExcludes = new ArrayList<>();
 
+    private List<String> allowedOrigins = new ArrayList<>();
+
     public Boolean getSavePositionHistory() {
         return savePositionHistory;
     }
@@ -238,4 +245,36 @@ public class UserSetting {
     public void setSipLog(Boolean sipLog) {
         this.sipLog = sipLog;
     }
+
+    public List<String> getAllowedOrigins() {
+        return allowedOrigins;
+    }
+
+    public void setAllowedOrigins(List<String> allowedOrigins) {
+        this.allowedOrigins = allowedOrigins;
+    }
+
+    public Boolean getSendToPlatformsWhenIdLost() {
+        return sendToPlatformsWhenIdLost;
+    }
+
+    public void setSendToPlatformsWhenIdLost(Boolean sendToPlatformsWhenIdLost) {
+        this.sendToPlatformsWhenIdLost = sendToPlatformsWhenIdLost;
+    }
+
+    public Boolean getRefuseChannelStatusChannelFormNotify() {
+        return refuseChannelStatusChannelFormNotify;
+    }
+
+    public void setRefuseChannelStatusChannelFormNotify(Boolean refuseChannelStatusChannelFormNotify) {
+        this.refuseChannelStatusChannelFormNotify = refuseChannelStatusChannelFormNotify;
+    }
+
+    public String getRecordPath() {
+        return recordPath;
+    }
+
+    public void setRecordPath(String recordPath) {
+        this.recordPath = recordPath;
+    }
 }

+ 0 - 64
src/main/java/com/genersoft/iot/vmp/conf/druid/DruidConfiguration.java

@@ -1,64 +0,0 @@
-package com.genersoft.iot.vmp.conf.druid;
-
-import com.alibaba.druid.support.http.StatViewServlet;
-import com.alibaba.druid.support.http.WebStatFilter;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.web.servlet.FilterRegistrationBean;
-import org.springframework.boot.web.servlet.ServletRegistrationBean;
-import org.springframework.context.annotation.Bean;
-
-import javax.servlet.Filter;
-import javax.servlet.Servlet;
-
-/**
- * druid监控配置
- * @author
- */
-public class DruidConfiguration  {
-
-    @Value("${rj-druid-manage.allow:127.0.0.1}")
-    private String allow;
-
-    @Value("${rj-druid-manage.deny:}")
-    private String deny;
-
-    @Value("${rj-druid-manage.loginUsername:admin}")
-    private String loginUsername;
-
-    @Value("${rj-druid-manage.loginPassword:admin}")
-    private String loginPassword;
-
-    @Value("${rj-druid-manage.resetEnable:false}")
-    private String resetEnable;
-
-    /**
-     * druid监控页面开启
-     */
-    @Bean
-    public ServletRegistrationBean druidServlet() {
-        ServletRegistrationBean<Servlet> servletRegistrationBean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
-        // IP白名单
-        servletRegistrationBean.addInitParameter("allow", allow);
-        // IP黑名单(共同存在时,deny优先于allow)
-        servletRegistrationBean.addInitParameter("deny", deny);
-        //控制台管理用户
-        servletRegistrationBean.addInitParameter("loginUsername", loginUsername);
-        servletRegistrationBean.addInitParameter("loginPassword", loginPassword);
-        //是否能够重置数据 禁用HTML页面上的“Reset All”功能
-        servletRegistrationBean.addInitParameter("resetEnable", resetEnable);
-        return servletRegistrationBean;
-    }
-
-    /**
-     * druid url监控配置
-     */
-    @Bean
-    public FilterRegistrationBean filterRegistrationBean() {
-        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>(new WebStatFilter());
-        filterRegistrationBean.addUrlPatterns("/*");
-        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
-        return filterRegistrationBean;
-    }
-
-
-}

+ 0 - 24
src/main/java/com/genersoft/iot/vmp/conf/druid/EnableDruidSupport.java

@@ -1,24 +0,0 @@
-package com.genersoft.iot.vmp.conf.druid;
-
-import org.springframework.boot.web.servlet.ServletComponentScan;
-import org.springframework.context.annotation.Import;
-
-import java.lang.annotation.*;
-
-/**
- * druid监控支持注解
- *
- * @author
- * {@link DruidConfiguration} druid监控页面安全配置支持
- * {@link ServletComponentScan} druid监控页面需要扫描servlet
- */
-@Target(ElementType.TYPE)
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-@Inherited
-@Import({
-        DruidConfiguration.class,
-})
-@ServletComponentScan
-public @interface EnableDruidSupport {
-}

+ 9 - 11
src/main/java/com/genersoft/iot/vmp/conf/security/AnonymousAuthenticationEntryPoint.java

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

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

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

+ 0 - 24
src/main/java/com/genersoft/iot/vmp/conf/security/InvalidSessionHandler.java

@@ -1,24 +0,0 @@
-package com.genersoft.iot.vmp.conf.security;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.security.web.session.InvalidSessionStrategy;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-
-/**
- * 登录超时的处理
- */
-public class InvalidSessionHandler implements InvalidSessionStrategy {
-
-    private final static Logger logger = LoggerFactory.getLogger(InvalidSessionHandler.class);
-
-    @Override
-    public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse httpServletResponse) throws IOException, ServletException {
-        String username = request.getParameter("username");
-        logger.info("[登录超时] - [{}]", username);
-    }
-}

+ 84 - 0
src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java

@@ -0,0 +1,84 @@
+package com.genersoft.iot.vmp.conf.security;
+
+import com.genersoft.iot.vmp.conf.UserSetting;
+import com.genersoft.iot.vmp.conf.security.dto.JwtUser;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * jwt token 过滤器
+ */
+
+@Component
+public class JwtAuthenticationFilter extends OncePerRequestFilter {
+
+
+    @Autowired
+    private UserSetting userSetting;
+
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
+
+        // 忽略登录请求的token验证
+        String requestURI = request.getRequestURI();
+        if (requestURI.equalsIgnoreCase("/api/user/login")) {
+            chain.doFilter(request, response);
+            return;
+        }
+        if (!userSetting.isInterfaceAuthentication()) {
+            // 构建UsernamePasswordAuthenticationToken,这里密码为null,是因为提供了正确的JWT,实现自动登录
+            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(null, null, new ArrayList<>() );
+            SecurityContextHolder.getContext().setAuthentication(token);
+            chain.doFilter(request, response);
+            return;
+        }
+        String jwt = request.getHeader(JwtUtils.getHeader());
+        // 这里如果没有jwt,继续往后走,因为后面还有鉴权管理器等去判断是否拥有身份凭证,所以是可以放行的
+        // 没有jwt相当于匿名访问,若有一些接口是需要权限的,则不能访问这些接口
+        if (StringUtils.isBlank(jwt)) {
+            jwt = request.getParameter(JwtUtils.getHeader());
+            if (StringUtils.isBlank(jwt)) {
+                chain.doFilter(request, response);
+                return;
+            }
+        }
+
+        JwtUser jwtUser = JwtUtils.verifyToken(jwt);
+        String username = jwtUser.getUserName();
+        // TODO 处理各个状态
+        switch (jwtUser.getStatus()){
+            case EXPIRED:
+                response.setStatus(400);
+                chain.doFilter(request, response);
+                // 异常
+                return;
+            case EXCEPTION:
+                // 过期
+                response.setStatus(400);
+                chain.doFilter(request, response);
+                return;
+            case EXPIRING_SOON:
+                // 即将过期
+//                return;
+            default:
+        }
+
+        // 构建UsernamePasswordAuthenticationToken,这里密码为null,是因为提供了正确的JWT,实现自动登录
+        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, jwtUser.getPassword(), new ArrayList<>() );
+        SecurityContextHolder.getContext().setAuthentication(token);
+        chain.doFilter(request, response);
+    }
+
+}

Разница между файлами не показана из-за своего большого размера
+ 138 - 0
src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java


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

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

+ 13 - 3
src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java

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

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

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

+ 53 - 0
src/main/java/com/genersoft/iot/vmp/conf/security/dto/JwtUser.java

@@ -0,0 +1,53 @@
+package com.genersoft.iot.vmp.conf.security.dto;
+
+public class JwtUser {
+
+    public enum TokenStatus{
+        /**
+         * 正常的使用状态
+         */
+        NORMAL,
+        /**
+         * 过期而失效
+         */
+        EXPIRED,
+        /**
+         * 即将过期
+         */
+        EXPIRING_SOON,
+        /**
+         * 异常
+         */
+        EXCEPTION
+    }
+
+    private String userName;
+
+    private String password;
+
+    private TokenStatus status;
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    public TokenStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(TokenStatus status) {
+        this.status = status;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+}

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

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

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

@@ -171,7 +171,7 @@ public class DigestServerAuthenticationHelper  {
      */
     public boolean doAuthenticatePlainTextPassword(Request request, String pass) {
         AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
-        if ( authHeader == null ) {
+        if ( authHeader == null || authHeader.getRealm() == null) {
             return false;
         }
         String realm = authHeader.getRealm().trim();

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

@@ -188,6 +188,13 @@ public class Device {
 	@Schema(description = "SIP交互IP(设备访问平台的IP)")
 	private String localIp;
 
+	@Schema(description = "是否作为消息通道")
+	private boolean asMessageChannel;
+
+	@Schema(description = "设备注册的事务信息")
+	private SipTransactionInfo sipTransactionInfo;
+
+
 	public String getDeviceId() {
 		return deviceId;
 	}
@@ -428,4 +435,20 @@ public class Device {
 	public void setKeepaliveIntervalTime(int keepaliveIntervalTime) {
 		this.keepaliveIntervalTime = keepaliveIntervalTime;
 	}
+
+	public boolean isAsMessageChannel() {
+		return asMessageChannel;
+	}
+
+	public void setAsMessageChannel(boolean asMessageChannel) {
+		this.asMessageChannel = asMessageChannel;
+	}
+
+	public SipTransactionInfo getSipTransactionInfo() {
+		return sipTransactionInfo;
+	}
+
+	public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) {
+		this.sipTransactionInfo = sipTransactionInfo;
+	}
 }

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

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

+ 10 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatformCatch.java

@@ -16,6 +16,8 @@ public class ParentPlatformCatch {
 
     private ParentPlatform parentPlatform;
 
+    private SipTransactionInfo sipTransactionInfo;
+
     public String getId() {
         return id;
     }
@@ -55,4 +57,12 @@ public class ParentPlatformCatch {
     public void setCallId(String callId) {
         this.callId = callId;
     }
+
+    public SipTransactionInfo getSipTransactionInfo() {
+        return sipTransactionInfo;
+    }
+
+    public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) {
+        this.sipTransactionInfo = sipTransactionInfo;
+    }
 }

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

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

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

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

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

@@ -1,7 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.event.device;
 
 import com.genersoft.iot.vmp.gb28181.bean.Device;
-import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.service.IDeviceService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationListener;
@@ -9,8 +8,6 @@ import org.springframework.stereotype.Component;
 
 import javax.sip.ClientTransaction;
 import javax.sip.address.SipURI;
-import javax.sip.header.CallIdHeader;
-import javax.sip.header.ToHeader;
 import javax.sip.message.Request;
 
 /**
@@ -34,7 +31,7 @@ public class RequestTimeoutEventImpl implements ApplicationListener<RequestTimeo
                 if (device == null) {
                     return;
                 }
-                deviceService.offline(device.getDeviceId());
+                deviceService.offline(device.getDeviceId(), "等待消息超时");
             }
 
         }

+ 2 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/task/SipRunner.java

@@ -61,9 +61,9 @@ public class SipRunner implements CommandLineRunner {
 
         for (Device device : deviceList) {
             if (deviceService.expire(device)){
-                deviceService.offline(device.getDeviceId());
+                deviceService.offline(device.getDeviceId(), "注册已过期");
             }else {
-                deviceService.online(device);
+                deviceService.online(device, null);
             }
         }
         // 重置cseq计数

+ 1 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java

@@ -120,7 +120,7 @@ public interface ISIPCommander {
 	 */ 
 	void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
 						   String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
-						   SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+						   SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
 
 
 	/**
@@ -217,7 +217,6 @@ public interface ISIPCommander {
 	 *
 	 * @param device      视频设备
 	 * @param channelId      通道id,非通道则是设备本身
-	 * @param frontCmd     上级平台的指令,如果存在则直接下发
 	 * @param enabled     看守位使能:1 = 开启,0 = 关闭
 	 * @param resetTime   自动归位时间间隔,开启看守位时使用,单位:秒(s)
 	 * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255

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

@@ -22,12 +22,10 @@ public interface ISIPCommanderForPlatform {
      * @param parentPlatform
      * @return
      */
-    void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent)
-            throws InvalidArgumentException, ParseException, SipException;
+    void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException;
 
-    void register(ParentPlatform parentPlatform, String callId, WWWAuthenticateHeader www,
-                  SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister)
-            throws SipException, InvalidArgumentException, ParseException;
+    void register(ParentPlatform parentPlatform, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException;
+    void register(ParentPlatform parentPlatform, SipTransactionInfo sipTransactionInfo, WWWAuthenticateHeader www, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister) throws SipException, InvalidArgumentException, ParseException;
 
     /**
      * 向上级平台注销
@@ -35,8 +33,7 @@ public interface ISIPCommanderForPlatform {
      * @param parentPlatform
      * @return
      */
-    void unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent)
-            throws InvalidArgumentException, ParseException, SipException;
+    void unregister(ParentPlatform parentPlatform, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException;
 
 
     /**

+ 14 - 9
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java

@@ -46,7 +46,7 @@ public class SIPRequestHeaderPlarformProvider {
 	@Autowired
 	private IRedisCatchStorage redisCatchStorage;
 
-	public Request createRegisterRequest(@NotNull ParentPlatform parentPlatform, long CSeq, String fromTag, String viaTag, CallIdHeader callIdHeader, boolean isRegister) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+	public Request createRegisterRequest(@NotNull ParentPlatform parentPlatform, long CSeq, String fromTag, String toTag, CallIdHeader callIdHeader, boolean isRegister) throws ParseException, InvalidArgumentException, PeerUnavailableException {
 		Request request = null;
 		String sipAddress = parentPlatform.getDeviceIp() + ":" + parentPlatform.getDevicePort();
 		//请求行
@@ -54,7 +54,8 @@ public class SIPRequestHeaderPlarformProvider {
 				parentPlatform.getServerIP() + ":" + parentPlatform.getServerPort());
 		//via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getServerIP(), parentPlatform.getServerPort(), parentPlatform.getTransport(), viaTag);
+		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getServerIP(),
+				parentPlatform.getServerPort(), parentPlatform.getTransport(), SipUtils.getNewViaTag());
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
 		//from
@@ -64,7 +65,7 @@ public class SIPRequestHeaderPlarformProvider {
 		//to
 		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain());
 		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,null);
+		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,toTag);
 
 		//Forwards
 		MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
@@ -86,15 +87,21 @@ public class SIPRequestHeaderPlarformProvider {
 		return request;
 	}
 
-	public Request createRegisterRequest(@NotNull ParentPlatform parentPlatform, String fromTag, String viaTag,
-										 String callId, WWWAuthenticateHeader www , CallIdHeader callIdHeader, boolean isRegister) throws ParseException, PeerUnavailableException, InvalidArgumentException {
+	public Request createRegisterRequest(@NotNull ParentPlatform parentPlatform, String fromTag, String toTag,
+										 WWWAuthenticateHeader www , CallIdHeader callIdHeader, boolean isRegister) throws ParseException, PeerUnavailableException, InvalidArgumentException {
 
 
-		Request registerRequest = createRegisterRequest(parentPlatform, redisCatchStorage.getCSEQ(), fromTag, viaTag, callIdHeader, isRegister);
+		Request registerRequest = createRegisterRequest(parentPlatform, redisCatchStorage.getCSEQ(), fromTag, toTag, callIdHeader, isRegister);
 		SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP() + ":" + parentPlatform.getServerPort());
 		if (www == null) {
 			AuthorizationHeader authorizationHeader = sipLayer.getSipFactory().createHeaderFactory().createAuthorizationHeader("Digest");
-			authorizationHeader.setUsername(parentPlatform.getDeviceGBId());
+			String username = parentPlatform.getUsername();
+			if ( username == null || username == "" )
+			{
+				authorizationHeader.setUsername(parentPlatform.getDeviceGBId());
+			} else {
+				authorizationHeader.setUsername(username);
+			}
 			authorizationHeader.setURI(requestURI);
 			authorizationHeader.setAlgorithm("MD5");
 			registerRequest.addHeader(authorizationHeader);
@@ -108,8 +115,6 @@ public class SIPRequestHeaderPlarformProvider {
 		// qop 保护质量 包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略
 		String qop = www.getQop();
 
-		callIdHeader.setCallId(callId);
-
 		String cNonce = null;
 		String nc = "00000001";
 		if (qop != null) {

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

@@ -276,7 +276,7 @@ public class SIPCommander implements ISIPCommander {
             return;
         }
 
-        logger.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
+        logger.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getSdpIp(), ssrcInfo.getPort());
         HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
         subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
             if (event != null) {
@@ -377,7 +377,7 @@ public class SIPCommander implements ISIPCommander {
                                   SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
 
 
-        logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
+        logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getSdpIp(), mediaServerItem.getIp(), ssrcInfo.getPort());
         String sdpIp;
         if (!ObjectUtils.isEmpty(device.getSdpIp())) {
             sdpIp = device.getSdpIp();
@@ -479,10 +479,11 @@ public class SIPCommander implements ISIPCommander {
      */
     @Override
     public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
-                                  String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
-                                  SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+                                  String startTime, String endTime, int downloadSpeed,
+                                  InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
+                                  SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
 
-        logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
+        logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getSdpIp(), mediaServerItem.getIp(), ssrcInfo.getPort());
         String sdpIp;
         if (!ObjectUtils.isEmpty(device.getSdpIp())) {
             sdpIp = device.getSdpIp();
@@ -549,11 +550,14 @@ public class SIPCommander implements ISIPCommander {
         content.append("a=downloadspeed:" + downloadSpeed + "\r\n");
 
         content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
-
+        logger.debug("此时请求下载信令的ssrc===>{}",ssrcInfo.getSsrc());
         HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, null, mediaServerItem.getId());
         // 添加订阅
+        CallIdHeader newCallIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport());
+        String callId=newCallIdHeader.getCallId();
         subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
-            hookEvent.call(new InviteStreamInfo(mediaServerItem, json,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId(), "rtp", ssrcInfo.getStream()));
+            logger.debug("sipc 添加订阅===callId {}",callId);
+            hookEvent.call(new InviteStreamInfo(mediaServerItem, json,callId, "rtp", ssrcInfo.getStream()));
             subscribe.removeSubscribe(hookSubscribe);
             hookSubscribe.getContent().put("regist", false);
             hookSubscribe.getContent().put("schema", "rtsp");
@@ -562,7 +566,7 @@ public class SIPCommander implements ISIPCommander {
                     (MediaServerItem mediaServerItemForEnd, JSONObject jsonForEnd) -> {
                         logger.info("[录像]下载结束, 发送BYE");
                         try {
-                            streamByeCmd(device, channelId, ssrcInfo.getStream(),sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId());
+                            streamByeCmd(device, channelId, ssrcInfo.getStream(),callId);
                         } catch (InvalidArgumentException | ParseException | SipException |
                                  SsrcTransactionNotFoundException e) {
                             logger.error("[录像]下载结束, 发送BYE失败 {}", e.getMessage());
@@ -570,15 +574,24 @@ public class SIPCommander implements ISIPCommander {
                     });
         });
 
-        Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()), ssrcInfo.getSsrc());
+        Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null,newCallIdHeader, ssrcInfo.getSsrc());
         if (inviteStreamCallback != null) {
-            inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId(), "rtp", ssrcInfo.getStream()));
+            inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null,callId, "rtp", ssrcInfo.getStream()));
         }
 
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent -> {
-            ResponseEvent responseEvent = (ResponseEvent) okEvent.event;
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, event -> {
+            ResponseEvent responseEvent = (ResponseEvent) event.event;
             SIPResponse response = (SIPResponse) responseEvent.getResponse();
-            streamSession.put(device.getDeviceId(), channelId, response.getCallIdHeader().getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.download);
+            String contentString =new String(response.getRawContent());
+            int ssrcIndex = contentString.indexOf("y=");
+            String ssrc=ssrcInfo.getSsrc();
+            if (ssrcIndex >= 0) {
+                ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
+            }
+            logger.debug("接收到的下载响应ssrc====>{}",ssrcInfo.getSsrc());
+            logger.debug("接收到的下载响应ssrc====>{}",ssrc);
+            streamSession.put(device.getDeviceId(), channelId, response.getCallIdHeader().getCallId(), ssrcInfo.getStream(), ssrc, mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.download);
+            okEvent.response(event);
         });
     }
 
@@ -778,7 +791,7 @@ public class SIPCommander implements ISIPCommander {
         cmdXml.append("<GuardCmd>" + guardCmdStr + "</GuardCmd>\r\n");
         cmdXml.append("</Control>\r\n");
 
-        
+
 
         Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
         sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
@@ -854,7 +867,6 @@ public class SIPCommander implements ISIPCommander {
      *
      * @param device      视频设备
      * @param channelId      通道id,非通道则是设备本身
-     * @param frontCmd     上级平台的指令,如果存在则直接下发
      * @param enabled     看守位使能:1 = 开启,0 = 关闭
      * @param resetTime   自动归位时间间隔,开启看守位时使用,单位:秒(s)
      * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255
@@ -978,7 +990,7 @@ public class SIPCommander implements ISIPCommander {
         catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
         catalogXml.append("</Query>\r\n");
 
-
+        
 
         Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
 
@@ -1181,7 +1193,6 @@ public class SIPCommander implements ISIPCommander {
         cmdXml.append("</Query>\r\n");
 
 
-
         Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
         sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
     }
@@ -1427,7 +1438,7 @@ public class SIPCommander implements ISIPCommander {
         if (device == null) {
             return;
         }
-        logger.info("[发送 报警通知] {}/{}->{},{}", device.getDeviceId(), deviceAlarm.getChannelId(),
+        logger.info("[发送报警通知]设备: {}/{}->{},{}", device.getDeviceId(), deviceAlarm.getChannelId(),
                 deviceAlarm.getLongitude(), deviceAlarm.getLatitude());
 
         String characterSet = device.getCharset();
@@ -1439,7 +1450,7 @@ public class SIPCommander implements ISIPCommander {
         deviceStatusXml.append("<DeviceID>" + deviceAlarm.getChannelId() + "</DeviceID>\r\n");
         deviceStatusXml.append("<AlarmPriority>" + deviceAlarm.getAlarmPriority() + "</AlarmPriority>\r\n");
         deviceStatusXml.append("<AlarmMethod>" + deviceAlarm.getAlarmMethod() + "</AlarmMethod>\r\n");
-        deviceStatusXml.append("<AlarmTime>" + deviceAlarm.getAlarmTime() + "</AlarmTime>\r\n");
+        deviceStatusXml.append("<AlarmTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(deviceAlarm.getAlarmTime()) + "</AlarmTime>\r\n");
         deviceStatusXml.append("<AlarmDescription>" + deviceAlarm.getAlarmDescription() + "</AlarmDescription>\r\n");
         deviceStatusXml.append("<Longitude>" + deviceAlarm.getLongitude() + "</Longitude>\r\n");
         deviceStatusXml.append("<Latitude>" + deviceAlarm.getLatitude() + "</Latitude>\r\n");

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

@@ -24,6 +24,7 @@ import com.genersoft.iot.vmp.service.bean.SSRCInfo;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
 import com.genersoft.iot.vmp.utils.DateUtil;
+import com.genersoft.iot.vmp.utils.GitUtil;
 import gov.nist.javax.sip.message.MessageFactoryImpl;
 import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.message.SIPResponse;
@@ -85,26 +86,49 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
     @Autowired
     private DynamicTask dynamicTask;
 
+    @Autowired
+    private GitUtil gitUtil;
+
     @Override
     public void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException {
         register(parentPlatform, null, null, errorEvent, okEvent, false, true);
     }
 
     @Override
-    public void unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException {
-        register(parentPlatform, null, null, errorEvent, okEvent, false, false);
+    public void register(ParentPlatform parentPlatform, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException {
+
+        register(parentPlatform, sipTransactionInfo, null, errorEvent, okEvent, false, true);
     }
 
     @Override
-    public void register(ParentPlatform parentPlatform, @Nullable String callId, @Nullable WWWAuthenticateHeader www,
+    public void unregister(ParentPlatform parentPlatform, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException {
+        register(parentPlatform, sipTransactionInfo, null, errorEvent, okEvent, false, false);
+    }
+
+    @Override
+    public void register(ParentPlatform parentPlatform, @Nullable SipTransactionInfo sipTransactionInfo, @Nullable WWWAuthenticateHeader www,
                             SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister) throws SipException, InvalidArgumentException, ParseException {
             Request request;
-            if (!registerAgain ) {
-                CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
 
+            CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
+            String fromTag = SipUtils.getNewFromTag();
+            String toTag = null;
+            if (sipTransactionInfo != null ) {
+                if (sipTransactionInfo.getCallId() != null) {
+                    callIdHeader.setCallId(sipTransactionInfo.getCallId());
+                }
+                if (sipTransactionInfo.getFromTag() != null) {
+                    fromTag = sipTransactionInfo.getFromTag();
+                }
+                if (sipTransactionInfo.getToTag() != null) {
+                    toTag = sipTransactionInfo.getToTag();
+                }
+            }
+
+            if (!registerAgain ) {
                 request = headerProviderPlatformProvider.createRegisterRequest(parentPlatform,
-                        redisCatchStorage.getCSEQ(), SipUtils.getNewFromTag(),
-                        SipUtils.getNewViaTag(), callIdHeader, isRegister);
+                        redisCatchStorage.getCSEQ(), fromTag,
+                        toTag, callIdHeader, isRegister);
                 // 将 callid 写入缓存, 等注册成功可以更新状态
                 String callIdFromHeader = callIdHeader.getCallId();
                 redisCatchStorage.updatePlatformRegisterInfo(callIdFromHeader, PlatformRegisterInfo.getInstance(parentPlatform.getServerGBId(), isRegister));
@@ -122,8 +146,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                 });
 
             }else {
-                CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
-                request = headerProviderPlatformProvider.createRegisterRequest(parentPlatform, SipUtils.getNewFromTag(), null, callId, www, callIdHeader, isRegister);
+                request = headerProviderPlatformProvider.createRegisterRequest(parentPlatform, fromTag, toTag, www, callIdHeader, isRegister);
             }
 
             sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, null, okEvent);
@@ -245,6 +268,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                         catalogXml.append("<IPAddress>" + channel.getIpAddress() + "</IPAddress>\r\n");
                         catalogXml.append("<Port>" + channel.getPort() + "</Port>\r\n");
                         catalogXml.append("<Password>" + channel.getPort() + "</Password>\r\n");
+                        catalogXml.append("<PTZType>" + channel.getPTZType() + "</PTZType>\r\n");
                         catalogXml.append("<Status>" + (channel.getStatus() == 1?"ON":"OFF") + "</Status>\r\n");
                         catalogXml.append("<Longitude>" +
                                 (channel.getLongitudeWgs84() != 0? channel.getLongitudeWgs84():channel.getLongitude())
@@ -285,6 +309,9 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
 
         String callId = request.getCallIdHeader().getCallId();
 
+        logger.info("[命令发送] 国标级联{} 目录查询回复: 共{}条,已发送{}条", parentPlatform.getServerGBId(),
+                channels.size(), Math.min(index + parentPlatform.getCatalogGroup(), channels.size()));
+        logger.debug(catalogXml);
         if (sendAfterResponse) {
             // 默认按照收到200回复后发送下一条, 如果超时收不到回复,就以30毫秒的间隔直接发送。
             dynamicTask.startDelay(timeoutTaskKey, ()->{
@@ -336,17 +363,22 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         if (parentPlatform == null) {
             return;
         }
+        String deviceId = device == null ? parentPlatform.getDeviceGBId() : device.getDeviceId();
+        String deviceName = device == null ? parentPlatform.getName() : device.getName();
+        String manufacturer = device == null ? "WVP-28181-PRO" : device.getManufacturer();
+        String model = device == null ? "platform" : device.getModel();
+        String firmware = device == null ? gitUtil.getBuildVersion() : device.getFirmware();
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer deviceInfoXml = new StringBuffer(600);
         deviceInfoXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
         deviceInfoXml.append("<Response>\r\n");
         deviceInfoXml.append("<CmdType>DeviceInfo</CmdType>\r\n");
         deviceInfoXml.append("<SN>" +sn + "</SN>\r\n");
-        deviceInfoXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        deviceInfoXml.append("<DeviceName>" + device.getName() + "</DeviceName>\r\n");
-        deviceInfoXml.append("<Manufacturer>" + device.getManufacturer() + "</Manufacturer>\r\n");
-        deviceInfoXml.append("<Model>" + device.getModel() + "</Model>\r\n");
-        deviceInfoXml.append("<Firmware>" + device.getFirmware() + "</Firmware>\r\n");
+        deviceInfoXml.append("<DeviceID>" + deviceId + "</DeviceID>\r\n");
+        deviceInfoXml.append("<DeviceName>" + deviceName + "</DeviceName>\r\n");
+        deviceInfoXml.append("<Manufacturer>" + manufacturer + "</Manufacturer>\r\n");
+        deviceInfoXml.append("<Model>" + model + "</Model>\r\n");
+        deviceInfoXml.append("<Firmware>" + firmware + "</Firmware>\r\n");
         deviceInfoXml.append("<Result>OK</Result>\r\n");
         deviceInfoXml.append("</Response>\r\n");
 
@@ -423,7 +455,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         if (parentPlatform == null) {
             return;
         }
-        logger.info("[发送报警通知] {}/{}->{},{}: {}", parentPlatform.getServerGBId(), deviceAlarm.getChannelId(),
+        logger.info("[发送报警通知]平台: {}/{}->{},{}: {}", parentPlatform.getServerGBId(), deviceAlarm.getChannelId(),
                 deviceAlarm.getLongitude(), deviceAlarm.getLatitude(), JSON.toJSONString(deviceAlarm));
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer deviceStatusXml = new StringBuffer(600);
@@ -434,7 +466,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                 .append("<DeviceID>" + deviceAlarm.getChannelId() + "</DeviceID>\r\n")
                 .append("<AlarmPriority>" + deviceAlarm.getAlarmPriority() + "</AlarmPriority>\r\n")
                 .append("<AlarmMethod>" + deviceAlarm.getAlarmMethod() + "</AlarmMethod>\r\n")
-                .append("<AlarmTime>" + deviceAlarm.getAlarmTime() + "</AlarmTime>\r\n")
+                .append("<AlarmTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(deviceAlarm.getAlarmTime()) + "</AlarmTime>\r\n")
                 .append("<AlarmDescription>" + deviceAlarm.getAlarmDescription() + "</AlarmDescription>\r\n")
                 .append("<Longitude>" + deviceAlarm.getLongitude() + "</Longitude>\r\n")
                 .append("<Latitude>" + deviceAlarm.getLatitude() + "</Latitude>\r\n")

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

@@ -45,7 +45,7 @@ public abstract class SIPRequestProcessorParent {
 		try {
 			return SipFactory.getInstance().createHeaderFactory();
 		} catch (PeerUnavailableException e) {
-			e.printStackTrace();
+			logger.error("未处理的异常 ", e);
 		}
 		return null;
 	}
@@ -54,7 +54,7 @@ public abstract class SIPRequestProcessorParent {
 		try {
 			return SipFactory.getInstance().createMessageFactory();
 		} catch (PeerUnavailableException e) {
-			e.printStackTrace();
+			logger.error("未处理的异常 ", e);
 		}
 		return null;
 	}

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

@@ -8,6 +8,7 @@ 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.AudioBroadcastManager;
+import com.genersoft.iot.vmp.gb28181.session.SsrcConfig;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
@@ -457,12 +458,8 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                             if (!userSetting.getPushStreamAfterAck()) {
                                 playService.startPushStream(sendRtpItem, sipResponse, platform, request.getCallIdHeader());
                             }
-                        } catch (SipException e) {
-                            e.printStackTrace();
-                        } catch (InvalidArgumentException e) {
-                            e.printStackTrace();
-                        } catch (ParseException e) {
-                            e.printStackTrace();
+                        } catch (SipException | InvalidArgumentException | ParseException e) {
+                            logger.error("[命令发送失败] 国标级联 回复SdpAck", e);
                         }
                     };
                     SipSubscribe.Event errorEvent = ((event) -> {
@@ -471,7 +468,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                             Response response = getMessageFactory().createResponse(event.statusCode, evt.getRequest());
                             sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
                         } catch (ParseException | SipException  e) {
-                            e.printStackTrace();
+                            logger.error("未处理的异常 ", e);
                         }
                     });
                     sendRtpItem.setApp("rtp");
@@ -543,6 +540,15 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                         }
                     }
                 } else if (gbStream != null) {
+                    if(ssrc.equals(ssrcDefault))
+                    {
+                        SsrcConfig ssrcConfig = mediaServerItem.getSsrcConfig();
+                        if(ssrcConfig != null)
+                        {
+                            ssrc = ssrcConfig.getPlaySsrc();
+                            ssrcConfig.releaseSsrc(ssrc);
+                        }
+                    }
                     if("push".equals(gbStream.getStreamType())) {
                         if (streamPushItem != null && streamPushItem.isPushIng()) {
                             // 推流状态
@@ -572,7 +578,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
         } catch (SdpParseException e) {
             logger.error("sdp解析错误", e);
         } catch (SdpException e) {
-            e.printStackTrace();
+            logger.error("未处理的异常 ", e);
         }
     }
 
@@ -727,11 +733,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                     mediaListManager.removedChannelOnlineEventLister(gbStream.getApp(), gbStream.getStream());
                     responseAck(request, Response.REQUEST_TIMEOUT); // 超时
                 } catch (SipException e) {
-                    e.printStackTrace();
+                    logger.error("未处理的异常 ", e);
                 } catch (InvalidArgumentException e) {
-                    e.printStackTrace();
+                    logger.error("未处理的异常 ", e);
                 } catch (ParseException e) {
-                    e.printStackTrace();
+                    logger.error("未处理的异常 ", e);
                 }
             }, userSetting.getPlatformPlayTimeout());
             // 添加监听
@@ -750,11 +756,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                         try {
                             responseAck(request, Response.BUSY_HERE);
                         } catch (SipException e) {
-                            e.printStackTrace();
+                            logger.error("未处理的异常 ", e);
                         } catch (InvalidArgumentException e) {
-                            e.printStackTrace();
+                            logger.error("未处理的异常 ", e);
                         } catch (ParseException e) {
-                            e.printStackTrace();
+                            logger.error("未处理的异常 ", e);
                         }
                         return;
                     }
@@ -812,11 +818,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                         try {
                             responseAck(request, Response.BUSY_HERE);
                         } catch (SipException e) {
-                            e.printStackTrace();
+                            logger.error("未处理的异常 ", e);
                         } catch (InvalidArgumentException e) {
-                            e.printStackTrace();
+                            logger.error("未处理的异常 ", e);
                         } catch (ParseException e) {
-                            e.printStackTrace();
+                            logger.error("未处理的异常 ", e);
                         }
                         return;
                     }
@@ -869,7 +875,13 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
         content.append("s=Play\r\n");
         content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
         content.append("t=0 0\r\n");
-        content.append("m=video " + sendRtpItem.getLocalPort() + " RTP/AVP 96\r\n");
+        // 非严格模式端口不统一, 增加兼容性,修改为一个不为0的端口
+        int localPort = sendRtpItem.getLocalPort();
+        if(localPort == 0)
+        {
+            localPort = new Random().nextInt(65535) + 1;
+        }
+        content.append("m=video " + localPort + " RTP/AVP 96\r\n");
         content.append("a=sendonly\r\n");
         content.append("a=rtpmap:96 PS/90000\r\n");
         if (sendRtpItem.isTcp()) {
@@ -890,11 +902,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
             }
             return sipResponse;
         } catch (SipException e) {
-            e.printStackTrace();
+            logger.error("未处理的异常 ", e);
         } catch (InvalidArgumentException e) {
-            e.printStackTrace();
+            logger.error("未处理的异常 ", e);
         } catch (ParseException e) {
-            e.printStackTrace();
+            logger.error("未处理的异常 ", e);
         }
         return null;
     }

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

@@ -93,7 +93,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 		try {
 			responseAck((SIPRequest) evt.getRequest(), Response.OK, null, null);
 		}catch (SipException | InvalidArgumentException | ParseException e) {
-			e.printStackTrace();
+			logger.error("未处理的异常 ", e);
 		}
 		boolean runed = !taskQueue.isEmpty();
 		taskQueue.offer(new HandlerCatchData(evt, null, null));
@@ -229,7 +229,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 			jsonObject.put("speed", mobilePosition.getSpeed());
 			redisCatchStorage.sendMobilePositionMsg(jsonObject);
 		} catch (DocumentException  e) {
-			e.printStackTrace();
+			logger.error("未处理的异常 ", e);
 		}
 	}
 
@@ -339,7 +339,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 				publisher.deviceAlarmEventPublish(deviceAlarm);
 			}
 		} catch (DocumentException e) {
-			e.printStackTrace();
+			logger.error("未处理的异常 ", e);
 		}
 	}
 
@@ -397,12 +397,20 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 						case CatalogEvent.OFF :
 							// 离线
 							logger.info("[收到通道离线通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
-							storager.deviceChannelOffline(deviceId, channel.getChannelId());
+							if (userSetting.getRefuseChannelStatusChannelFormNotify()) {
+								storager.deviceChannelOffline(deviceId, channel.getChannelId());
+							}else {
+								logger.info("[收到通道离线通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
+							}
 							break;
 						case CatalogEvent.VLOST:
 							// 视频丢失
 							logger.info("[收到通道视频丢失通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
-							storager.deviceChannelOffline(deviceId, channel.getChannelId());
+							if (userSetting.getRefuseChannelStatusChannelFormNotify()) {
+								storager.deviceChannelOffline(deviceId, channel.getChannelId());
+							}else {
+								logger.info("[收到通道视频丢失通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
+							}
 							break;
 						case CatalogEvent.DEFECT:
 							// 故障
@@ -432,7 +440,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 				}
 			}
 		} catch (DocumentException e) {
-			e.printStackTrace();
+			logger.error("未处理的异常 ", e);
 		}
 	}
 

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

@@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.RemoteAddressInfo;
+import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
 import com.genersoft.iot.vmp.gb28181.bean.WvpSipDate;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
@@ -18,6 +19,7 @@ import gov.nist.javax.sip.address.AddressImpl;
 import gov.nist.javax.sip.address.SipUri;
 import gov.nist.javax.sip.header.SIPDateHeader;
 import gov.nist.javax.sip.message.SIPRequest;
+import gov.nist.javax.sip.message.SIPResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.InitializingBean;
@@ -34,6 +36,7 @@ import javax.sip.header.AuthorizationHeader;
 import javax.sip.header.ContactHeader;
 import javax.sip.header.FromHeader;
 import javax.sip.header.ViaHeader;
+import javax.sip.message.Request;
 import javax.sip.message.Response;
 import java.security.NoSuchAlgorithmException;
 import java.text.ParseException;
@@ -105,6 +108,30 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
             SipUri uri = (SipUri) address.getURI();
             String deviceId = uri.getUser();
             Device device = deviceService.getDevice(deviceId);
+
+            RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request,
+                    userSetting.getSipUseSourceIpAsRemoteAddress());
+
+            if (device != null &&
+                device.getSipTransactionInfo() != null &&
+                request.getCallIdHeader().getCallId().equals(device.getSipTransactionInfo().getCallId())) {
+                logger.info("[注册请求] 注册续订: {}", device.getDeviceId());
+                device.setExpires(request.getExpires().getExpires());
+                device.setIp(remoteAddressInfo.getIp());
+                device.setPort(remoteAddressInfo.getPort());
+                device.setHostAddress(remoteAddressInfo.getIp().concat(":").concat(String.valueOf(remoteAddressInfo.getPort())));
+                device.setLocalIp(request.getLocalAddress().getHostAddress());
+                Response registerOkResponse = getRegisterOkResponse(request);
+                // 判断TCP还是UDP
+                ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
+                String transport = reqViaHeader.getTransport();
+                device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP");
+                sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), registerOkResponse);
+                device.setRegisterTime(DateUtil.getNow());
+                SipTransactionInfo sipTransactionInfo = new SipTransactionInfo((SIPResponse)registerOkResponse);
+                deviceService.online(device, sipTransactionInfo);
+                return;
+            }
             String password = (device != null && !ObjectUtils.isEmpty(device.getPassword()))? device.getPassword() : sipConfig.getPassword();
             AuthorizationHeader authHead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
             if (authHead == null && !ObjectUtils.isEmpty(password)) {
@@ -147,9 +174,6 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
             // 添加Expires头
             response.addHeader(request.getExpires());
 
-            RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request,
-                    userSetting.getSipUseSourceIpAsRemoteAddress());
-
             if (device == null) {
                 device = new Device();
                 device.setStreamMode("UDP");
@@ -182,13 +206,33 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
             if (registerFlag) {
                 logger.info("[注册成功] deviceId: {}->{}",  deviceId, requestAddress);
                 device.setRegisterTime(DateUtil.getNow());
-                deviceService.online(device);
+                SipTransactionInfo sipTransactionInfo = new SipTransactionInfo((SIPResponse)response);
+                deviceService.online(device, sipTransactionInfo);
             } else {
                 logger.info("[注销成功] deviceId: {}->{}" ,deviceId, requestAddress);
-                deviceService.offline(deviceId);
+                deviceService.offline(deviceId, "主动注销");
             }
         } catch (SipException | NoSuchAlgorithmException | ParseException e) {
-            e.printStackTrace();
+            logger.error("未处理的异常 ", e);
         }
     }
+
+    private Response getRegisterOkResponse(Request request) throws ParseException {
+        // 携带授权头并且密码正确
+        Response  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);
+
+        // 添加Contact头
+        response.addHeader(request.getHeader(ContactHeader.NAME));
+        // 添加Expires头
+        response.addHeader(request.getExpires());
+
+        return response;
+
+    }
 }

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

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 
+import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.CmdType;
@@ -8,14 +9,11 @@ import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
 import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
-import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
-import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
 import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
-import gov.nist.javax.sip.SipProviderImpl;
 import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.message.SIPResponse;
 import org.dom4j.DocumentException;
@@ -26,7 +24,9 @@ import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
-import javax.sip.*;
+import javax.sip.InvalidArgumentException;
+import javax.sip.RequestEvent;
+import javax.sip.SipException;
 import javax.sip.header.ExpiresHeader;
 import javax.sip.message.Response;
 import java.text.ParseException;
@@ -93,7 +93,7 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 				sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
 			}
 		} catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
-			e.printStackTrace();
+			logger.error("未处理的异常 ", e);
 		}
 
 	}
@@ -146,7 +146,7 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 			}
 
 		} catch (SipException | InvalidArgumentException | ParseException e) {
-			e.printStackTrace();
+			logger.error("未处理的异常 ", e);
 		}
 	}
 
@@ -192,7 +192,7 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 				subscribeHolder.putCatalogSubscribe(platformId, subscribeInfo);
 			}
 		} catch (SipException | InvalidArgumentException | ParseException e) {
-			e.printStackTrace();
+			logger.error("未处理的异常 ", e);
 		}
 	}
 }

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

@@ -73,35 +73,38 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent
         String channelId = getText(rootElement, "DeviceID");
         // 远程启动功能
         if (!ObjectUtils.isEmpty(getText(rootElement, "TeleBoot"))) {
-            if (parentPlatform.getServerGBId().equals(targetGBId)) {
-                // 远程启动本平台:需要在重新启动程序后先对SipStack解绑
-                logger.info("执行远程启动本平台命令");
-                try {
-                    cmderFroPlatform.unregister(parentPlatform, null, null);
-                } catch (InvalidArgumentException | ParseException | SipException e) {
-                    logger.error("[命令发送失败] 国标级联 注销: {}", e.getMessage());
-                }
-                taskExecutor.execute(() -> {
-                    // 远程启动
-//                    try {
-//                        Thread.sleep(3000);
-//                        SipProvider up = (SipProvider) SpringBeanFactory.getBean("udpSipProvider");
-//                        SipStackImpl stack = (SipStackImpl)up.getSipStack();
-//                        stack.stop();
-//                        Iterator listener = stack.getListeningPoints();
-//                        while (listener.hasNext()) {
-//                            stack.deleteListeningPoint((ListeningPoint) listener.next());
-//                        }
-//                        Iterator providers = stack.getSipProviders();
-//                        while (providers.hasNext()) {
-//                            stack.deleteSipProvider((SipProvider) providers.next());
-//                        }
-//                        VManageBootstrap.restart();
-//                    } catch (InterruptedException | ObjectInUseException e) {
-//                        logger.error("[任务执行失败] 服务重启: {}", e.getMessage());
-//                    }
-                });
-            }
+            // TODO 拒绝远程启动命令
+            logger.warn("[国标级联]收到平台的远程启动命令, 不处理");
+
+//            if (parentPlatform.getServerGBId().equals(targetGBId)) {
+//                // 远程启动本平台:需要在重新启动程序后先对SipStack解绑
+//                logger.info("执行远程启动本平台命令");
+//                try {
+//                    cmderFroPlatform.unregister(parentPlatform, null, null);
+//                } catch (InvalidArgumentException | ParseException | SipException e) {
+//                    logger.error("[命令发送失败] 国标级联 注销: {}", e.getMessage());
+//                }
+//                taskExecutor.execute(() -> {
+//                    // 远程启动
+////                    try {
+////                        Thread.sleep(3000);
+////                        SipProvider up = (SipProvider) SpringBeanFactory.getBean("udpSipProvider");
+////                        SipStackImpl stack = (SipStackImpl)up.getSipStack();
+////                        stack.stop();
+////                        Iterator listener = stack.getListeningPoints();
+////                        while (listener.hasNext()) {
+////                            stack.deleteListeningPoint((ListeningPoint) listener.next());
+////                        }
+////                        Iterator providers = stack.getSipProviders();
+////                        while (providers.hasNext()) {
+////                            stack.deleteSipProvider((SipProvider) providers.next());
+////                        }
+////                        VManageBootstrap.restart();
+////                    } catch (InterruptedException | ObjectInUseException e) {
+////                        logger.error("[任务执行失败] 服务重启: {}", e.getMessage());
+////                    }
+//                });
+//            }
         }
         DeviceControlType deviceControlType = DeviceControlType.typeOf(rootElement);
         logger.info("[接受deviceControl命令] 命令: {}", deviceControlType);

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

@@ -186,9 +186,13 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
                             // 发送给平台的报警信息。 发送redis通知
                             logger.info("[发送给平台的报警信息]内容:{}", JSONObject.toJSONString(deviceAlarm));
                             AlarmChannelMessage alarmChannelMessage = new AlarmChannelMessage();
-                            alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod()));
+                            if (deviceAlarm.getAlarmMethod() != null) {
+                                alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod()));
+                            }
                             alarmChannelMessage.setAlarmDescription(deviceAlarm.getAlarmDescription());
-                            alarmChannelMessage.setAlarmType(Integer.parseInt(deviceAlarm.getAlarmType()));
+                            if (deviceAlarm.getAlarmType() != null) {
+                                alarmChannelMessage.setAlarmType(Integer.parseInt(deviceAlarm.getAlarmType()));
+                            }
                             alarmChannelMessage.setGbId(channelId);
                             redisCatchStorage.sendAlarmMsg(alarmChannelMessage);
                             continue;
@@ -204,6 +208,7 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
                             publisher.deviceAlarmEventPublish(deviceAlarm);
                         }
                     }catch (Exception e) {
+                        logger.error("未处理的异常 ", e);
                         logger.warn("[收到报警通知] 发现未处理的异常, {}\r\n{}",e.getMessage(), evt.getRequest());
                     }
                 }
@@ -264,12 +269,15 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
         if (channelId.equals(parentPlatform.getDeviceGBId())) {
             // 发送给平台的报警信息。 发送redis通知
             AlarmChannelMessage alarmChannelMessage = new AlarmChannelMessage();
-            alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod()));
+            if (deviceAlarm.getAlarmMethod() != null) {
+                alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod()));
+            }
             alarmChannelMessage.setAlarmDescription(deviceAlarm.getAlarmDescription());
             alarmChannelMessage.setGbId(channelId);
-            alarmChannelMessage.setAlarmType(Integer.parseInt(deviceAlarm.getAlarmType()));
+            if (deviceAlarm.getAlarmType() != null) {
+                alarmChannelMessage.setAlarmType(Integer.parseInt(deviceAlarm.getAlarmType()));
+            }
             redisCatchStorage.sendAlarmMsg(alarmChannelMessage);
-            return;
         }
     }
 }

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

@@ -88,13 +88,13 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
             // 对于已经离线的设备判断他的注册是否已经过期
             if (!deviceService.expire(device)){
                 device.setOnline(0);
-                deviceService.online(device);
+                deviceService.online(device, null);
             }
         }
         // 刷新过期任务
         String registerExpireTaskKey = VideoManagerConstants.REGISTER_EXPIRE_TASK_KEY_PREFIX + device.getDeviceId();
         // 如果三次心跳失败,则设置设备离线
-        dynamicTask.startDelay(registerExpireTaskKey, ()-> deviceService.offline(device.getDeviceId()), device.getKeepaliveIntervalTime()*1000*3);
+        dynamicTask.startDelay(registerExpireTaskKey, ()-> deviceService.offline(device.getDeviceId(), "三次心跳失败"), device.getKeepaliveIntervalTime()*1000*3);
 
     }
 

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

@@ -149,9 +149,10 @@ public class MobilePositionNotifyMessageHandler extends SIPRequestProcessorParen
                         redisCatchStorage.sendMobilePositionMsg(jsonObject);
 
                     } catch (DocumentException e) {
-                        e.printStackTrace();
+                        logger.error("未处理的异常 ", e);
                     } catch (Exception e) {
-                        logger.warn("[移动位置通知] 发现未处理的异常, {}\r\n{}",e.getMessage(), evt.getRequest());
+                        logger.warn("[移动位置通知] 发现未处理的异常, \r\n{}", evt.getRequest());
+                        logger.error("[移动位置通知] 异常内容: ", e);
                     }
                 }
             });

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

@@ -60,17 +60,24 @@ public class DeviceInfoQueryMessageHandler extends SIPRequestProcessorParent imp
             return;
         }
         String sn = rootElement.element("SN").getText();
+
         /*根据WVP原有的数据结构,设备和通道是分开放置,设备信息都是存放在设备表里,通道表里的设备信息不可作为真实信息处理
         大部分NVR/IPC设备对他的通道信息实现都是返回默认的值没有什么参考价值。NVR/IPC通道我们统一使用设备表的设备信息来作为返回。
         我们这里使用查询数据库的方式来实现这个设备信息查询的功能,在其他地方对设备信息更新达到正确的目的。*/
+
         String channelId = getText(rootElement, "DeviceID");
-        Device device = storager.queryDeviceInfoByPlatformIdAndChannelId(parentPlatform.getServerGBId(), channelId);
-        if (device ==null){
-            logger.error("[平台没有该通道的使用权限]:platformId"+parentPlatform.getServerGBId()+"  deviceID:"+channelId);
-            return;
+        // 查询这是通道id还是设备id
+        Device device = null;
+        // 如果id指向平台的国标编号,那么就是查询平台的信息
+        if (!parentPlatform.getDeviceGBId().equals(channelId)) {
+            device = storager.queryDeviceInfoByPlatformIdAndChannelId(parentPlatform.getServerGBId(), channelId);
+            if (device ==null){
+                logger.error("[平台没有该通道的使用权限]:platformId"+parentPlatform.getServerGBId()+"  deviceID:"+channelId);
+                return;
+            }
         }
         try {
-            cmderFroPlatform.deviceInfoResponse(parentPlatform,device, sn, fromHeader.getTag());
+            cmderFroPlatform.deviceInfoResponse(parentPlatform, device, sn, fromHeader.getTag());
         } catch (SipException | InvalidArgumentException | ParseException e) {
             logger.error("[命令发送失败] 国标级联 DeviceInfo查询回复: {}", e.getMessage());
         }

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

@@ -132,7 +132,8 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
 
                         }
                     }catch (Exception e) {
-                        logger.warn("[收到通道] 发现未处理的异常, {}\r\n{}",e.getMessage(), evt.getRequest());
+                        logger.warn("[收到通道] 发现未处理的异常, \r\n{}", evt.getRequest());
+                        logger.error("[收到通道] 异常内容: ", e);
                     }
                 }
             });

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

@@ -1,10 +1,8 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
 
 import com.alibaba.fastjson2.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.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;
@@ -26,7 +24,6 @@ import javax.sip.RequestEvent;
 import javax.sip.SipException;
 import javax.sip.message.Response;
 import java.text.ParseException;
-import java.util.Objects;
 
 @Component
 public class DeviceStatusResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
@@ -74,9 +71,9 @@ public class DeviceStatusResponseMessageHandler extends SIPRequestProcessorParen
         }
         String text = onlineElement.getText();
         if ("ONLINE".equalsIgnoreCase(text.trim())) {
-            deviceService.online(device);
+            deviceService.online(device, null);
         }else {
-            deviceService.offline(device.getDeviceId());
+            deviceService.offline(device.getDeviceId(), "设备状态查询结果:" + text.trim());
         }
         RequestMessage msg = new RequestMessage();
         msg.setKey(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + device.getDeviceId());

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

@@ -142,7 +142,7 @@ public class MobilePositionResponseMessageHandler extends SIPRequestProcessorPar
             }
 
         } catch (DocumentException e) {
-            e.printStackTrace();
+            logger.error("未处理的异常 ", e);
         }
     }
 

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

@@ -150,7 +150,8 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
                     }
                 }
             } catch (Exception e) {
-                logger.error("[国标录像] 发现未处理的异常, "+e.getMessage(), e);
+                logger.error("[国标录像] 发现未处理的异常, \r\n{}", evt.getRequest());
+                logger.error("[国标录像] 异常内容: ", e);
             }
         });
     }
@@ -163,7 +164,11 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
     public void releaseRequest(String deviceId, String sn,RecordInfo recordInfo){
         String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + sn;
         // 对数据进行排序
-        Collections.sort(recordInfo.getRecordList());
+        if(recordInfo!=null && recordInfo.getRecordList()!=null) {
+            Collections.sort(recordInfo.getRecordList());
+        }else{
+            recordInfo.setRecordList(new ArrayList<>());
+        }
 
         RequestMessage msg = new RequestMessage();
         msg.setKey(key);

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

@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.response.impl;
 
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatformCatch;
+import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
 import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
@@ -10,6 +11,7 @@ import com.genersoft.iot.vmp.service.IPlatformService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
+import gov.nist.javax.sip.message.SIPResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -18,7 +20,6 @@ import org.springframework.stereotype.Component;
 import javax.sip.InvalidArgumentException;
 import javax.sip.ResponseEvent;
 import javax.sip.SipException;
-import javax.sip.header.CallIdHeader;
 import javax.sip.header.WWWAuthenticateHeader;
 import javax.sip.message.Response;
 import java.text.ParseException;
@@ -65,9 +66,8 @@ public class RegisterResponseProcessor extends SIPResponseProcessorAbstract {
 	 */
 	@Override
 	public void process(ResponseEvent evt) {
-		Response response = evt.getResponse();
-		CallIdHeader callIdHeader = (CallIdHeader) response.getHeader(CallIdHeader.NAME);
-		String callId = callIdHeader.getCallId();
+		SIPResponse response = (SIPResponse)evt.getResponse();
+		String callId = response.getCallIdHeader().getCallId();
 		PlatformRegisterInfo platformRegisterInfo = redisCatchStorage.queryPlatformRegisterInfo(callId);
 		if (platformRegisterInfo == null) {
 			logger.info(String.format("[国标级联]未找到callId: %s 的注册/注销平台id", callId ));
@@ -90,15 +90,17 @@ public class RegisterResponseProcessor extends SIPResponseProcessorAbstract {
 
 		if (response.getStatusCode() == Response.UNAUTHORIZED) {
 			WWWAuthenticateHeader www = (WWWAuthenticateHeader)response.getHeader(WWWAuthenticateHeader.NAME);
+			SipTransactionInfo sipTransactionInfo = new SipTransactionInfo(response);
 			try {
-				sipCommanderForPlatform.register(parentPlatform, callId, www, null, null, true, platformRegisterInfo.isRegister());
+				sipCommanderForPlatform.register(parentPlatform, sipTransactionInfo, www, null, null, true, platformRegisterInfo.isRegister());
 			} catch (SipException | InvalidArgumentException | ParseException e) {
 				logger.error("[命令发送失败] 国标级联 再次注册: {}", e.getMessage());
 			}
 		}else if (response.getStatusCode() == Response.OK){
 
 			if (platformRegisterInfo.isRegister()) {
-				platformService.online(parentPlatform);
+				SipTransactionInfo sipTransactionInfo = new SipTransactionInfo(response);
+				platformService.online(parentPlatform, sipTransactionInfo);
 			}else {
 				platformService.offline(parentPlatform, false);
 			}

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

@@ -9,17 +9,12 @@ import org.jetbrains.annotations.NotNull;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
-import org.springframework.util.ObjectUtils;
-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 {
@@ -137,6 +132,11 @@ public class AssistRESTfulUtils {
         return sendGet(mediaServerItem, "api/record/file/duration",param, callback);
     }
 
+    public JSONObject getInfo(MediaServerItem mediaServerItem, RequestCallback callback){
+        Map<String, Object> param = new HashMap<>();
+        return sendGet(mediaServerItem, "api/record/info",param, callback);
+    }
+
     public JSONObject addStreamCallInfo(MediaServerItem mediaServerItem, String app, String stream, String callId, RequestCallback callback){
         Map<String, Object> param = new HashMap<>();
         param.put("app",app);

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

@@ -119,10 +119,11 @@ public class ZLMHttpHookListener {
      * 服务器定时上报时间,上报间隔可配置,默认10s上报一次
      */
     @ResponseBody
+
     @PostMapping(value = "/on_server_keepalive", produces = "application/json;charset=UTF-8")
     public HookResult onServerKeepalive(@RequestBody OnServerKeepaliveHookParam param) {
 
-        logger.info("[ZLM HOOK] 收到zlm心跳:" + param.getMediaServerId());
+//        logger.info("[ZLM HOOK] 收到zlm心跳:" + param.getMediaServerId());
 
         taskExecutor.execute(() -> {
             List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_keepalive);
@@ -142,6 +143,7 @@ public class ZLMHttpHookListener {
      * 播放器鉴权事件,rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件。
      */
     @ResponseBody
+
     @PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8")
     public HookResult onPlay(@RequestBody OnPlayHookParam param) {
         if (logger.isDebugEnabled()) {
@@ -264,9 +266,28 @@ public class ZLMHttpHookListener {
             }
 
         }
+        if (mediaInfo.getRecordAssistPort() > 0 && userSetting.getRecordPath() == null) {
+            logger.info("推流时发现尚未设置录像路径,从assist服务中读取");
+            JSONObject info = assistRESTfulUtils.getInfo(mediaInfo, null);
+            if (info != null && info.getInteger("code") != null && info.getInteger("code") == 0 ) {
+                JSONObject dataJson = info.getJSONObject("data");
+                if (dataJson != null) {
+                    String recordPath = dataJson.getString("record");
+                    userSetting.setRecordPath(recordPath);
+                    result.setMp4_save_path(recordPath);
+                    // 修改zlm中的录像路径
+                    if (mediaInfo.isAutoConfig()) {
+                        taskExecutor.execute(() -> {
+                            mediaServerService.setZLMConfig(mediaInfo, false);
+                        });
+                    }
+                }
+            }
+        }
         return result;
     }
 
+
     /**
      * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。
      */
@@ -293,8 +314,12 @@ public class ZLMHttpHookListener {
                     subscribe.response(mediaInfo, json);
                 }
             }
-            // 流消失移除redis play
+
+            List<OnStreamChangedHookParam.MediaTrack> tracks = param.getTracks();
+            // TODO 重构此处逻辑
+
             if (param.isRegist()) {
+                // 处理流注册的鉴权信息
                 if (param.getOriginType() == OriginType.RTMP_PUSH.ordinal()
                         || param.getOriginType() == OriginType.RTSP_PUSH.ordinal()
                         || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) {

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

@@ -334,9 +334,9 @@ public class ZLMRESTfulUtils {
         sendPost(mediaServerItem, "kick_sessions",param, null);
     }
 
-    public void getSnap(MediaServerItem mediaServerItem, String flvUrl, int timeout_sec, int expire_sec, String targetPath, String fileName) {
+    public void getSnap(MediaServerItem mediaServerItem, String streamUrl, int timeout_sec, int expire_sec, String targetPath, String fileName) {
         Map<String, Object> param = new HashMap<>(3);
-        param.put("url", flvUrl);
+        param.put("url", streamUrl);
         param.put("timeout_sec", timeout_sec);
         param.put("expire_sec", expire_sec);
         sendGetForImg(mediaServerItem, "getSnap", param, targetPath, fileName);

+ 4 - 1
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java

@@ -262,8 +262,11 @@ public class ZLMRTPServerFactory {
                         logger.info("[保持端口] {}->监听端口到期继续保持监听", ssrc);
                         keepPort(serverItem, ssrc);
                     });
-        }
         logger.info("[保持端口] {}->监听端口: {}", ssrc, localPort);
+            logger.info("[保持端口] {}->监听端口: {}", ssrc, localPort);
+        }else {
+            logger.info("[保持端口] 监听端口失败: {}", ssrc);
+        }
         return localPort;
     }
 

+ 400 - 3
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerConfig.java

@@ -10,21 +10,87 @@ public class ZLMServerConfig {
     @JSONField(name = "api.secret")
     private String apiSecret;
 
+    @JSONField(name = "api.snapRoot")
+    private String apiSnapRoot;
+
+    @JSONField(name = "api.defaultSnap")
+    private String apiDefaultSnap;
+
     @JSONField(name = "ffmpeg.bin")
     private String ffmpegBin;
 
     @JSONField(name = "ffmpeg.cmd")
     private String ffmpegCmd;
 
+    @JSONField(name = "ffmpeg.snap")
+    private String ffmpegSnap;
+
     @JSONField(name = "ffmpeg.log")
     private String ffmpegLog;
 
+    @JSONField(name = "ffmpeg.restart_sec")
+    private String ffmpegRestartSec;
+
+    @JSONField(name = "protocol.modify_stamp")
+    private String protocolModifyStamp;
+
+    @JSONField(name = "protocol.enable_audio")
+    private String protocolEnableAudio;
+
+    @JSONField(name = "protocol.add_mute_audio")
+    private String protocolAddMuteAudio;
+
+    @JSONField(name = "protocol.continue_push_ms")
+    private String protocolContinuePushMs;
+
+    @JSONField(name = "protocol.enable_hls")
+    private String protocolEnableHls;
+
+    @JSONField(name = "protocol.enable_mp4")
+    private String protocolEnableMp4;
+
+    @JSONField(name = "protocol.enable_rtsp")
+    private String protocolEnableRtsp;
+
+    @JSONField(name = "protocol.enable_rtmp")
+    private String protocolEnableRtmp;
+
+    @JSONField(name = "protocol.enable_ts")
+    private String protocolEnableTs;
+
+    @JSONField(name = "protocol.enable_fmp4")
+    private String protocolEnableFmp4;
+
+    @JSONField(name = "protocol.mp4_as_player")
+    private String protocolMp4AsPlayer;
+
+    @JSONField(name = "protocol.mp4_max_second")
+    private String protocolMp4MaxSecond;
+
+    @JSONField(name = "protocol.mp4_save_path")
+    private String protocolMp4SavePath;
+
+    @JSONField(name = "protocol.hls_save_path")
+    private String protocolHlsSavePath;
+
+    @JSONField(name = "protocol.hls_demand")
+    private String protocolHlsDemand;
+
+    @JSONField(name = "protocol.rtsp_demand")
+    private String protocolRtspDemand;
+
+    @JSONField(name = "protocol.rtmp_demand")
+    private String protocolRtmpDemand;
+
+    @JSONField(name = "protocol.ts_demand")
+    private String protocolTsDemand;
+
+    @JSONField(name = "protocol.fmp4_demand")
+    private String protocolFmp4Demand;
+
     @JSONField(name = "general.enableVhost")
     private String generalEnableVhost;
 
-    @JSONField(name = "general.mediaServerId")
-    private String generalMediaServerId;
-
     @JSONField(name = "general.flowThreshold")
     private String generalFlowThreshold;
 
@@ -34,6 +100,25 @@ public class ZLMServerConfig {
     @JSONField(name = "general.streamNoneReaderDelayMS")
     private int generalStreamNoneReaderDelayMS;
 
+    @JSONField(name = "general.resetWhenRePlay")
+    private String generalResetWhenRePlay;
+
+    @JSONField(name = "general.mergeWriteMS")
+    private String generalMergeWriteMS;
+
+    @JSONField(name = "general.mediaServerId")
+    private String generalMediaServerId;
+
+    @JSONField(name = "general.wait_track_ready_ms")
+    private String generalWaitTrackReadyMs;
+
+    @JSONField(name = "general.wait_add_track_ms")
+    private String generalWaitAddTrackMs;
+
+    @JSONField(name = "general.unready_frame_cache")
+    private String generalUnreadyFrameCache;
+
+
     @JSONField(name = "ip")
     private String ip;
 
@@ -59,6 +144,18 @@ public class ZLMServerConfig {
     @JSONField(name = "hls.segNum")
     private String hlsSegNum;
 
+    @JSONField(name = "hls.segRetain")
+    private String hlsSegRetain;
+
+    @JSONField(name = "hls.broadcastRecordTs")
+    private String hlsBroadcastRecordTs;
+
+    @JSONField(name = "hls.deleteDelaySec")
+    private String hlsDeleteDelaySec;
+
+    @JSONField(name = "hls.segKeep")
+    private String hlsSegKeep;
+
     @JSONField(name = "hook.access_file_except_hls")
     private String hookAccessFileExceptHLS;
 
@@ -104,6 +201,18 @@ public class ZLMServerConfig {
     @JSONField(name = "hook.on_stream_not_found")
     private String hookOnStreamNotFound;
 
+    @JSONField(name = "hook.on_server_started")
+    private String hookOnServerStarted;
+
+    @JSONField(name = "hook.on_server_keepalive")
+    private String hookOnServerKeepalive;
+
+    @JSONField(name = "hook.on_send_rtp_stopped")
+    private String hookOnSendRtpStopped;
+
+    @JSONField(name = "hook.on_rtp_server_timeout")
+    private String hookOnRtpServerTimeout;
+
     @JSONField(name = "hook.timeoutSec")
     private String hookTimeoutSec;
 
@@ -813,4 +922,292 @@ public class ZLMServerConfig {
     public void setPortRange(String portRange) {
         this.portRange = portRange;
     }
+
+    public String getApiSnapRoot() {
+        return apiSnapRoot;
+    }
+
+    public void setApiSnapRoot(String apiSnapRoot) {
+        this.apiSnapRoot = apiSnapRoot;
+    }
+
+    public String getApiDefaultSnap() {
+        return apiDefaultSnap;
+    }
+
+    public void setApiDefaultSnap(String apiDefaultSnap) {
+        this.apiDefaultSnap = apiDefaultSnap;
+    }
+
+    public String getFfmpegSnap() {
+        return ffmpegSnap;
+    }
+
+    public void setFfmpegSnap(String ffmpegSnap) {
+        this.ffmpegSnap = ffmpegSnap;
+    }
+
+    public String getFfmpegRestartSec() {
+        return ffmpegRestartSec;
+    }
+
+    public void setFfmpegRestartSec(String ffmpegRestartSec) {
+        this.ffmpegRestartSec = ffmpegRestartSec;
+    }
+
+    public String getProtocolModifyStamp() {
+        return protocolModifyStamp;
+    }
+
+    public void setProtocolModifyStamp(String protocolModifyStamp) {
+        this.protocolModifyStamp = protocolModifyStamp;
+    }
+
+    public String getProtocolEnableAudio() {
+        return protocolEnableAudio;
+    }
+
+    public void setProtocolEnableAudio(String protocolEnableAudio) {
+        this.protocolEnableAudio = protocolEnableAudio;
+    }
+
+    public String getProtocolAddMuteAudio() {
+        return protocolAddMuteAudio;
+    }
+
+    public void setProtocolAddMuteAudio(String protocolAddMuteAudio) {
+        this.protocolAddMuteAudio = protocolAddMuteAudio;
+    }
+
+    public String getProtocolContinuePushMs() {
+        return protocolContinuePushMs;
+    }
+
+    public void setProtocolContinuePushMs(String protocolContinuePushMs) {
+        this.protocolContinuePushMs = protocolContinuePushMs;
+    }
+
+    public String getProtocolEnableHls() {
+        return protocolEnableHls;
+    }
+
+    public void setProtocolEnableHls(String protocolEnableHls) {
+        this.protocolEnableHls = protocolEnableHls;
+    }
+
+    public String getProtocolEnableMp4() {
+        return protocolEnableMp4;
+    }
+
+    public void setProtocolEnableMp4(String protocolEnableMp4) {
+        this.protocolEnableMp4 = protocolEnableMp4;
+    }
+
+    public String getProtocolEnableRtsp() {
+        return protocolEnableRtsp;
+    }
+
+    public void setProtocolEnableRtsp(String protocolEnableRtsp) {
+        this.protocolEnableRtsp = protocolEnableRtsp;
+    }
+
+    public String getProtocolEnableRtmp() {
+        return protocolEnableRtmp;
+    }
+
+    public void setProtocolEnableRtmp(String protocolEnableRtmp) {
+        this.protocolEnableRtmp = protocolEnableRtmp;
+    }
+
+    public String getProtocolEnableTs() {
+        return protocolEnableTs;
+    }
+
+    public void setProtocolEnableTs(String protocolEnableTs) {
+        this.protocolEnableTs = protocolEnableTs;
+    }
+
+    public String getProtocolEnableFmp4() {
+        return protocolEnableFmp4;
+    }
+
+    public void setProtocolEnableFmp4(String protocolEnableFmp4) {
+        this.protocolEnableFmp4 = protocolEnableFmp4;
+    }
+
+    public String getProtocolMp4AsPlayer() {
+        return protocolMp4AsPlayer;
+    }
+
+    public void setProtocolMp4AsPlayer(String protocolMp4AsPlayer) {
+        this.protocolMp4AsPlayer = protocolMp4AsPlayer;
+    }
+
+    public String getProtocolMp4MaxSecond() {
+        return protocolMp4MaxSecond;
+    }
+
+    public void setProtocolMp4MaxSecond(String protocolMp4MaxSecond) {
+        this.protocolMp4MaxSecond = protocolMp4MaxSecond;
+    }
+
+    public String getProtocolMp4SavePath() {
+        return protocolMp4SavePath;
+    }
+
+    public void setProtocolMp4SavePath(String protocolMp4SavePath) {
+        this.protocolMp4SavePath = protocolMp4SavePath;
+    }
+
+    public String getProtocolHlsSavePath() {
+        return protocolHlsSavePath;
+    }
+
+    public void setProtocolHlsSavePath(String protocolHlsSavePath) {
+        this.protocolHlsSavePath = protocolHlsSavePath;
+    }
+
+    public String getProtocolHlsDemand() {
+        return protocolHlsDemand;
+    }
+
+    public void setProtocolHlsDemand(String protocolHlsDemand) {
+        this.protocolHlsDemand = protocolHlsDemand;
+    }
+
+    public String getProtocolRtspDemand() {
+        return protocolRtspDemand;
+    }
+
+    public void setProtocolRtspDemand(String protocolRtspDemand) {
+        this.protocolRtspDemand = protocolRtspDemand;
+    }
+
+    public String getProtocolRtmpDemand() {
+        return protocolRtmpDemand;
+    }
+
+    public void setProtocolRtmpDemand(String protocolRtmpDemand) {
+        this.protocolRtmpDemand = protocolRtmpDemand;
+    }
+
+    public String getProtocolTsDemand() {
+        return protocolTsDemand;
+    }
+
+    public void setProtocolTsDemand(String protocolTsDemand) {
+        this.protocolTsDemand = protocolTsDemand;
+    }
+
+    public String getProtocolFmp4Demand() {
+        return protocolFmp4Demand;
+    }
+
+    public void setProtocolFmp4Demand(String protocolFmp4Demand) {
+        this.protocolFmp4Demand = protocolFmp4Demand;
+    }
+
+    public String getGeneralResetWhenRePlay() {
+        return generalResetWhenRePlay;
+    }
+
+    public void setGeneralResetWhenRePlay(String generalResetWhenRePlay) {
+        this.generalResetWhenRePlay = generalResetWhenRePlay;
+    }
+
+    public String getGeneralMergeWriteMS() {
+        return generalMergeWriteMS;
+    }
+
+    public void setGeneralMergeWriteMS(String generalMergeWriteMS) {
+        this.generalMergeWriteMS = generalMergeWriteMS;
+    }
+
+    public String getGeneralWaitTrackReadyMs() {
+        return generalWaitTrackReadyMs;
+    }
+
+    public void setGeneralWaitTrackReadyMs(String generalWaitTrackReadyMs) {
+        this.generalWaitTrackReadyMs = generalWaitTrackReadyMs;
+    }
+
+    public String getGeneralWaitAddTrackMs() {
+        return generalWaitAddTrackMs;
+    }
+
+    public void setGeneralWaitAddTrackMs(String generalWaitAddTrackMs) {
+        this.generalWaitAddTrackMs = generalWaitAddTrackMs;
+    }
+
+    public String getGeneralUnreadyFrameCache() {
+        return generalUnreadyFrameCache;
+    }
+
+    public void setGeneralUnreadyFrameCache(String generalUnreadyFrameCache) {
+        this.generalUnreadyFrameCache = generalUnreadyFrameCache;
+    }
+
+    public String getHlsSegRetain() {
+        return hlsSegRetain;
+    }
+
+    public void setHlsSegRetain(String hlsSegRetain) {
+        this.hlsSegRetain = hlsSegRetain;
+    }
+
+    public String getHlsBroadcastRecordTs() {
+        return hlsBroadcastRecordTs;
+    }
+
+    public void setHlsBroadcastRecordTs(String hlsBroadcastRecordTs) {
+        this.hlsBroadcastRecordTs = hlsBroadcastRecordTs;
+    }
+
+    public String getHlsDeleteDelaySec() {
+        return hlsDeleteDelaySec;
+    }
+
+    public void setHlsDeleteDelaySec(String hlsDeleteDelaySec) {
+        this.hlsDeleteDelaySec = hlsDeleteDelaySec;
+    }
+
+    public String getHlsSegKeep() {
+        return hlsSegKeep;
+    }
+
+    public void setHlsSegKeep(String hlsSegKeep) {
+        this.hlsSegKeep = hlsSegKeep;
+    }
+
+    public String getHookOnServerStarted() {
+        return hookOnServerStarted;
+    }
+
+    public void setHookOnServerStarted(String hookOnServerStarted) {
+        this.hookOnServerStarted = hookOnServerStarted;
+    }
+
+    public String getHookOnServerKeepalive() {
+        return hookOnServerKeepalive;
+    }
+
+    public void setHookOnServerKeepalive(String hookOnServerKeepalive) {
+        this.hookOnServerKeepalive = hookOnServerKeepalive;
+    }
+
+    public String getHookOnSendRtpStopped() {
+        return hookOnSendRtpStopped;
+    }
+
+    public void setHookOnSendRtpStopped(String hookOnSendRtpStopped) {
+        this.hookOnSendRtpStopped = hookOnSendRtpStopped;
+    }
+
+    public String getHookOnRtpServerTimeout() {
+        return hookOnRtpServerTimeout;
+    }
+
+    public void setHookOnRtpServerTimeout(String hookOnRtpServerTimeout) {
+        this.hookOnRtpServerTimeout = hookOnRtpServerTimeout;
+    }
 }

+ 9 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java

@@ -5,6 +5,7 @@ public class HookResultForOnPublish extends HookResult{
     private boolean enable_audio;
     private boolean enable_mp4;
     private int mp4_max_second;
+    private String mp4_save_path;
 
     public HookResultForOnPublish() {
     }
@@ -41,4 +42,12 @@ public class HookResultForOnPublish extends HookResult{
     public void setMp4_max_second(int mp4_max_second) {
         this.mp4_max_second = mp4_max_second;
     }
+
+    public String getMp4_save_path() {
+        return mp4_save_path;
+    }
+
+    public void setMp4_save_path(String mp4_save_path) {
+        this.mp4_save_path = mp4_save_path;
+    }
 }

+ 3 - 2
src/main/java/com/genersoft/iot/vmp/service/IDeviceService.java

@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.service;
 
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
+import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
 import com.genersoft.iot.vmp.gb28181.bean.SyncStatus;
 import com.genersoft.iot.vmp.vmanager.bean.BaseTree;
 import com.genersoft.iot.vmp.vmanager.bean.ResourceBaceInfo;
@@ -18,13 +19,13 @@ public interface IDeviceService {
      * 设备上线
      * @param device 设备信息
      */
-    void online(Device device);
+    void online(Device device, SipTransactionInfo sipTransactionInfo);
 
     /**
      * 设备下线
      * @param deviceId 设备编号
      */
-    void offline(String deviceId);
+    void offline(String deviceId, String reason);
 
     /**
      * 添加目录订阅

+ 8 - 1
src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java

@@ -6,6 +6,7 @@ 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.InviteTimeOutCallback;
+import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
 import com.github.pagehelper.PageInfo;
 
 import javax.sip.InvalidArgumentException;
@@ -34,11 +35,17 @@ public interface IPlatformService {
      */
     boolean add(ParentPlatform parentPlatform);
 
+    /**
+     * 添加级联平台
+     * @param parentPlatform 级联平台
+     */
+    boolean update(ParentPlatform parentPlatform);
+
     /**
      * 平台上线
      * @param parentPlatform 平台信息
      */
-    void online(ParentPlatform parentPlatform);
+    void online(ParentPlatform parentPlatform, SipTransactionInfo sipTransactionInfo);
 
     /**
      * 平台离线

+ 12 - 4
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java

@@ -96,7 +96,7 @@ public class DeviceServiceImpl implements IDeviceService {
     private ZLMRESTfulUtils zlmresTfulUtils;
 
     @Override
-    public void online(Device device) {
+    public void online(Device device, SipTransactionInfo sipTransactionInfo) {
         logger.info("[设备上线] deviceId:{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort());
         Device deviceInRedis = redisCatchStorage.getDevice(device.getDeviceId());
         Device deviceInDb = deviceMapper.getDeviceByDeviceId(device.getDeviceId());
@@ -111,6 +111,14 @@ public class DeviceServiceImpl implements IDeviceService {
             // 默认心跳间隔60
             device.setKeepaliveIntervalTime(60);
         }
+        if (sipTransactionInfo != null) {
+            device.setSipTransactionInfo(sipTransactionInfo);
+        }else {
+            if (deviceInRedis != null) {
+                device.setSipTransactionInfo(deviceInRedis.getSipTransactionInfo());
+            }
+        }
+
         // 第一次上线 或则设备之前是离线状态--进行通道同步和设备信息查询
         if (device.getCreateTime() == null) {
             device.setOnline(1);
@@ -163,12 +171,12 @@ public class DeviceServiceImpl implements IDeviceService {
         // 刷新过期任务
         String registerExpireTaskKey = VideoManagerConstants.REGISTER_EXPIRE_TASK_KEY_PREFIX + device.getDeviceId();
         // 如果第一次注册那么必须在60 * 3时间内收到一个心跳,否则设备离线
-        dynamicTask.startDelay(registerExpireTaskKey, ()-> offline(device.getDeviceId()), device.getKeepaliveIntervalTime() * 1000 * 3);
+        dynamicTask.startDelay(registerExpireTaskKey, ()-> offline(device.getDeviceId(), "首次注册后未能收到心跳"), device.getKeepaliveIntervalTime() * 1000 * 3);
     }
 
     @Override
-    public void offline(String deviceId) {
-        logger.error("[设备离线], device:{}", deviceId);
+    public void offline(String deviceId, String reason) {
+        logger.error("[设备离线],{}, device:{}", reason, deviceId);
         Device device = deviceMapper.getDeviceByDeviceId(deviceId);
         if (device == null) {
             return;

+ 12 - 2
src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java

@@ -40,6 +40,9 @@ public class GbStreamServiceImpl implements IGbStreamService {
     @Autowired
     private PlatformGbStreamMapper platformGbStreamMapper;
 
+    @Autowired
+    private SubscribeHolder subscribeHolder;
+
     @Autowired
     private ParentPlatformMapper platformMapper;
 
@@ -73,16 +76,23 @@ public class GbStreamServiceImpl implements IGbStreamService {
         }
         try {
             List<DeviceChannel> deviceChannelList = new ArrayList<>();
-            for (GbStream gbStream : gbStreams) {
+
+
+            for (int i = 0; i < gbStreams.size(); i++) {
+                GbStream gbStream = gbStreams.get(i);
                 gbStream.setCatalogId(catalogId);
                 gbStream.setPlatformId(platformId);
                 // TODO 修改为批量提交
                 platformGbStreamMapper.add(gbStream);
+                logger.info("[关联通道]直播流通道 平台:{}, 共需关联通道数:{}, 已关联:{}", platformId, gbStreams.size(), i + 1);
                 DeviceChannel deviceChannelListByStream = getDeviceChannelListByStreamWithStatus(gbStream, catalogId, parentPlatform);
                 deviceChannelList.add(deviceChannelListByStream);
             }
             dataSourceTransactionManager.commit(transactionStatus);     //手动提交
-            eventPublisher.catalogEventPublish(platformId, deviceChannelList, CatalogEvent.ADD);
+            if (subscribeHolder.getCatalogSubscribe(platformId) != null) {
+                eventPublisher.catalogEventPublish(platformId, deviceChannelList, CatalogEvent.ADD);
+            }
+
             result = true;
         }catch (Exception e) {
             logger.error("批量保存流与平台的关系时错误", e);

+ 29 - 0
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java

@@ -11,6 +11,7 @@ import com.genersoft.iot.vmp.conf.exception.ControllerException;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 import com.genersoft.iot.vmp.gb28181.session.SsrcConfig;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
+import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils;
 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
 import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
@@ -38,6 +39,7 @@ import org.springframework.transaction.TransactionDefinition;
 import org.springframework.transaction.TransactionStatus;
 import org.springframework.util.ObjectUtils;
 
+import java.io.File;
 import java.time.LocalDateTime;
 import java.util.*;
 
@@ -63,6 +65,9 @@ public class MediaServerServiceImpl implements IMediaServerService {
     @Autowired
     private UserSetting userSetting;
 
+    @Autowired
+    private AssistRESTfulUtils assistRESTfulUtils;
+
     @Autowired
     private ZLMRESTfulUtils zlmresTfulUtils;
 
@@ -409,13 +414,27 @@ public class MediaServerServiceImpl implements IMediaServerService {
         }
         RedisUtil.set(key, serverItem);
         resetOnlineServerItem(serverItem);
+
+
         if (serverItem.isAutoConfig()) {
+            // 查看assist服务的录像路径配置
+            if (serverItem.getRecordAssistPort() > 0 && userSetting.getRecordPath() == null) {
+                JSONObject info = assistRESTfulUtils.getInfo(serverItem, null);
+                if (info != null && info.getInteger("code") != null && info.getInteger("code") == 0 ) {
+                    JSONObject dataJson = info.getJSONObject("data");
+                    if (dataJson != null) {
+                        String recordPath = dataJson.getString("record");
+                        userSetting.setRecordPath(recordPath);
+                    }
+                }
+            }
             setZLMConfig(serverItem, "0".equals(zlmServerConfig.getHookEnable()));
         }
         final String zlmKeepaliveKey = zlmKeepaliveKeyPrefix + serverItem.getId();
         dynamicTask.stop(zlmKeepaliveKey);
         dynamicTask.startDelay(zlmKeepaliveKey, new KeepAliveTimeoutRunnable(serverItem), (Math.getExponent(serverItem.getHookAliveInterval()) + 5) * 1000);
         publisher.zlmOnlineEventPublish(serverItem.getId());
+
         logger.info("[ZLM] 连接成功 {} - {}:{} ",
                 zlmServerConfig.getGeneralMediaServerId(), zlmServerConfig.getIp(), zlmServerConfig.getHttpPort());
     }
@@ -549,6 +568,9 @@ public class MediaServerServiceImpl implements IMediaServerService {
 
         Map<String, Object> param = new HashMap<>();
         param.put("api.secret",mediaServerItem.getSecret()); // -profile:v Baseline
+        if (mediaServerItem.getRtspPort() != 0) {
+            param.put("ffmpeg.snap", "%s -rtsp_transport tcp -i %s -y -f mjpeg -t 0.001 %s");
+        }
         param.put("hook.enable","1");
         param.put("hook.on_flow_report","");
         param.put("hook.on_play",String.format("%s/on_play", hookPrex));
@@ -583,6 +605,13 @@ public class MediaServerServiceImpl implements IMediaServerService {
             param.put("rtp_proxy.port_range", mediaServerItem.getRtpPortRange().replace(",", "-"));
         }
 
+        if (userSetting.getRecordPath() != null) {
+            File recordPathFile = new File(userSetting.getRecordPath());
+            File mp4SavePathFile = recordPathFile.getParentFile().getAbsoluteFile();
+            param.put("protocol.mp4_save_path", mp4SavePathFile.getAbsoluteFile());
+            param.put("record.appName", recordPathFile.getName());
+        }
+
         JSONObject responseJSON = zlmresTfulUtils.setServerConfig(mediaServerItem, param);
 
         if (responseJSON != null && responseJSON.getInteger("code") == 0) {

+ 52 - 12
src/main/java/com/genersoft/iot/vmp/service/impl/PlatformChannelServiceImpl.java

@@ -1,9 +1,6 @@
 package com.genersoft.iot.vmp.service.impl;
 
-import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
-import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
-import com.genersoft.iot.vmp.gb28181.bean.PlatformCatalog;
-import com.genersoft.iot.vmp.gb28181.bean.TreeType;
+import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
 import com.genersoft.iot.vmp.service.IPlatformChannelService;
@@ -15,7 +12,10 @@ import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionStatus;
 import org.springframework.util.ObjectUtils;
 
 import java.util.ArrayList;
@@ -34,6 +34,16 @@ public class PlatformChannelServiceImpl implements IPlatformChannelService {
     @Autowired
     private PlatformChannelMapper platformChannelMapper;
 
+    @Autowired
+    TransactionDefinition transactionDefinition;
+
+    @Autowired
+    DataSourceTransactionManager dataSourceTransactionManager;
+
+    @Autowired
+    private SubscribeHolder subscribeHolder;
+
+
     @Autowired
     private DeviceChannelMapper deviceChannelMapper;
 
@@ -69,17 +79,47 @@ public class PlatformChannelServiceImpl implements IPlatformChannelService {
         }
         List<ChannelReduce> channelReducesToAdd = new ArrayList<>(deviceAndChannels.values());
         // 对剩下的数据进行存储
-        int result = 0;
+        int allCount = 0;
+        boolean result = false;
+        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
+        int limitCount = 300;
         if (channelReducesToAdd.size() > 0) {
-            result = platformChannelMapper.addChannels(platformId, channelReducesToAdd);
-            // TODO 后续给平台增加控制开关以控制是否响应目录订阅
-            List<DeviceChannel> deviceChannelList = getDeviceChannelListByChannelReduceList(channelReducesToAdd, catalogId, platform);
-            if (deviceChannelList != null) {
-                eventPublisher.catalogEventPublish(platformId, deviceChannelList, CatalogEvent.ADD);
+            if (channelReducesToAdd.size() > limitCount) {
+                for (int i = 0; i < channelReducesToAdd.size(); i += limitCount) {
+                    int toIndex = i + limitCount;
+                    if (i + limitCount > channelReducesToAdd.size()) {
+                        toIndex = channelReducesToAdd.size();
+                    }
+                    int count = platformChannelMapper.addChannels(platformId, channelReducesToAdd.subList(i, toIndex));
+                    result = result || count < 0;
+                    allCount += count;
+                    logger.info("[关联通道]国标通道 平台:{}, 共需关联通道数:{}, 已关联:{}", platformId, channelReducesToAdd.size(), toIndex);
+                }
+            }else {
+                allCount = platformChannelMapper.addChannels(platformId, channelReducesToAdd);
+                result = result || allCount < 0;
+                logger.info("[关联通道]国标通道 平台:{}, 关联通道数:{}", platformId, channelReducesToAdd.size());
             }
-        }
 
-        return result;
+            if (result) {
+                //事务回滚
+                dataSourceTransactionManager.rollback(transactionStatus);
+                allCount = 0;
+            }else {
+                logger.info("[关联通道]国标通道 平台:{}, 正在存入数据库", platformId);
+                dataSourceTransactionManager.commit(transactionStatus);
+
+            }
+            SubscribeInfo catalogSubscribe = subscribeHolder.getCatalogSubscribe(platformId);
+            if (catalogSubscribe != null) {
+                List<DeviceChannel> deviceChannelList = getDeviceChannelListByChannelReduceList(channelReducesToAdd, catalogId, platform);
+                if (deviceChannelList != null) {
+                    eventPublisher.catalogEventPublish(platformId, deviceChannelList, CatalogEvent.ADD);
+                }
+            }
+            logger.info("[关联通道]国标通道 平台:{}, 存入数据库成功", platformId);
+        }
+        return allCount;
     }
 
     private List<DeviceChannel> getDeviceChannelListByChannelReduceList(List<ChannelReduce> channelReduces, String catalogId, ParentPlatform platform) {

+ 97 - 18
src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java

@@ -21,8 +21,8 @@ import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
 import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback;
 import com.genersoft.iot.vmp.service.bean.SSRCInfo;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.dao.GbStreamMapper;
-import com.genersoft.iot.vmp.storager.dao.ParentPlatformMapper;
+import com.genersoft.iot.vmp.storager.dao.*;
+import com.genersoft.iot.vmp.utils.DateUtil;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import org.slf4j.Logger;
@@ -53,6 +53,15 @@ public class PlatformServiceImpl implements IPlatformService {
     @Autowired
     private ParentPlatformMapper platformMapper;
 
+    @Autowired
+    private PlatformCatalogMapper catalogMapper;
+
+    @Autowired
+    private PlatformChannelMapper platformChannelMapper;
+
+    @Autowired
+    private PlatformGbStreamMapper platformGbStreamMapper;
+
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
 
@@ -135,36 +144,106 @@ public class PlatformServiceImpl implements IPlatformService {
     }
 
     @Override
-    public void online(ParentPlatform parentPlatform) {
-        logger.info("[国标级联]:{}, 平台上线/更新注册", parentPlatform.getServerGBId());
+    public boolean update(ParentPlatform parentPlatform) {
+        logger.info("[国标级联]更新平台 {}", parentPlatform.getDeviceGBId());
+        parentPlatform.setCharacterSet(parentPlatform.getCharacterSet().toUpperCase());
+        ParentPlatform parentPlatformOld = platformMapper.getParentPlatById(parentPlatform.getId());
+        ParentPlatformCatch parentPlatformCatchOld = redisCatchStorage.queryPlatformCatchInfo(parentPlatformOld.getServerGBId());
+        parentPlatform.setUpdateTime(DateUtil.getNow());
+        if (!parentPlatformOld.getTreeType().equals(parentPlatform.getTreeType())) {
+            // 目录结构发生变化,清空之前的关联关系
+            logger.info("保存平台{}时发现目录结构变化,清空关联关系", parentPlatform.getDeviceGBId());
+            catalogMapper.delByPlatformId(parentPlatformOld.getServerGBId());
+            platformChannelMapper.delByPlatformId(parentPlatformOld.getServerGBId());
+            platformGbStreamMapper.delByPlatformId(parentPlatformOld.getServerGBId());
+        }
+
+
+        // 停止心跳定时
+        final String keepaliveTaskKey = KEEPALIVE_KEY_PREFIX + parentPlatformOld.getServerGBId();
+        dynamicTask.stop(keepaliveTaskKey);
+        // 停止注册定时
+        final String registerTaskKey = REGISTER_KEY_PREFIX + parentPlatformOld.getServerGBId();
+        dynamicTask.stop(registerTaskKey);
+        // 注销旧的
+        try {
+            if (parentPlatformOld.isStatus()) {
+                logger.info("保存平台{}时发现救平台在线,发送注销命令", parentPlatform.getDeviceGBId());
+                commanderForPlatform.unregister(parentPlatformOld, parentPlatformCatchOld.getSipTransactionInfo(), null, eventResult -> {
+                    logger.info("[国标级联] 注销成功, 平台:{}", parentPlatformOld.getServerGBId());
+                });
+            }
+
+        } catch (InvalidArgumentException | ParseException | SipException e) {
+            logger.error("[命令发送失败] 国标级联 注销: {}", e.getMessage());
+        }
+
+        // 更新数据库
+        if (parentPlatform.getCatalogGroup() == 0) {
+            parentPlatform.setCatalogGroup(1);
+        }
+        if (parentPlatform.getAdministrativeDivision() == null) {
+            parentPlatform.setAdministrativeDivision(parentPlatform.getAdministrativeDivision());
+        }
+
+        platformMapper.updateParentPlatform(parentPlatform);
+        // 更新redis
+        redisCatchStorage.delPlatformCatchInfo(parentPlatformOld.getServerGBId());
+        ParentPlatformCatch parentPlatformCatch = new ParentPlatformCatch();
+        parentPlatformCatch.setParentPlatform(parentPlatform);
+        parentPlatformCatch.setId(parentPlatform.getServerGBId());
+        redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
+        // 注册
+        if (parentPlatform.isEnable()) {
+            // 保存时启用就发送注册
+            // 注册成功时由程序直接调用了online方法
+            try {
+                commanderForPlatform.register(parentPlatform, eventResult -> {
+                    logger.info("[国标级联] {},添加向上级注册失败,请确定上级平台可用时重新保存", parentPlatform.getServerGBId());
+                }, null);
+            } catch (InvalidArgumentException | ParseException | SipException e) {
+                logger.error("[命令发送失败] 国标级联: {}", e.getMessage());
+            }
+        }
+        // 重新开启定时注册, 使用续订消息
+        // 重新开始心跳保活
+
+
+        return false;
+    }
+
+
+    @Override
+    public void online(ParentPlatform parentPlatform, SipTransactionInfo sipTransactionInfo) {
+        logger.info("[国标级联]:{}, 平台上线", parentPlatform.getServerGBId());
         platformMapper.updateParentPlatformStatus(parentPlatform.getServerGBId(), true);
         ParentPlatformCatch parentPlatformCatch = redisCatchStorage.queryPlatformCatchInfo(parentPlatform.getServerGBId());
-        if (parentPlatformCatch != null) {
-            parentPlatformCatch.getParentPlatform().setStatus(true);
-            redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
-        }else {
+        if (parentPlatformCatch == null) {
             parentPlatformCatch = new ParentPlatformCatch();
             parentPlatformCatch.setParentPlatform(parentPlatform);
             parentPlatformCatch.setId(parentPlatform.getServerGBId());
             parentPlatform.setStatus(true);
             parentPlatformCatch.setParentPlatform(parentPlatform);
-            redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
         }
 
+        parentPlatformCatch.getParentPlatform().setStatus(true);
+        parentPlatformCatch.setSipTransactionInfo(sipTransactionInfo);
+        redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
+
         final String registerTaskKey = REGISTER_KEY_PREFIX + parentPlatform.getServerGBId();
         if (!dynamicTask.isAlive(registerTaskKey)) {
+            logger.info("[国标级联]:{}, 添加定时注册任务", parentPlatform.getServerGBId());
             // 添加注册任务
             dynamicTask.startCron(registerTaskKey,
                 // 注册失败(注册成功时由程序直接调用了online方法)
-                ()-> {
-                    registerTask(parentPlatform);
-                },
-                (parentPlatform.getExpires() - 10) *1000);
+                ()-> registerTask(parentPlatform, sipTransactionInfo),
+                    parentPlatform.getExpires() * 1000);
         }
 
 
         final String keepaliveTaskKey = KEEPALIVE_KEY_PREFIX + parentPlatform.getServerGBId();
         if (!dynamicTask.contains(keepaliveTaskKey)) {
+            logger.info("[国标级联]:{}, 添加定时心跳任务", parentPlatform.getServerGBId());
             // 添加心跳任务
             dynamicTask.startCron(keepaliveTaskKey,
                     ()-> {
@@ -205,11 +284,11 @@ public class PlatformServiceImpl implements IPlatformService {
                             logger.error("[命令发送失败] 国标级联 发送心跳: {}", e.getMessage());
                         }
                     },
-                    (parentPlatform.getKeepTimeout() - 10)*1000);
+                    (parentPlatform.getKeepTimeout())*1000);
         }
     }
 
-    private void registerTask(ParentPlatform parentPlatform){
+    private void registerTask(ParentPlatform parentPlatform, SipTransactionInfo sipTransactionInfo){
         try {
             // 设置超时重发, 后续从底层支持消息重发
             String key = KEEPALIVE_KEY_PREFIX + parentPlatform.getServerGBId() + "_timeout";
@@ -217,10 +296,10 @@ public class PlatformServiceImpl implements IPlatformService {
                 return;
             }
             dynamicTask.startDelay(key, ()->{
-                registerTask(parentPlatform);
+                registerTask(parentPlatform, sipTransactionInfo);
             }, 1000);
-            logger.info("[国标级联] 平台:{}注册即将到期,重新注册", parentPlatform.getServerGBId());
-            commanderForPlatform.register(parentPlatform, eventResult -> {
+            logger.info("[国标级联] 平台:{}注册即将到期,开始续订", parentPlatform.getServerGBId());
+            commanderForPlatform.register(parentPlatform, sipTransactionInfo,  eventResult -> {
                 dynamicTask.stop(key);
                 offline(parentPlatform, false);
             },eventResult -> {

+ 73 - 16
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java

@@ -438,7 +438,12 @@ public class PlayServiceImpl implements IPlayService {
                 onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId);
                 hookEvent.response(mediaServerItemInuse, response);
                 logger.info("[点播成功] deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
-                String streamUrl = String.format("http://127.0.0.1:%s/%s/%s.live.flv", mediaServerItemInuse.getHttpPort(), "rtp", ssrcInfo.getStream());
+                String streamUrl;
+                if (mediaServerItemInuse.getRtspPort() != 0) {
+                    streamUrl = String.format("rtsp://127.0.0.1:%s/%s/%s", mediaServerItemInuse.getRtspPort(), "rtp",  ssrcInfo.getStream());
+                }else {
+                    streamUrl = String.format("http://127.0.0.1:%s/%s/%s.live.mp4", mediaServerItemInuse.getHttpPort(), "rtp",  ssrcInfo.getStream());
+                }
                 String path = "snap";
                 String fileName = device.getDeviceId() + "_" + channelId + ".jpg";
                 // 请求截图
@@ -806,23 +811,75 @@ public class PlayServiceImpl implements IPlayService {
             hookCallBack.call(downloadResult);
             streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
         };
-
+        InviteStreamCallback hookEvent = (InviteStreamInfo inviteStreamInfo) -> {
+            logger.info("收到订阅消息: " + inviteStreamInfo.getCallId());
+            dynamicTask.stop(downLoadTimeOutTaskKey);
+            StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);
+            streamInfo.setStartTime(startTime);
+            streamInfo.setEndTime(endTime);
+            redisCatchStorage.startDownload(streamInfo, inviteStreamInfo.getCallId());
+            downloadResult.setCode(ErrorCode.SUCCESS.getCode());
+            downloadResult.setMsg(ErrorCode.SUCCESS.getMsg());
+            downloadResult.setData(streamInfo);
+            downloadResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem());
+            downloadResult.setResponse(inviteStreamInfo.getResponse());
+            hookCallBack.call(downloadResult);
+        };
         try {
             cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, infoCallBack,
-                    inviteStreamInfo -> {
-                        logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString());
-                        dynamicTask.stop(downLoadTimeOutTaskKey);
-                        StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);
-                        streamInfo.setStartTime(startTime);
-                        streamInfo.setEndTime(endTime);
-                        redisCatchStorage.startDownload(streamInfo, inviteStreamInfo.getCallId());
-                        downloadResult.setCode(ErrorCode.SUCCESS.getCode());
-                        downloadResult.setMsg(ErrorCode.SUCCESS.getMsg());
-                        downloadResult.setData(streamInfo);
-                        downloadResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem());
-                        downloadResult.setResponse(inviteStreamInfo.getResponse());
-                        hookCallBack.call(downloadResult);
-                    }, errorEvent);
+                    hookEvent, errorEvent, eventResult ->
+                    {
+                        if (eventResult.type == SipSubscribe.EventResultType.response) {
+                            ResponseEvent responseEvent = (ResponseEvent) eventResult.event;
+                            String contentString = new String(responseEvent.getResponse().getRawContent());
+                            // 获取ssrc
+                            int ssrcIndex = contentString.indexOf("y=");
+                            // 检查是否有y字段
+                            if (ssrcIndex >= 0) {
+                                //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
+                                String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
+                                // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
+                                if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
+                                    return;
+                                }
+                                logger.info("[回放消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
+                                if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
+                                    logger.info("[回放消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
+
+                                    if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
+                                        // ssrc 不可用
+                                        // 释放ssrc
+                                        mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+                                        streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
+                                        eventResult.msg = "下级自定义了ssrc,但是此ssrc不可用";
+                                        eventResult.statusCode = 400;
+                                        errorEvent.response(eventResult);
+                                        return;
+                                    }
+
+                                    // 单端口模式streamId也有变化,需要重新设置监听
+                                    if (!mediaServerItem.isRtpEnable()) {
+                                        // 添加订阅
+                                        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
+                                        subscribe.removeSubscribe(hookSubscribe);
+                                        hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
+                                        subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
+                                            logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
+                                            dynamicTask.stop(downLoadTimeOutTaskKey);
+                                            // hook响应
+                                            onPublishHandlerForPlayback(mediaServerItemInUse, response, device.getDeviceId(), channelId, hookCallBack);
+                                            hookEvent.call(new InviteStreamInfo(mediaServerItem, null, eventResult.callId, "rtp", ssrcInfo.getStream()));
+                                        });
+                                    }
+                                    // 关闭rtp server
+                                    mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
+                                    // 重新开启ssrc server
+                                    mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), true, ssrcInfo.getPort());
+                                }
+                            }
+                        }
+
+                    });
         } catch (InvalidArgumentException | SipException | ParseException e) {
             logger.error("[命令发送失败] 录像下载: {}", e.getMessage());
 

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java

@@ -201,7 +201,7 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
             dataSourceTransactionManager.commit(transactionStatus);     //手动提交
             result = true;
         }catch (Exception e) {
-            e.printStackTrace();
+            logger.error("未处理的异常 ", e);
             dataSourceTransactionManager.rollback(transactionStatus);
         }
         return result;

+ 45 - 9
src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.service.redisMsg;
 
 import com.alibaba.fastjson2.JSON;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
@@ -44,13 +45,17 @@ public class RedisAlarmMsgListener implements MessageListener {
     @Autowired
     private ThreadPoolTaskExecutor taskExecutor;
 
+    @Autowired
+    private UserSetting userSetting;
+
     @Override
     public void onMessage(@NotNull Message message, byte[] bytes) {
+        // 消息示例:  PUBLISH alarm_receive '{ "gbId": "", "alarmSn": 1, "alarmType": "111", "alarmDescription": "222", }'
         logger.info("收到来自REDIS的ALARM通知: {}", new String(message.getBody()));
         boolean isEmpty = taskQueue.isEmpty();
         taskQueue.offer(message);
         if (isEmpty) {
-            logger.info("[线程池信息]活动线程数:{}, 最大线程数: {}", taskExecutor.getActiveCount(), taskExecutor.getMaxPoolSize());
+//            logger.info("[线程池信息]活动线程数:{}, 最大线程数: {}", taskExecutor.getActiveCount(), taskExecutor.getMaxPoolSize());
             taskExecutor.execute(() -> {
                 while (!taskQueue.isEmpty()) {
                     Message msg = taskQueue.poll();
@@ -69,22 +74,52 @@ public class RedisAlarmMsgListener implements MessageListener {
                         deviceAlarm.setAlarmMethod("" + alarmChannelMessage.getAlarmSn());
                         deviceAlarm.setAlarmType("" + alarmChannelMessage.getAlarmType());
                         deviceAlarm.setAlarmPriority("1");
-                        deviceAlarm.setAlarmTime(DateUtil.getNowForISO8601());
+                        deviceAlarm.setAlarmTime(DateUtil.getNow());
                         deviceAlarm.setLongitude(0);
                         deviceAlarm.setLatitude(0);
 
                         if (ObjectUtils.isEmpty(gbId)) {
-                            // 发送给所有的上级
-                            List<ParentPlatform> parentPlatforms = storage.queryEnableParentPlatformList(true);
-                            if (parentPlatforms.size() > 0) {
-                                for (ParentPlatform parentPlatform : parentPlatforms) {
+                            if (userSetting.getSendToPlatformsWhenIdLost()) {
+                                // 发送给所有的上级
+                                List<ParentPlatform> parentPlatforms = storage.queryEnableParentPlatformList(true);
+                                if (parentPlatforms.size() > 0) {
+                                    for (ParentPlatform parentPlatform : parentPlatforms) {
+                                        try {
+                                            deviceAlarm.setChannelId(parentPlatform.getDeviceGBId());
+                                            commanderForPlatform.sendAlarmMessage(parentPlatform, deviceAlarm);
+                                        } catch (SipException | InvalidArgumentException | ParseException e) {
+                                            logger.error("[命令发送失败] 国标级联 发送报警: {}", e.getMessage());
+                                        }
+                                    }
+                                }
+                            }else {
+                                // 获取开启了消息推送的设备和平台
+                                List<ParentPlatform> parentPlatforms = storage.queryEnablePlatformListWithAsMessageChannel();
+                                if (parentPlatforms.size() > 0) {
+                                    for (ParentPlatform parentPlatform : parentPlatforms) {
+                                        try {
+                                            deviceAlarm.setChannelId(parentPlatform.getDeviceGBId());
+                                            commanderForPlatform.sendAlarmMessage(parentPlatform, deviceAlarm);
+                                        } catch (SipException | InvalidArgumentException | ParseException e) {
+                                            logger.error("[命令发送失败] 国标级联 发送报警: {}", e.getMessage());
+                                        }
+                                    }
+                                }
+
+                            }
+                            // 获取开启了消息推送的设备和平台
+                            List<Device> devices = storage.queryDeviceWithAsMessageChannel();
+                            if (devices.size() > 0) {
+                                for (Device device : devices) {
                                     try {
-                                        commanderForPlatform.sendAlarmMessage(parentPlatform, deviceAlarm);
-                                    } catch (SipException | InvalidArgumentException | ParseException e) {
-                                        logger.error("[命令发送失败] 国标级联 发送报警: {}", e.getMessage());
+                                        deviceAlarm.setChannelId(device.getDeviceId());
+                                        commander.sendAlarmMessage(device, deviceAlarm);
+                                    } catch (InvalidArgumentException | SipException | ParseException e) {
+                                        logger.error("[命令发送失败] 发送报警: {}", e.getMessage());
                                     }
                                 }
                             }
+
                         }else {
                             Device device = storage.queryVideoDevice(gbId);
                             ParentPlatform platform = storage.queryParentPlatByServerGBId(gbId);
@@ -105,6 +140,7 @@ public class RedisAlarmMsgListener implements MessageListener {
                             }
                         }
                     }catch (Exception e) {
+                        logger.error("未处理的异常 ", e);
                         logger.warn("[REDIS的ALARM通知] 发现未处理的异常, {}",e.getMessage());
                     }
                 }

+ 2 - 1
src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGbPlayMsgListener.java

@@ -202,7 +202,8 @@ public class RedisGbPlayMsgListener implements MessageListener {
 
                         }
                     }catch (Exception e) {
-                        logger.warn("[RedisGbPlayMsg] 发现未处理的异常, {}",e.getMessage());
+                        logger.warn("[RedisGbPlayMsg] 发现未处理的异常, \r\n{}", JSON.toJSONString(message));
+                        logger.error("[RedisGbPlayMsg] 异常内容: ", e);
                     }
                 }
             });

+ 2 - 1
src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGpsMsgListener.java

@@ -53,7 +53,8 @@ public class RedisGpsMsgListener implements MessageListener {
                         // 只是放入redis缓存起来
                         redisCatchStorage.updateGpsMsgInfo(gpsMsgInfo);
                     }catch (Exception e) {
-                        logger.warn("[REDIS的ALARM通知] 发现未处理的异常, {}",e.getMessage());
+                        logger.warn("[REDIS的ALARM通知] 发现未处理的异常, \r\n{}", JSON.toJSONString(message));
+                        logger.error("[REDIS的ALARM通知] 异常内容: ", e);
                     }
                 }
             });

+ 2 - 1
src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamResponseListener.java

@@ -58,7 +58,8 @@ public class RedisPushStreamResponseListener implements MessageListener {
                             responseEvents.get(response.getApp() + response.getStream()).run(response);
                         }
                     }catch (Exception e) {
-                        logger.warn("[REDIS的ALARM通知] 发现未处理的异常, {}",e.getMessage());
+                        logger.warn("[REDIS消息-请求推流结果] 发现未处理的异常, \r\n{}", JSON.toJSONString(message));
+                        logger.error("[REDIS消息-请求推流结果] 异常内容: ", e);
                     }
                 }
             });

+ 2 - 1
src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamStatusListMsgListener.java

@@ -95,7 +95,8 @@ public class RedisPushStreamStatusListMsgListener implements MessageListener {
                             gbStreamService.updateGbIdOrName(streamPushItemForUpdate);
                         }
                     }catch (Exception e) {
-                        logger.warn("[REDIS的ALARM通知] 发现未处理的异常, {}",e.getMessage());
+                        logger.warn("[REDIS消息-推流设备列表更新] 发现未处理的异常, \r\n{}", JSON.toJSONString(message));
+                        logger.error("[REDIS消息-推流设备列表更新] 异常内容: ", e);
                     }
                 }
             });

+ 2 - 1
src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamStatusMsgListener.java

@@ -79,7 +79,8 @@ public class RedisPushStreamStatusMsgListener implements MessageListener, Applic
                             streamPushService.online(statusChangeFromPushStream.getOnlineStreams());
                         }
                     }catch (Exception e) {
-                        logger.warn("[REDIS的ALARM通知] 发现未处理的异常, {}",e.getMessage());
+                        logger.warn("[REDIS消息-推流设备状态变化] 发现未处理的异常, \r\n{}", JSON.toJSONString(message));
+                        logger.error("[REDIS消息-推流设备状态变化] 异常内容: ", e);
                     }
                 }
             });

+ 2 - 1
src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisStreamMsgListener.java

@@ -82,7 +82,8 @@ public class RedisStreamMsgListener implements MessageListener {
                             zlmMediaListManager.removeMedia(app, stream);
                         }
                     }catch (Exception e) {
-                        logger.warn("[REDIS的ALARM通知] 发现未处理的异常, {}",e.getMessage());
+                        logger.warn("[REDIS消息-流变化] 发现未处理的异常, \r\n{}", JSON.toJSONString(message));
+                        logger.error("[REDIS消息-流变化] 异常内容: ", e);
                     }
                 }
             });

+ 8 - 1
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java

@@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
 import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
 import com.genersoft.iot.vmp.storager.dao.dto.ChannelSourceInfo;
 import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
+import com.genersoft.iot.vmp.web.gb28181.dto.DeviceChannelExtend;
 import com.github.pagehelper.PageInfo;
 
 import java.util.List;
@@ -58,7 +59,7 @@ public interface IVideoManagerStorage {
 	 */
 	public PageInfo<DeviceChannel> queryChannelsByDeviceId(String deviceId, String query, Boolean hasSubChannel, Boolean online, Boolean catalogUnderDevice, int page, int count);
 	
-	public List<DeviceChannel> queryChannelsByDeviceIdWithStartAndLimit(String deviceId, String query, Boolean hasSubChannel, Boolean online, int start, int limit,List<String> channelIds);
+	public List<DeviceChannelExtend> queryChannelsByDeviceIdWithStartAndLimit(String deviceId, List<String> channelIds, String query, Boolean hasSubChannel, Boolean online, int start, int limit);
 
 
 	/**
@@ -374,4 +375,10 @@ public interface IVideoManagerStorage {
 	void cleanContentForPlatform(String serverGBId);
 
 	List<DeviceChannel> queryChannelWithCatalog(String serverGBId);
+
+	List<DeviceChannelExtend> queryChannelsByDeviceId(String serial, List<String> channelIds, Boolean online);
+
+	List<ParentPlatform> queryEnablePlatformListWithAsMessageChannel();
+
+	List<Device> queryDeviceWithAsMessageChannel();
 }

+ 53 - 25
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java

@@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannelInPlatform;
 import com.genersoft.iot.vmp.vmanager.bean.ResourceBaceInfo;
 import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
+import com.genersoft.iot.vmp.web.gb28181.dto.DeviceChannelExtend;
 import org.apache.ibatis.annotations.*;
 import org.springframework.stereotype.Repository;
 
@@ -82,7 +83,56 @@ public interface DeviceChannelMapper {
             "</foreach> </if>" +
             "ORDER BY dc.channelId " +
             " </script>"})
-    List<DeviceChannel> queryChannels(String deviceId, String parentChannelId, String query, Boolean hasSubChannel, Boolean online,List<String> channelIds);
+    List<DeviceChannel> queryChannels(String deviceId, String parentChannelId, String query, Boolean hasSubChannel, Boolean online, List<String> channelIds);
+
+    @Select(value = {" <script>" +
+            "SELECT " +
+            "dc.*, " +
+            "de.name as deviceName, " +
+            "de.online as deviceOnline " +
+            "from " +
+            "device_channel dc " +
+            "LEFT JOIN device de ON dc.deviceId = de.deviceId " +
+            "WHERE 1=1" +
+            " <if test='deviceId != null'> AND dc.deviceId = #{deviceId} </if> " +
+            " <if test='query != null'> AND (dc.channelId LIKE '%${query}%' OR dc.name LIKE '%${query}%' OR dc.name LIKE '%${query}%')</if> " +
+            " <if test='parentChannelId != null'> AND dc.parentId=#{parentChannelId} </if> " +
+            " <if test='online == true' > AND dc.status=1</if>" +
+            " <if test='online == false' > AND dc.status=0</if>" +
+            " <if test='hasSubChannel == true' >  AND dc.subCount > 0 </if>" +
+            " <if test='hasSubChannel == false' >  AND dc.subCount = 0 </if>" +
+            "<if test='channelIds != null'> AND dc.channelId in <foreach item='item' index='index' collection='channelIds' open='(' separator=',' close=')'>" +
+            "#{item} " +
+            "</foreach> </if>" +
+            "ORDER BY dc.channelId ASC" +
+            " </script>"})
+    List<DeviceChannelExtend> queryChannelsWithDeviceInfo(String deviceId, String parentChannelId, String query, Boolean hasSubChannel, Boolean online, List<String> channelIds);
+
+
+    @Select(value = {" <script>" +
+            "SELECT " +
+            "dc.*, " +
+            "de.name as deviceName, " +
+            "de.online as deviceOnline " +
+            "from " +
+            "device_channel dc " +
+            "LEFT JOIN device de ON dc.deviceId = de.deviceId " +
+            "WHERE 1=1" +
+            " <if test='deviceId != null'> AND dc.deviceId = #{deviceId} </if> " +
+            " <if test='query != null'> AND (dc.channelId LIKE '%${query}%' OR dc.name LIKE '%${query}%' OR dc.name LIKE '%${query}%')</if> " +
+            " <if test='parentChannelId != null'> AND dc.parentId=#{parentChannelId} </if> " +
+            " <if test='online == true' > AND dc.status=1</if>" +
+            " <if test='online == false' > AND dc.status=0</if>" +
+            " <if test='hasSubChannel == true' >  AND dc.subCount > 0 </if>" +
+            " <if test='hasSubChannel == false' >  AND dc.subCount = 0 </if>" +
+            "<if test='channelIds != null'> AND dc.channelId in <foreach item='item' index='index' collection='channelIds' open='(' separator=',' close=')'>" +
+            "#{item} " +
+            "</foreach> </if>" +
+            "ORDER BY dc.channelId ASC " +
+            "Limit #{limit} OFFSET #{start}" +
+            " </script>"})
+    List<DeviceChannelExtend> queryChannelsByDeviceIdWithStartAndLimit(String deviceId,List<String> channelIds, String parentChannelId, String query,
+                                                                       Boolean hasSubChannel, Boolean online, int start, int limit);
 
     @Select("SELECT * FROM device_channel WHERE deviceId=#{deviceId} AND channelId=#{channelId}")
     DeviceChannel queryChannel(String deviceId, String channelId);
@@ -245,28 +295,6 @@ public interface DeviceChannelMapper {
     int batchUpdate(List<DeviceChannel> updateChannels);
 
 
-    @Select(value = {" <script>" +
-            "SELECT " +
-            "dc1.* " +
-            "from " +
-            "device_channel dc1 " +
-            "WHERE " +
-            "dc1.deviceId = #{deviceId} " +
-            " <if test='query != null'> AND (dc1.channelId LIKE concat('%',#{query},'%') OR dc1.name LIKE concat('%',#{query},'%') OR dc1.name LIKE concat('%',#{query},'%'))</if> " +
-            " <if test='parentChannelId != null'> AND dc1.parentId=#{parentChannelId} </if> " +
-            " <if test='online == true' > AND dc1.status=1</if>" +
-            " <if test='online == false' > AND dc1.status=0</if>" +
-            " <if test='hasSubChannel == true' >  AND dc1.subCount >0</if>" +
-            " <if test='hasSubChannel == false' >  AND dc1.subCount=0</if>" +
-            "<if test='channelIds != null'> AND dc1.channelId in <foreach item='item' index='index' collection='channelIds' open='(' separator=',' close=')'>" +
-            "#{item} " +
-            "</foreach> </if>" +
-            "ORDER BY dc1.channelId ASC " +
-            "Limit #{limit} OFFSET #{start}" +
-            " </script>"})
-    List<DeviceChannel> queryChannelsByDeviceIdWithStartAndLimit(String deviceId, String parentChannelId, String query,
-                                                                 Boolean hasSubChannel, Boolean online, int start, int limit,List<String> channelIds);
-
     @Select("SELECT * FROM device_channel WHERE deviceId=#{deviceId} AND status=1")
     List<DeviceChannel> queryOnlineChannelsByDeviceId(String deviceId);
 
@@ -316,10 +344,10 @@ public interface DeviceChannelMapper {
             "select * " +
             "from device_channel " +
             "where deviceId=#{deviceId}" +
-            " <if test='parentId != null and length != null' > and parentId = #{parentId} or left(channelId, #{parentId.length()}) = #{parentId} and length(channelId)=#{length} </if>" +
+            " <if test='parentId != null and length != null' > and parentId = #{parentId} or left(channelId, LENGTH(#{parentId})) = #{parentId} and length(channelId)=#{length} </if>" +
             " <if test='parentId == null and length != null' > and parentId = #{parentId} or length(channelId)=#{length} </if>" +
             " <if test='parentId == null and length == null' > and parentId = #{parentId} </if>" +
-            " <if test='parentId != null and length == null' > and parentId = #{parentId} or left(channelId, #{parentId.length()}) = #{parentId} </if>" +
+            " <if test='parentId != null and length == null' > and parentId = #{parentId} or left(channelId, LENGTH(#{parentId})) = #{parentId} </if>" +
             " </script>"})
     List<DeviceChannel> getChannelsWithCivilCodeAndLength(String deviceId, String parentId, Integer length);
 

+ 21 - 7
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java

@@ -39,9 +39,12 @@ public interface DeviceMapper {
             "mobilePositionSubmissionInterval," +
             "subscribeCycleForAlarm," +
             "ssrcCheck," +
+            "asMessageChannel," +
             "geoCoordSys," +
             "treeType," +
-            "online" +
+            "online," +
+            "mediaServerId," +
+            "(SELECT count(0) FROM device_channel WHERE deviceId=device.deviceId) as channelCount "+
             " FROM device WHERE deviceId = #{deviceId}")
     Device getDeviceByDeviceId(String deviceId);
 
@@ -70,6 +73,7 @@ public interface DeviceMapper {
                 "mobilePositionSubmissionInterval," +
                 "subscribeCycleForAlarm," +
                 "ssrcCheck," +
+                "asMessageChannel," +
                 "geoCoordSys," +
                 "treeType," +
                 "online" +
@@ -98,6 +102,7 @@ public interface DeviceMapper {
                 "#{mobilePositionSubmissionInterval}," +
                 "#{subscribeCycleForAlarm}," +
                 "#{ssrcCheck}," +
+                "#{asMessageChannel}," +
                 "#{geoCoordSys}," +
                 "#{treeType}," +
                 "#{online}" +
@@ -152,9 +157,11 @@ public interface DeviceMapper {
             "mobilePositionSubmissionInterval," +
             "subscribeCycleForAlarm," +
             "ssrcCheck," +
+            "asMessageChannel," +
             "geoCoordSys," +
             "treeType," +
             "online," +
+            "mediaServerId," +
             "(SELECT count(0) FROM device_channel WHERE deviceId=de.deviceId) as channelCount  FROM device de" +
             "<if test=\"online != null\"> where online=${online}</if>"+
             " </script>"
@@ -164,9 +171,6 @@ public interface DeviceMapper {
     @Delete("DELETE FROM device WHERE deviceId=#{deviceId}")
     int del(String deviceId);
 
-    @Update("UPDATE device SET online=0")
-    int outlineForAll();
-
     @Select("SELECT " +
             "deviceId, " +
             "coalesce(custom_name, name) as name, " +
@@ -192,6 +196,7 @@ public interface DeviceMapper {
             "mobilePositionSubmissionInterval," +
             "subscribeCycleForAlarm," +
             "ssrcCheck," +
+            "asMessageChannel," +
             "geoCoordSys," +
             "treeType," +
             "online " +
@@ -222,6 +227,7 @@ public interface DeviceMapper {
             "mobilePositionSubmissionInterval," +
             "subscribeCycleForAlarm," +
             "ssrcCheck," +
+            "asMessageChannel," +
             "geoCoordSys," +
             "treeType," +
             "online" +
@@ -243,12 +249,13 @@ public interface DeviceMapper {
             "<if test=\"mobilePositionSubmissionInterval != null\">, mobilePositionSubmissionInterval=#{mobilePositionSubmissionInterval}</if>" +
             "<if test=\"subscribeCycleForAlarm != null\">, subscribeCycleForAlarm=#{subscribeCycleForAlarm}</if>" +
             "<if test=\"ssrcCheck != null\">, ssrcCheck=#{ssrcCheck}</if>" +
+            "<if test=\"asMessageChannel != null\">, asMessageChannel=#{asMessageChannel}</if>" +
             "<if test=\"geoCoordSys != null\">, geoCoordSys=#{geoCoordSys}</if>" +
             "<if test=\"treeType != null\">, treeType=#{treeType}</if>" +
             "<if test=\"mediaServerId != null\">, mediaServerId=#{mediaServerId}</if>" +
             "WHERE deviceId=#{deviceId}"+
             " </script>"})
-    int updateCustom(Device device);
+    void updateCustom(Device device);
 
     @Insert("INSERT INTO device (" +
             "deviceId, " +
@@ -259,9 +266,11 @@ public interface DeviceMapper {
             "updateTime," +
             "charset," +
             "ssrcCheck," +
+            "asMessageChannel," +
             "geoCoordSys," +
             "treeType," +
-            "online" +
+            "online," +
+            "mediaServerId" +
             ") VALUES (" +
             "#{deviceId}," +
             "#{name}," +
@@ -271,9 +280,11 @@ public interface DeviceMapper {
             "#{updateTime}," +
             "#{charset}," +
             "#{ssrcCheck}," +
+            "#{asMessageChannel}," +
             "#{geoCoordSys}," +
             "#{treeType}," +
-            "#{online}" +
+            "#{online}," +
+            "#{mediaServerId}" +
             ")")
     void addCustomDevice(Device device);
 
@@ -282,4 +293,7 @@ public interface DeviceMapper {
 
     @Select("select * from device")
     List<Device> getAll();
+
+    @Select("select * from device where  asMessageChannel = 1")
+    List<Device> queryDeviceWithAsMessageChannel();
 }

+ 7 - 3
src/main/java/com/genersoft/iot/vmp/storager/dao/ParentPlatformMapper.java

@@ -15,10 +15,10 @@ import java.util.List;
 public interface ParentPlatformMapper {
 
     @Insert("INSERT INTO parent_platform (enable, name, serverGBId, serverGBDomain, serverIP, serverPort, deviceGBId, deviceIp,  " +
-            "            devicePort, username, password, expires, keepTimeout, transport, characterSet, ptz, rtcp, " +
+            "            devicePort, username, password, expires, keepTimeout, transport, characterSet, ptz, rtcp, asMessageChannel, " +
             "            status, startOfflinePush, catalogId, administrativeDivision, catalogGroup, createTime, updateTime, treeType) " +
             "            VALUES (#{enable}, #{name}, #{serverGBId}, #{serverGBDomain}, #{serverIP}, #{serverPort}, #{deviceGBId}, #{deviceIp}, " +
-            "            #{devicePort}, #{username}, #{password}, #{expires}, #{keepTimeout}, #{transport}, #{characterSet}, #{ptz}, #{rtcp}, " +
+            "            #{devicePort}, #{username}, #{password}, #{expires}, #{keepTimeout}, #{transport}, #{characterSet}, #{ptz}, #{rtcp}, #{asMessageChannel}, " +
             "            #{status},  #{startOfflinePush}, #{catalogId}, #{administrativeDivision}, #{catalogGroup}, #{createTime}, #{updateTime}, #{treeType})")
     int addParentPlatform(ParentPlatform parentPlatform);
 
@@ -40,6 +40,7 @@ public interface ParentPlatformMapper {
             "characterSet=#{characterSet}, " +
             "ptz=#{ptz}, " +
             "rtcp=#{rtcp}, " +
+            "asMessageChannel=#{asMessageChannel}, " +
             "status=#{status}, " +
             "startOfflinePush=#{startOfflinePush}, " +
             "catalogGroup=#{catalogGroup}, " +
@@ -68,9 +69,12 @@ public interface ParentPlatformMapper {
             "FROM parent_platform pp ")
     List<ParentPlatform> getParentPlatformList();
 
-    @Select("SELECT * FROM parent_platform WHERE enable=#{enable}")
+    @Select("SELECT * FROM parent_platform WHERE enable=#{enable} ")
     List<ParentPlatform> getEnableParentPlatformList(boolean enable);
 
+    @Select("SELECT * FROM parent_platform WHERE enable=1 and asMessageChannel = 1")
+    List<ParentPlatform> queryEnablePlatformListWithAsMessageChannel();
+
     @Select("SELECT * FROM parent_platform WHERE serverGBId=#{platformGbId}")
     ParentPlatform getParentPlatByServerGBId(String platformGbId);
 

+ 9 - 6
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java

@@ -177,12 +177,14 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
     @Override
     public boolean startDownload(StreamInfo stream, String callId) {
         boolean result;
+        String key=String.format("%S_%s_%s_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX,
+                userSetting.getServerId(), stream.getMediaServerId(), stream.getDeviceID(), stream.getChannelId(), stream.getStream(), callId);
         if (stream.getProgress() == 1) {
-            result = RedisUtil.set(String.format("%S_%s_%s_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX,
-                    userSetting.getServerId(), stream.getMediaServerId(), stream.getDeviceID(), stream.getChannelId(), stream.getStream(), callId), stream);
+            logger.debug("添加下载缓存==已完成下载=》{}",key);
+            result = RedisUtil.set(key, stream);
         }else {
-            result = RedisUtil.set(String.format("%S_%s_%s_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX,
-                    userSetting.getServerId(), stream.getMediaServerId(), stream.getDeviceID(), stream.getChannelId(), stream.getStream(), callId), stream, 60*60);
+            logger.debug("添加下载缓存==未完成下载=》{}",key);
+            result = RedisUtil.set(key, stream, 60*60);
         }
         return result;
     }
@@ -617,7 +619,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
                 stream,
                 callId
         );
-        List<Object> streamInfoScan = RedisUtil.scan(key);
+        List<Object> streamInfoScan = RedisUtil.scan2(key);
         if (streamInfoScan.size() > 0) {
             return (StreamInfo) RedisUtil.get((String) streamInfoScan.get(0));
         }else {
@@ -855,7 +857,8 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
 
     @Override
     public void sendAlarmMsg(AlarmChannelMessage msg) {
-        String key = VideoManagerConstants.VM_MSG_SUBSCRIBE_ALARM_RECEIVE;
+        // 此消息用于对接第三方服务下级来的消息内容
+        String key = VideoManagerConstants.VM_MSG_SUBSCRIBE_ALARM;
         logger.info("[redis发送通知] 报警{}: {}", key, JSON.toJSON(msg));
         RedisUtil.convertAndSend(key, (JSONObject)JSON.toJSON(msg));
     }

+ 20 - 4
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java

@@ -13,6 +13,7 @@ import com.genersoft.iot.vmp.storager.dao.*;
 import com.genersoft.iot.vmp.storager.dao.dto.ChannelSourceInfo;
 import com.genersoft.iot.vmp.utils.DateUtil;
 import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
+import com.genersoft.iot.vmp.web.gb28181.dto.DeviceChannelExtend;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import org.slf4j.Logger;
@@ -189,7 +190,7 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 			dataSourceTransactionManager.commit(transactionStatus);     //手动提交
 			return true;
 		}catch (Exception e) {
-			e.printStackTrace();
+			logger.error("未处理的异常 ", e);
 			dataSourceTransactionManager.rollback(transactionStatus);
 			return false;
 		}
@@ -305,7 +306,7 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 			}
 			return true;
 		}catch (Exception e) {
-			e.printStackTrace();
+			logger.error("未处理的异常 ", e);
 			dataSourceTransactionManager.rollback(transactionStatus);
 			return false;
 		}
@@ -359,8 +360,8 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 	}
 
 	@Override
-	public List<DeviceChannel> queryChannelsByDeviceIdWithStartAndLimit(String deviceId, String query, Boolean hasSubChannel, Boolean online, int start, int limit,List<String> channelIds) {
-		return deviceChannelMapper.queryChannelsByDeviceIdWithStartAndLimit(deviceId, null, query, hasSubChannel, online, start, limit,channelIds);
+	public List<DeviceChannelExtend> queryChannelsByDeviceIdWithStartAndLimit(String deviceId, List<String> channelIds, String query, Boolean hasSubChannel, Boolean online, int start, int limit) {
+		return deviceChannelMapper.queryChannelsByDeviceIdWithStartAndLimit(deviceId, channelIds, null, query, hasSubChannel, online, start, limit);
 	}
 
 
@@ -369,6 +370,11 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 		return deviceChannelMapper.queryChannels(deviceId, null,null, null, online,channelIds);
 	}
 
+	@Override
+	public List<DeviceChannelExtend> queryChannelsByDeviceId(String deviceId, List<String> channelIds, Boolean online) {
+		return deviceChannelMapper.queryChannelsWithDeviceInfo(deviceId, null,null, null, online,channelIds);
+	}
+
 	@Override
 	public PageInfo<DeviceChannel> querySubChannels(String deviceId, String parentChannelId, String query, Boolean hasSubChannel, Boolean online, int page, int count) {
 		PageHelper.startPage(page, count);
@@ -511,6 +517,16 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 		return platformMapper.getEnableParentPlatformList(enable);
 	}
 
+	@Override
+	public List<ParentPlatform> queryEnablePlatformListWithAsMessageChannel() {
+		return platformMapper.queryEnablePlatformListWithAsMessageChannel();
+	}
+
+	@Override
+	public List<Device> queryDeviceWithAsMessageChannel() {
+		return deviceMapper.queryDeviceWithAsMessageChannel();
+	}
+
 	@Override
 	public void outlineForAllParentPlatform() {
 		platformMapper.outlineForAllParentPlatform();

+ 0 - 1
src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java

@@ -45,7 +45,6 @@ public class DateUtil {
 	
 	public static String ISO8601Toyyyy_MM_dd_HH_mm_ss(String formatTime) {
         return formatter.format(formatterCompatibleISO8601.parse(formatTime));
-
     }
 
     /**

+ 7 - 1
src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java

@@ -881,7 +881,13 @@ public class RedisUtil {
 
         return new ArrayList<>(resultKeys);
     }
-
+    public static List<Object> scan2(String query) {
+        if (redisTemplate == null) {
+            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
+        }
+        Set<String> keys = redisTemplate.keys(query);
+        return new ArrayList<>(keys);
+    }
     //    ============================== 消息发送与订阅 ==============================
     public static void convertAndSend(String channel, JSONObject msg) {
         if (redisTemplate == null) {

+ 4 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/bean/WVPResult.java

@@ -28,6 +28,10 @@ public class WVPResult<T> implements Cloneable{
         return new WVPResult<>(ErrorCode.SUCCESS.getCode(), msg, t);
     }
 
+    public static WVPResult success() {
+        return new WVPResult<>(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), null);
+    }
+
     public static <T> WVPResult<T> success(T t) {
         return success(t, ErrorCode.SUCCESS.getMsg());
     }

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/MobilePosition/MobilePositionController.java

@@ -30,7 +30,7 @@ import java.util.UUID;
  *  位置信息管理
  */
 @Tag(name  = "位置信息管理")
-@CrossOrigin
+
 @RestController
 @RequestMapping("/api/position")
 public class MobilePositionController {

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

@@ -17,7 +17,7 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
  * @data: 2021-01-20
  */
 @Tag(name  = "SSE推送")
-@CrossOrigin
+
 @Controller
 @RequestMapping("/api")
 public class SseController {

+ 11 - 17
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/alarm/AlarmController.java

@@ -6,35 +6,28 @@ import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
-import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookListener;
 import com.genersoft.iot.vmp.service.IDeviceAlarmService;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.DateUtil;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
-import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
 import com.github.pagehelper.PageInfo;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.responses.ApiResponse;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.SipException;
 import java.text.ParseException;
-import java.time.LocalDateTime;
 import java.util.Arrays;
 import java.util.List;
 
 @Tag(name = "报警信息管理")
-@CrossOrigin
+
 @RestController
 @RequestMapping("/api/alarm")
 public class AlarmController {
@@ -78,11 +71,11 @@ public class AlarmController {
         if (ObjectUtils.isEmpty(deviceIds)) {
             deviceIds = null;
         }
+
         if (ObjectUtils.isEmpty(time)) {
             time = null;
-        }
-        if (!DateUtil.verification(time, DateUtil.formatter) ){
-            return null;
+        }else if (!DateUtil.verification(time, DateUtil.formatter) ){
+            throw new ControllerException(ErrorCode.ERROR400.getCode(), "time格式为" + DateUtil.PATTERN);
         }
         List<String> deviceIdList = null;
         if (deviceIds != null) {
@@ -110,7 +103,7 @@ public class AlarmController {
         deviceAlarm.setAlarmDescription("test");
         deviceAlarm.setAlarmMethod("1");
         deviceAlarm.setAlarmPriority("1");
-        deviceAlarm.setAlarmTime(DateUtil.formatterISO8601.format(LocalDateTime.now()));
+        deviceAlarm.setAlarmTime(DateUtil.getNow());
         deviceAlarm.setAlarmType("1");
         deviceAlarm.setLongitude(115.33333);
         deviceAlarm.setLatitude(39.33333);
@@ -177,16 +170,17 @@ public class AlarmController {
         if (ObjectUtils.isEmpty(alarmType)) {
             alarmType = null;
         }
+
         if (ObjectUtils.isEmpty(startTime)) {
             startTime = null;
+        }else if (!DateUtil.verification(startTime, DateUtil.formatter) ){
+            throw new ControllerException(ErrorCode.ERROR400.getCode(), "startTime格式为" + DateUtil.PATTERN);
         }
+
         if (ObjectUtils.isEmpty(endTime)) {
             endTime = null;
-        }
-
-
-        if (!DateUtil.verification(startTime, DateUtil.formatter) || !DateUtil.verification(endTime, DateUtil.formatter)){
-            throw new ControllerException(ErrorCode.ERROR100.getCode(), "开始时间或结束时间格式有误");
+        }else if (!DateUtil.verification(endTime, DateUtil.formatter) ){
+            throw new ControllerException(ErrorCode.ERROR400.getCode(), "endTime格式为" + DateUtil.PATTERN);
         }
 
         return deviceAlarmService.getAllAlarm(page, count, deviceId, alarmPriority, alarmMethod,

+ 0 - 4
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceConfig.java

@@ -14,7 +14,6 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
-
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
@@ -22,9 +21,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.ResponseEntity;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.context.request.async.DeferredResult;
 
@@ -34,7 +31,6 @@ import java.text.ParseException;
 import java.util.UUID;
 
 @Tag(name = "国标设备配置")
-@CrossOrigin
 @RestController
 @RequestMapping("/api/device/config")
 public class DeviceConfig {

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java

@@ -32,7 +32,7 @@ import java.text.ParseException;
 import java.util.UUID;
 
 @Tag(name  = "国标设备控制")
-@CrossOrigin
+
 @RestController
 @RequestMapping("/api/device/control")
 public class DeviceControl {

+ 5 - 2
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java

@@ -24,6 +24,7 @@ import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.apache.commons.compress.utils.IOUtils;
+import org.apache.ibatis.annotations.Options;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -46,7 +47,7 @@ import java.util.*;
 
 @Tag(name  = "国标设备查询", description = "国标设备查询")
 @SuppressWarnings("rawtypes")
-@CrossOrigin
+
 @RestController
 @RequestMapping("/api/device/query")
 public class DeviceQuery {
@@ -97,8 +98,10 @@ public class DeviceQuery {
 	@Parameter(name = "page", description = "当前页", required = true)
 	@Parameter(name = "count", description = "每页查询数量", required = true)
 	@GetMapping("/devices")
+	@Options()
 	public PageInfo<Device> devices(int page, int count){
-		
+//		if (page == null) page = 0;
+//		if (count == null) count = 20;
 		return storager.queryVideoDeviceList(page, count,null);
 	}
 

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/gbStream/GbStreamController.java

@@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.*;
 import java.util.List;
 
 @Tag(name  = "视频流关联到级联平台")
-@CrossOrigin
+
 @RestController
 @RequestMapping("/api/gbStream")
 public class GbStreamController {

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/media/MediaController.java

@@ -24,7 +24,7 @@ import javax.servlet.http.HttpServletRequest;
 
 @Tag(name  = "媒体流相关")
 @Controller
-@CrossOrigin
+
 @RequestMapping(value = "/api/media")
 public class MediaController {
 

+ 9 - 54
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/platform/PlatformController.java

@@ -7,6 +7,7 @@ import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
+import com.genersoft.iot.vmp.gb28181.bean.ParentPlatformCatch;
 import com.genersoft.iot.vmp.gb28181.bean.PlatformCatalog;
 import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
@@ -37,7 +38,7 @@ import java.util.List;
  * 级联平台管理
  */
 @Tag(name  = "级联平台管理")
-@CrossOrigin
+
 @RestController
 @RequestMapping("/api/platform")
 public class PlatformController {
@@ -205,58 +206,8 @@ public class PlatformController {
         ) {
             throw new ControllerException(ErrorCode.ERROR400);
         }
-        parentPlatform.setCharacterSet(parentPlatform.getCharacterSet().toUpperCase());
-        ParentPlatform parentPlatformOld = storager.queryParentPlatByServerGBId(parentPlatform.getServerGBId());
-        parentPlatform.setUpdateTime(DateUtil.getNow());
-        if (!parentPlatformOld.getTreeType().equals(parentPlatform.getTreeType())) {
-             // 目录结构发生变化,清空之前的关联关系
-             logger.info("保存平台{}时发现目录结构变化,清空关联关系", parentPlatform.getDeviceGBId());
-             storager.cleanContentForPlatform(parentPlatform.getServerGBId());
-
-        }
-        boolean updateResult = storager.updateParentPlatform(parentPlatform);
-
-        if (updateResult) {
-            // 保存时启用就发送注册
-            if (parentPlatform.isEnable()) {
-                if (parentPlatformOld != null && parentPlatformOld.isStatus()) {
-                    try {
-                        commanderForPlatform.unregister(parentPlatformOld, null, null);
-                    } catch (InvalidArgumentException | ParseException | SipException e) {
-                        logger.error("[命令发送失败] 国标级联 注销: {}", e.getMessage());
-                    }
-                    try {
-                        Thread.sleep(500);
-                    } catch (InterruptedException e) {
-                        logger.error("[线程休眠失败] : {}", e.getMessage());
-                    }
-                    //  只要保存就发送注册
-                    try {
-                        commanderForPlatform.register(parentPlatform, null, null);
-                    } catch (InvalidArgumentException | ParseException | SipException e) {
-                        logger.error("[命令发送失败] 国标级联 注册: {}", e.getMessage());
-                    }
-
-                } else {
-                    //  只要保存就发送注册
-                    try {
-                        commanderForPlatform.register(parentPlatform, null, null);
-                    } catch (InvalidArgumentException | ParseException | SipException e) {
-                        logger.error("[命令发送失败] 国标级联 注册: {}", e.getMessage());
-                    }
-                }
-            } else if (parentPlatformOld != null && parentPlatformOld.isEnable() && !parentPlatform.isEnable()) { // 关闭启用时注销
-                try {
-                    commanderForPlatform.unregister(parentPlatformOld, null, null);
-                } catch (InvalidArgumentException | ParseException | SipException e) {
-                    logger.error("[命令发送失败] 国标级联 注销: {}", e.getMessage());
-                }
-                // 停止订阅相关的定时任务
-                subscribeHolder.removeAllSubscribe(parentPlatform.getServerGBId());
-            }
-        } else {
-            throw new ControllerException(ErrorCode.ERROR100.getCode(),"写入数据库失败");
-        }
+
+        platformService.update(parentPlatform);
     }
 
     /**
@@ -279,12 +230,16 @@ public class PlatformController {
             throw new ControllerException(ErrorCode.ERROR400);
         }
         ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(serverGBId);
+        ParentPlatformCatch parentPlatformCatch = redisCatchStorage.queryPlatformCatchInfo(serverGBId);
         if (parentPlatform == null) {
             throw new ControllerException(ErrorCode.ERROR100.getCode(), "平台不存在");
         }
+        if (parentPlatformCatch == null) {
+            throw new ControllerException(ErrorCode.ERROR100.getCode(), "平台不存在");
+        }
         // 发送离线消息,无论是否成功都删除缓存
         try {
-            commanderForPlatform.unregister(parentPlatform, (event -> {
+            commanderForPlatform.unregister(parentPlatform, parentPlatformCatch.getSipTransactionInfo(), (event -> {
                 // 清空redis缓存
                 redisCatchStorage.delPlatformCatchInfo(parentPlatform.getServerGBId());
                 redisCatchStorage.delPlatformKeepalive(parentPlatform.getServerGBId());

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java

@@ -41,7 +41,7 @@ import java.util.UUID;
  * @author lin
  */
 @Tag(name  = "国标设备点播")
-@CrossOrigin
+
 @RestController
 @RequestMapping("/api/play")
 public class PlayController {

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java

@@ -40,7 +40,7 @@ import java.util.UUID;
  * @author lin
  */
 @Tag(name = "视频回放")
-@CrossOrigin
+
 @RestController
 @RequestMapping("/api/playback")
 public class PlaybackController {

+ 7 - 9
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/ptz/PtzController.java

@@ -1,7 +1,12 @@
 package com.genersoft.iot.vmp.vmanager.gb28181.ptz;
 
- 
+
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
+import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
@@ -10,23 +15,16 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.context.request.async.DeferredResult;
 
-import com.genersoft.iot.vmp.gb28181.bean.Device;
-import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
-import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
-import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
-
 import javax.sip.InvalidArgumentException;
 import javax.sip.SipException;
 import java.text.ParseException;
 import java.util.UUID;
 
 @Tag(name  = "云台控制")
-@CrossOrigin
+
 @RestController
 @RequestMapping("/api/ptz")
 public class PtzController {

+ 8 - 11
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java

@@ -3,40 +3,37 @@ package com.genersoft.iot.vmp.vmanager.gb28181.record;
 import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
 import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.RecordInfo;
+import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.service.IDeviceService;
 import com.genersoft.iot.vmp.service.IPlayService;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.DateUtil;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
-
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.CrossOrigin;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.context.request.async.DeferredResult;
 
-import com.genersoft.iot.vmp.gb28181.bean.Device;
-import com.genersoft.iot.vmp.gb28181.bean.RecordInfo;
-import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
-import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
-
 import javax.sip.InvalidArgumentException;
 import javax.sip.SipException;
 import java.text.ParseException;
 import java.util.UUID;
 
 @Tag(name  = "国标录像")
-@CrossOrigin
+
 @RestController
 @RequestMapping("/api/gb_record")
 public class GBRecordController {
@@ -74,10 +71,10 @@ public class GBRecordController {
 		}
 		DeferredResult<WVPResult<RecordInfo>> result = new DeferredResult<>();
 		if (!DateUtil.verification(startTime, DateUtil.formatter)){
-			throw new ControllerException(ErrorCode.ERROR100.getCode(), "startTime error, format is " + DateUtil.PATTERN);
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "startTime格式为" + DateUtil.PATTERN);
 		}
 		if (!DateUtil.verification(endTime, DateUtil.formatter)){
-			throw new ControllerException(ErrorCode.ERROR100.getCode(), "endTime error, format is " + DateUtil.PATTERN);
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "endTime格式为" + DateUtil.PATTERN);
 		}
 
 		Device device = storager.queryVideoDevice(deviceId);

+ 0 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/log/LogController.java


Некоторые файлы не были показаны из-за большого количества измененных файлов