Browse Source

Merge branch 'wvp-28181-2.0'

# Conflicts:
#	doc/_content/introduction/deployment.md
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java
#	src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
#	src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
#	src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java
#	src/main/resources/all-application.yml
#	web_src/src/components/dialog/devicePlayer.vue
648540858 2 years atrás
parent
commit
f2c6210539
95 changed files with 2224 additions and 1088 deletions
  1. 1 0
      README.md
  2. 7 2
      doc/_content/introduction/compile.md
  3. 2 1
      doc/_content/introduction/config.md
  4. 3 1
      doc/_content/introduction/deployment.md
  5. 38 1
      pom.xml
  6. 0 2
      sql/update.sql
  7. 25 6
      src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java
  8. 77 0
      src/main/java/com/genersoft/iot/vmp/common/enums/DeviceControlType.java
  9. 3 1
      src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java
  10. 2 9
      src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java
  11. 30 0
      src/main/java/com/genersoft/iot/vmp/conf/ServiceInfo.java
  12. 0 24
      src/main/java/com/genersoft/iot/vmp/conf/security/UrlTokenHandler.java
  13. 13 2
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/AlarmChannelMessage.java
  14. 14 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarmMethod.java
  15. 143 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/DragZoomRequest.java
  16. 94 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/HomePositionRequest.java
  17. 11 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java
  18. 30 7
      src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEventListener.java
  19. 10 4
      src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java
  20. 6 1
      src/main/java/com/genersoft/iot/vmp/gb28181/session/RecordDataCatch.java
  21. 2 1
      src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java
  22. 53 49
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java
  23. 14 12
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  24. 3 3
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
  25. 11 8
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  26. 157 119
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
  27. 0 10
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java
  28. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
  29. 14 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java
  30. 244 30
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java
  31. 5 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java
  32. 3 3
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java
  33. 16 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceInfoQueryMessageHandler.java
  34. 4 4
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java
  35. 17 0
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/MessageElement.java
  36. 78 1
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java
  37. 534 536
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
  38. 2 1
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
  39. 36 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResult.java
  40. 44 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java
  41. 4 0
      src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java
  42. 10 4
      src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
  43. 3 1
      src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java
  44. 10 10
      src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java
  45. 9 2
      src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java
  46. 5 4
      src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceAlarmMapper.java
  47. 10 0
      src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformChannelMapper.java
  48. 15 12
      src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
  49. 136 0
      src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
  50. 36 0
      src/main/java/com/genersoft/iot/vmp/utils/JsonUtil.java
  51. 4 4
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java
  52. 6 2
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java
  53. 1 5
      src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java
  54. 1 1
      src/main/resources/all-application.yml
  55. 74 74
      src/main/resources/application-dev.yml
  56. 13 0
      src/main/resources/application.yml
  57. 1 0
      sql/mysql.sql
  58. 2 1
      web_src/build/utils.js
  59. 3 3
      web_src/config/index.js
  60. 2 2
      web_src/src/components/CloudRecord.vue
  61. 35 25
      web_src/src/components/CloudRecordDetail.vue
  62. 5 5
      web_src/src/components/DeviceList.vue
  63. 8 8
      web_src/src/components/GBRecordDetail.vue
  64. 1 1
      web_src/src/components/Login.vue
  65. 2 2
      web_src/src/components/ParentPlatformList.vue
  66. 5 5
      web_src/src/components/PushVideoList.vue
  67. 6 6
      web_src/src/components/StreamProxyList.vue
  68. 2 2
      web_src/src/components/UserManager.vue
  69. 6 6
      web_src/src/components/channelList.vue
  70. 4 4
      web_src/src/components/console.vue
  71. 1 1
      web_src/src/components/dialog/MediaServerEdit.vue
  72. 4 4
      web_src/src/components/dialog/StreamProxyEdit.vue
  73. 1 1
      web_src/src/components/dialog/SyncChannelProgress.vue
  74. 2 2
      web_src/src/components/dialog/addUser.vue
  75. 1 1
      web_src/src/components/dialog/catalogEdit.vue
  76. 1 1
      web_src/src/components/dialog/changePassword.vue
  77. 1 1
      web_src/src/components/dialog/changePasswordForAdmin.vue
  78. 1 1
      web_src/src/components/dialog/changePushKey.vue
  79. 1 1
      web_src/src/components/dialog/channelMapInfobox.vue
  80. 1 1
      web_src/src/components/dialog/chooseChannel.vue
  81. 4 4
      web_src/src/components/dialog/chooseChannelForCatalog.vue
  82. 5 5
      web_src/src/components/dialog/chooseChannelForGb.vue
  83. 5 5
      web_src/src/components/dialog/chooseChannelForStream.vue
  84. 1 1
      web_src/src/components/dialog/deviceEdit.vue
  85. 2 2
      web_src/src/components/dialog/getCatalog.vue
  86. 1 1
      web_src/src/components/dialog/importChannel.vue
  87. 1 1
      web_src/src/components/dialog/onvifEdit.vue
  88. 3 3
      web_src/src/components/dialog/platformEdit.vue
  89. 3 3
      web_src/src/components/dialog/pushStreamEdit.vue
  90. 1 1
      web_src/src/components/dialog/queryTrace.vue
  91. 4 4
      web_src/src/components/dialog/recordDownload.vue
  92. 1 1
      web_src/src/components/live.vue
  93. 1 1
      web_src/src/components/map.vue
  94. 5 5
      web_src/src/components/service/DeviceService.js
  95. 7 7
      web_src/src/components/service/MediaServer.js

+ 1 - 0
README.md

@@ -98,6 +98,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
 - [X] 支持推流鉴权
 - [X] 支持接口鉴权
 - [X] 云端录像,推流/代理/国标视频均可以录制在云端服务器,支持预览和下载
+- [X] 支持打包可执行jar和war
  
 
 # 遇到问题如何解决

+ 7 - 2
doc/_content/introduction/compile.md

@@ -77,13 +77,18 @@ npm run build
 **编译完成一般是这个样子,中间没有报红的错误信息**
 ![编译成功](_media/img.png)
 
-### 5.3 打包项目, 生成可执行jar
+### 5.3 生成可执行jar
 ```bash
 cd wvp-GB28181-pro
 mvn package
 ```
+### 5.4 生成war
+```bash
+cd wvp-GB28181-pro
+mvn package -P war
+```
 编译如果报错, 一般都是网络问题, 导致的依赖包下载失败  
-编译完成后在target目录下出现wvp-pro-***.jar。  
+编译完成后在target目录下出现wvp-pro-***.jar/wvp-pro-***.war
 接下来[配置服务](./_content/introduction/config.md)
 
   

+ 2 - 1
doc/_content/introduction/config.md

@@ -31,6 +31,7 @@ java -jar wvp-pro-*.jar
 ## 2 配置WVP-PRO
 ### 2.1 Mysql数据库配置
 首先你需要创建一个名为wvp(也可使用其他名字)的数据库,并使用sql/mysql.sql导入数据库,初始化数据库结构。
+(这里注意,取决于版本,新版的sql文件夹下有update.sql,补丁包,一定要注意运行导入)
 在application-dev.yml中配置(使用1.2方式的是在jar包的同级目录的application.yml)配置数据库连接,包括数据库连接信息,密码。
 ### 2.2 Redis数据库配置
 配置wvp中的redis连接信息,建议wvp自己单独使用一个db。
@@ -116,4 +117,4 @@ user-settings:
 
 
 如果配置信息无误,你可以启动zlm,再启动wvp来测试了,启动成功的话,你可以在wvp的日志下看到zlm已连接的提示。
-接下来[部署到服务器](./_content/introduction/deployment.md), 如何你只是本地运行直接再本地运行即可。
+接下来[部署到服务器](./_content/introduction/deployment.md), 如何你只是本地运行直接再本地运行即可。

+ 3 - 1
doc/_content/introduction/deployment.md

@@ -27,7 +27,9 @@
 ```shell
 nohup java -jar wvp-pro-*.jar &
 ```
-
+war包:  
+下载Tomcat后将war包放入webapps中,启动Tomcat以解压war包,停止Tomcat后,删除ROOT目录以及war包,将解压后的war包目录重命名为ROOT,
+然后启动Tomcat。  
 **启动ZLM**
 ```shell
 nohup ./MediaServer -d -m 3 &

+ 38 - 1
pom.xml

@@ -14,6 +14,7 @@
 	<version>2.6.7</version>
 	<name>web video platform</name>
 	<description>国标28181视频平台</description>
+	<packaging>${project.packaging}</packaging>
 
 	<repositories>
 		<repository>
@@ -56,6 +57,42 @@
 		<asciidoctor.pdf.output.directory>${project.build.directory}/asciidoc/pdf</asciidoctor.pdf.output.directory>
 	</properties>
 
+	<profiles>
+		<profile>
+			<id>jar</id>
+			<activation>
+				<activeByDefault>true</activeByDefault>
+			</activation>
+			<properties>
+				<project.packaging>jar</project.packaging>
+			</properties>
+		</profile>
+		<profile>
+			<id>war</id>
+			<properties>
+				<project.packaging>war</project.packaging>
+			</properties>
+			<dependencies>
+				<dependency>
+					<groupId>org.springframework.boot</groupId>
+					<artifactId>spring-boot-starter-web</artifactId>
+					<exclusions>
+						<exclusion>
+							<groupId>org.springframework.boot</groupId>
+							<artifactId>spring-boot-starter-jetty</artifactId>
+						</exclusion>
+					</exclusions>
+				</dependency>
+				<dependency>
+					<groupId>javax.servlet</groupId>
+					<artifactId>javax.servlet-api</artifactId>
+					<version>3.1.0</version>
+					<scope>provided</scope>
+				</dependency>
+			</dependencies>
+		</profile>
+	</profiles>
+
 	<dependencies>
 		<dependency>
 			<groupId>org.springframework.boot</groupId>
@@ -242,8 +279,8 @@
 			<artifactId>spring-boot-starter-test</artifactId>
 <!--			<scope>test</scope>-->
 		</dependency>
-	</dependencies>
 
+	</dependencies>
 
 
 	<build>

+ 0 - 2
sql/update.sql

@@ -1,3 +0,0 @@
-alter table device
-    add keepaliveIntervalTime int default null;

+ 25 - 6
src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java

@@ -1,20 +1,24 @@
 package com.genersoft.iot.vmp;
 
-import java.util.logging.LogManager;
-
 import com.genersoft.iot.vmp.conf.druid.EnableDruidSupport;
-import com.genersoft.iot.vmp.storager.impl.RedisCatchStorageImpl;
 import com.genersoft.iot.vmp.utils.GitUtil;
 import com.genersoft.iot.vmp.utils.SpringBeanFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
 import org.springframework.boot.web.servlet.ServletComponentScan;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
 import org.springframework.context.ConfigurableApplicationContext;
 import org.springframework.scheduling.annotation.EnableScheduling;
 
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import java.util.Collections;
+
 /**
  * 启动类
  */
@@ -22,7 +26,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
 @SpringBootApplication
 @EnableScheduling
 @EnableDruidSupport
-public class VManageBootstrap extends LogManager {
+public class VManageBootstrap extends SpringBootServletInitializer {
 
 	private final static Logger logger = LoggerFactory.getLogger(VManageBootstrap.class);
 
@@ -41,6 +45,21 @@ public class VManageBootstrap extends LogManager {
 		context.close();
 		VManageBootstrap.context = SpringApplication.run(VManageBootstrap.class, args);
 	}
-	
 
+	@Override
+	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+		return application.sources(VManageBootstrap.class);
+	}
+
+	@Override
+	public void onStartup(ServletContext servletContext) throws ServletException {
+		super.onStartup(servletContext);
+
+		servletContext.setSessionTrackingModes(
+				Collections.singleton(SessionTrackingMode.COOKIE)
+		);
+		SessionCookieConfig sessionCookieConfig = servletContext.getSessionCookieConfig();
+		sessionCookieConfig.setHttpOnly(true);
+
+	}
 }

+ 77 - 0
src/main/java/com/genersoft/iot/vmp/common/enums/DeviceControlType.java

@@ -0,0 +1,77 @@
+package com.genersoft.iot.vmp.common.enums;
+
+import org.dom4j.Element;
+import org.springframework.util.ObjectUtils;
+
+
+/**
+ * @author gaofuwang
+ * @date 2023/01/18/ 10:09:00
+ * @since 1.0
+ */
+public enum DeviceControlType {
+
+    /**
+     * 云台控制
+     * 上下左右,预置位,扫描,辅助功能,巡航
+     */
+    PTZ("PTZCmd","云台控制"),
+    /**
+     * 远程启动
+     */
+    TELE_BOOT("TeleBoot","远程启动"),
+    /**
+     * 录像控制
+     */
+    RECORD("RecordCmd","录像控制"),
+    /**
+     * 布防撤防
+     */
+    GUARD("GuardCmd","布防撤防"),
+    /**
+     * 告警控制
+     */
+    ALARM("AlarmCmd","告警控制"),
+    /**
+     * 强制关键帧
+     */
+    I_FRAME("IFameCmd","强制关键帧"),
+    /**
+     * 拉框放大
+     */
+    DRAG_ZOOM_IN("DragZoomIn","拉框放大"),
+    /**
+     * 拉框缩小
+     */
+    DRAG_ZOOM_OUT("DragZoomOut","拉框缩小"),
+    /**
+     * 看守位
+     */
+    HOME_POSITION("HomePosition","看守位");
+
+    private final String val;
+
+    private final String desc;
+
+    DeviceControlType(String val, String desc) {
+        this.val = val;
+        this.desc = desc;
+    }
+
+    public String getVal() {
+        return val;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    public static DeviceControlType typeOf(Element rootElement) {
+        for (DeviceControlType item : DeviceControlType.values()) {
+            if (!ObjectUtils.isEmpty(rootElement.element(item.val)) || !ObjectUtils.isEmpty(rootElement.elements(item.val))) {
+                return item;
+            }
+        }
+        return null;
+    }
+}

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

@@ -10,6 +10,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
 import org.springframework.web.filter.OncePerRequestFilter;
 
@@ -23,6 +24,7 @@ import java.io.IOException;
  * @author lin
  */
 @WebFilter(filterName = "ApiAccessFilter", urlPatterns = "/api/*", asyncSupported=true)
+@Component
 public class ApiAccessFilter extends OncePerRequestFilter {
 
     private final static Logger logger = LoggerFactory.getLogger(ApiAccessFilter.class);
@@ -48,7 +50,7 @@ public class ApiAccessFilter extends OncePerRequestFilter {
 
         filterChain.doFilter(servletRequest, servletResponse);
 
-        if (uriName != null && userSetting.getLogInDatebase()) {
+        if (uriName != null && userSetting != null && userSetting.getLogInDatebase() != null && userSetting.getLogInDatebase()) {
 
             LogDto logDto = new LogDto();
             logDto.setName(uriName);

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

@@ -2,7 +2,6 @@ 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;
@@ -15,11 +14,9 @@ import org.springframework.boot.web.servlet.ServletRegistrationBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.net.ConnectException;
 
@@ -77,9 +74,7 @@ public class ProxyServletConfig {
             } catch (IOException ioException) {
                 if (ioException instanceof ConnectException) {
                     logger.error("zlm 连接失败");
-                } else if (ioException instanceof ClientAbortException) {
-                    logger.error("zlm: 用户已中断连接,代理终止");
-                } else {
+                }  else {
                     logger.error("zlm 代理失败: ", e);
                 }
             } catch (RuntimeException exception){
@@ -195,9 +190,7 @@ public class ProxyServletConfig {
             } catch (IOException ioException) {
                 if (ioException instanceof ConnectException) {
                     logger.error("录像服务 连接失败");
-                } else if (ioException instanceof ClientAbortException) {
-                    logger.error("录像服务:用户已中断连接,代理终止");
-                } else {
+                }else {
                     logger.error("录像服务 代理失败: ", e);
                 }
             } catch (RuntimeException exception){

+ 30 - 0
src/main/java/com/genersoft/iot/vmp/conf/ServiceInfo.java

@@ -0,0 +1,30 @@
+package com.genersoft.iot.vmp.conf;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.web.context.WebServerInitializedEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ServiceInfo implements ApplicationListener<WebServerInitializedEvent> {
+
+    private final Logger logger = LoggerFactory.getLogger(ServiceInfo.class);
+
+    private static int serverPort;
+
+    public static int getServerPort() {
+        return serverPort;
+    }
+
+    @Override
+    public void onApplicationEvent(WebServerInitializedEvent event) {
+        // 项目启动获取启动的端口号
+        ServiceInfo.serverPort = event.getWebServer().getPort();
+        logger.info("项目启动获取启动的端口号:  " + ServiceInfo.serverPort);
+    }
+
+    public void setServerPort(int serverPort) {
+        ServiceInfo.serverPort = serverPort;
+    }
+}

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

@@ -1,24 +0,0 @@
-package com.genersoft.iot.vmp.conf.security;
-
-import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
-
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.SessionCookieConfig;
-import javax.servlet.SessionTrackingMode;
-import java.util.Collections;
-
-public class UrlTokenHandler extends SpringBootServletInitializer {
-
-    @Override
-    public void onStartup(ServletContext servletContext) throws ServletException {
-        super.onStartup(servletContext);
-
-        servletContext.setSessionTrackingModes(
-                Collections.singleton(SessionTrackingMode.COOKIE)
-        );
-        SessionCookieConfig sessionCookieConfig = servletContext.getSessionCookieConfig();
-        sessionCookieConfig.setHttpOnly(true);
-
-    }
-}

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

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

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

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

+ 143 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DragZoomRequest.java

@@ -0,0 +1,143 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+import com.genersoft.iot.vmp.gb28181.utils.MessageElement;
+
+/**
+ * 设备信息查询响应
+ *
+ * @author Y.G
+ * @version 1.0
+ * @date 2022/6/28 14:55
+ */
+public class DragZoomRequest {
+    /**
+     * 序列号
+     */
+    @MessageElement("SN")
+    private String sn;
+
+    @MessageElement("DeviceID")
+    private String deviceId;
+
+    @MessageElement(value = "DragZoomIn")
+    private DragZoom dragZoomIn;
+
+    @MessageElement(value = "DragZoomOut")
+    private DragZoom dragZoomOut;
+
+    /**
+     * 基本参数
+     */
+    public static class DragZoom {
+        /**
+         * 播放窗口长度像素值
+         */
+        @MessageElement("Length")
+        protected Integer length;
+        /**
+         * 播放窗口宽度像素值
+         */
+        @MessageElement("Width")
+        protected Integer width;
+        /**
+         * 拉框中心的横轴坐标像素值
+         */
+        @MessageElement("MidPointX")
+        protected Integer midPointX;
+        /**
+         * 拉框中心的纵轴坐标像素值
+         */
+        @MessageElement("MidPointY")
+        protected Integer midPointY;
+        /**
+         * 拉框长度像素值
+         */
+        @MessageElement("LengthX")
+        protected Integer lengthX;
+        /**
+         * 拉框宽度像素值
+         */
+        @MessageElement("LengthY")
+        protected Integer lengthY;
+
+        public Integer getLength() {
+            return length;
+        }
+
+        public void setLength(Integer length) {
+            this.length = length;
+        }
+
+        public Integer getWidth() {
+            return width;
+        }
+
+        public void setWidth(Integer width) {
+            this.width = width;
+        }
+
+        public Integer getMidPointX() {
+            return midPointX;
+        }
+
+        public void setMidPointX(Integer midPointX) {
+            this.midPointX = midPointX;
+        }
+
+        public Integer getMidPointY() {
+            return midPointY;
+        }
+
+        public void setMidPointY(Integer midPointY) {
+            this.midPointY = midPointY;
+        }
+
+        public Integer getLengthX() {
+            return lengthX;
+        }
+
+        public void setLengthX(Integer lengthX) {
+            this.lengthX = lengthX;
+        }
+
+        public Integer getLengthY() {
+            return lengthY;
+        }
+
+        public void setLengthY(Integer lengthY) {
+            this.lengthY = lengthY;
+        }
+    }
+
+    public String getSn() {
+        return sn;
+    }
+
+    public void setSn(String sn) {
+        this.sn = sn;
+    }
+
+    public String getDeviceId() {
+        return deviceId;
+    }
+
+    public void setDeviceId(String deviceId) {
+        this.deviceId = deviceId;
+    }
+
+    public DragZoom getDragZoomIn() {
+        return dragZoomIn;
+    }
+
+    public void setDragZoomIn(DragZoom dragZoomIn) {
+        this.dragZoomIn = dragZoomIn;
+    }
+
+    public DragZoom getDragZoomOut() {
+        return dragZoomOut;
+    }
+
+    public void setDragZoomOut(DragZoom dragZoomOut) {
+        this.dragZoomOut = dragZoomOut;
+    }
+}

+ 94 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/HomePositionRequest.java

@@ -0,0 +1,94 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+import com.genersoft.iot.vmp.gb28181.utils.MessageElement;
+
+/**
+ * 设备信息查询响应
+ *
+ * @author Y.G
+ * @version 1.0
+ * @date 2022/6/28 14:55
+ */
+public class HomePositionRequest {
+    /**
+     * 序列号
+     */
+    @MessageElement("SN")
+    private String sn;
+
+    @MessageElement("DeviceID")
+    private String deviceId;
+
+    @MessageElement(value = "HomePosition")
+    private HomePosition homePosition;
+
+
+    /**
+     * 基本参数
+     */
+    public static class HomePosition {
+        /**
+         * 播放窗口长度像素值
+         */
+        @MessageElement("Enabled")
+        protected String enabled;
+        /**
+         * 播放窗口宽度像素值
+         */
+        @MessageElement("ResetTime")
+        protected String resetTime;
+        /**
+         * 拉框中心的横轴坐标像素值
+         */
+        @MessageElement("PresetIndex")
+        protected String presetIndex;
+
+        public String getEnabled() {
+            return enabled;
+        }
+
+        public void setEnabled(String enabled) {
+            this.enabled = enabled;
+        }
+
+        public String getResetTime() {
+            return resetTime;
+        }
+
+        public void setResetTime(String resetTime) {
+            this.resetTime = resetTime;
+        }
+
+        public String getPresetIndex() {
+            return presetIndex;
+        }
+
+        public void setPresetIndex(String presetIndex) {
+            this.presetIndex = presetIndex;
+        }
+    }
+
+    public String getSn() {
+        return sn;
+    }
+
+    public void setSn(String sn) {
+        this.sn = sn;
+    }
+
+    public String getDeviceId() {
+        return deviceId;
+    }
+
+    public void setDeviceId(String deviceId) {
+        this.deviceId = deviceId;
+    }
+
+    public HomePosition getHomePosition() {
+        return homePosition;
+    }
+
+    public void setHomePosition(HomePosition homePosition) {
+        this.homePosition = homePosition;
+    }
+}

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

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.bean;
 
+
 import java.time.Instant;
 import java.util.List;
 
@@ -20,6 +21,8 @@ public class RecordInfo {
 	
 	private int sumNum;
 
+	private int count;
+
 	private Instant lastTime;
 	
 	private List<RecordItem> recordList;
@@ -79,4 +82,12 @@ public class RecordInfo {
 	public void setLastTime(Instant lastTime) {
 		this.lastTime = lastTime;
 	}
+
+	public int getCount() {
+		return count;
+	}
+
+	public void setCount(int count) {
+		this.count = count;
+	}
 }

+ 30 - 7
src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEventListener.java

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

+ 10 - 4
src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java

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

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

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

+ 2 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java

@@ -4,6 +4,7 @@ import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
 import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
+import com.genersoft.iot.vmp.utils.JsonUtil;
 import com.genersoft.iot.vmp.utils.redis.RedisUtil;
 import gov.nist.javax.sip.message.SIPResponse;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -134,7 +135,7 @@ public class VideoStreamSessionManager {
 		List<SsrcTransaction> result= new ArrayList<>();
 		for (int i = 0; i < ssrcTransactionKeys.size(); i++) {
 			String key = (String)ssrcTransactionKeys.get(i);
-			SsrcTransaction ssrcTransaction = (SsrcTransaction)RedisUtil.get(key);
+			SsrcTransaction ssrcTransaction = JsonUtil.redisJsonToObject(key, SsrcTransaction.class);
 			result.add(ssrcTransaction);
 		}
 		return result;

+ 53 - 49
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java

@@ -47,61 +47,65 @@ public class SIPSender {
     }
 
     public void transmitRequest(String ip, Message message, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, ParseException {
-        ViaHeader viaHeader = (ViaHeader)message.getHeader(ViaHeader.NAME);
-        String transport = "UDP";
-        if (viaHeader == null) {
-            logger.warn("[消息头缺失]: ViaHeader, 使用默认的UDP方式处理数据");
-        }else {
-            transport = viaHeader.getTransport();
-        }
-        if (message.getHeader(UserAgentHeader.NAME) == null) {
-            try {
-                message.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
-            } catch (ParseException e) {
-                logger.error("添加UserAgentHeader失败", e);
+        try {
+            ViaHeader viaHeader = (ViaHeader)message.getHeader(ViaHeader.NAME);
+            String transport = "UDP";
+            if (viaHeader == null) {
+                logger.warn("[消息头缺失]: ViaHeader, 使用默认的UDP方式处理数据");
+            }else {
+                transport = viaHeader.getTransport();
+            }
+            if (message.getHeader(UserAgentHeader.NAME) == null) {
+                try {
+                    message.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
+                } catch (ParseException e) {
+                    logger.error("添加UserAgentHeader失败", e);
+                }
             }
-        }
 
-        CallIdHeader callIdHeader = (CallIdHeader) message.getHeader(CallIdHeader.NAME);
-        // 添加错误订阅
-        if (errorEvent != null) {
-            sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> {
-                errorEvent.response(eventResult);
-                sipSubscribe.removeErrorSubscribe(eventResult.callId);
-                sipSubscribe.removeOkSubscribe(eventResult.callId);
-            }));
-        }
-        // 添加订阅
-        if (okEvent != null) {
-            sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult -> {
-                okEvent.response(eventResult);
-                sipSubscribe.removeOkSubscribe(eventResult.callId);
-                sipSubscribe.removeErrorSubscribe(eventResult.callId);
-            });
-        }
-        if ("TCP".equals(transport)) {
-            SipProviderImpl tcpSipProvider = sipLayer.getTcpSipProvider(ip);
-            if (tcpSipProvider == null) {
-                logger.error("[发送信息失败] 未找到tcp://{}的监听信息", ip);
-                return;
+            CallIdHeader callIdHeader = (CallIdHeader) message.getHeader(CallIdHeader.NAME);
+            // 添加错误订阅
+            if (errorEvent != null) {
+                sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> {
+                    errorEvent.response(eventResult);
+                    sipSubscribe.removeErrorSubscribe(eventResult.callId);
+                    sipSubscribe.removeOkSubscribe(eventResult.callId);
+                }));
             }
-            if (message instanceof Request) {
-                tcpSipProvider.sendRequest((Request)message);
-            }else if (message instanceof Response) {
-                tcpSipProvider.sendResponse((Response)message);
+            // 添加订阅
+            if (okEvent != null) {
+                sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult -> {
+                    okEvent.response(eventResult);
+                    sipSubscribe.removeOkSubscribe(eventResult.callId);
+                    sipSubscribe.removeErrorSubscribe(eventResult.callId);
+                });
             }
+            if ("TCP".equals(transport)) {
+                SipProviderImpl tcpSipProvider = sipLayer.getTcpSipProvider(ip);
+                if (tcpSipProvider == null) {
+                    logger.error("[发送信息失败] 未找到tcp://{}的监听信息", ip);
+                    return;
+                }
+                if (message instanceof Request) {
+                    tcpSipProvider.sendRequest((Request)message);
+                }else if (message instanceof Response) {
+                    tcpSipProvider.sendResponse((Response)message);
+                }
 
-        } else if ("UDP".equals(transport)) {
-            SipProviderImpl sipProvider = sipLayer.getUdpSipProvider(ip);
-            if (sipProvider == null) {
-                logger.error("[发送信息失败] 未找到udp://{}的监听信息", ip);
-                return;
-            }
-            if (message instanceof Request) {
-                sipProvider.sendRequest((Request)message);
-            }else if (message instanceof Response) {
-                sipProvider.sendResponse((Response)message);
+            } else if ("UDP".equals(transport)) {
+                SipProviderImpl sipProvider = sipLayer.getUdpSipProvider(ip);
+                if (sipProvider == null) {
+                    logger.error("[发送信息失败] 未找到udp://{}的监听信息", ip);
+                    return;
+                }
+                if (message instanceof Request) {
+                    sipProvider.sendRequest((Request)message);
+                }else if (message instanceof Response) {
+                    sipProvider.sendResponse((Response)message);
+                }
             }
+        } finally {
+            logger.info("[SEND]:SUCCESS:{}", message);
         }
     }
 

+ 14 - 12
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java

@@ -182,7 +182,7 @@ public interface ISIPCommander {
 	 * @param channelId  	预览通道
 	 * @param recordCmdStr	录像命令:Record / StopRecord
 	 */
-	void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+	void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 远程启动控制命令
@@ -196,7 +196,7 @@ public interface ISIPCommander {
 	 * 
 	 * @param device  	视频设备
 	 */
-	void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+	void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 报警复位命令
@@ -205,7 +205,7 @@ public interface ISIPCommander {
 	 * @param alarmMethod	报警方式(可选)
 	 * @param alarmType		报警类型(可选)
 	 */
-	void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+	void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
@@ -214,17 +214,19 @@ public interface ISIPCommander {
 	 * @param channelId  预览通道
 	 */
 	void iFrameCmd(Device device, String channelId) throws InvalidArgumentException, SipException, ParseException;
-	
+
 	/**
 	 * 看守位控制命令
-	 * 
-	 * @param device		视频设备
-	 * @param enabled		看守位使能:1 = 开启,0 = 关闭
-	 * @param resetTime		自动归位时间间隔,开启看守位时使用,单位:秒(s)
-	 * @param presetIndex	调用预置位编号,开启看守位时使用,取值范围0~255
-	 */
-	void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
-	
+	 *
+	 * @param device      视频设备
+	 * @param channelId      通道id,非通道则是设备本身
+	 * @param frontCmd     上级平台的指令,如果存在则直接下发
+	 * @param enabled     看守位使能:1 = 开启,0 = 关闭
+	 * @param resetTime   自动归位时间间隔,开启看守位时使用,单位:秒(s)
+	 * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255
+	 */
+	void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
+
 	/**
 	 * 设备配置命令
 	 * 

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

@@ -51,11 +51,11 @@ public interface ISIPCommanderForPlatform {
     /**
      * 向上级回复DeviceInfo查询信息
      * @param parentPlatform 平台信息
-     * @param sn
-     * @param fromTag
+     * @param sn SN
+     * @param fromTag FROM头的tag信息
      * @return
      */
-    void deviceInfoResponse(ParentPlatform parentPlatform, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException;
+    void deviceInfoResponse(ParentPlatform parentPlatform,Device device, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException;
 
     /**
      * 向上级回复DeviceStatus查询信息

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

@@ -32,6 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.DependsOn;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.ResponseEvent;
@@ -711,7 +712,7 @@ public class SIPCommander implements ISIPCommander {
      * @param recordCmdStr 录像命令:Record / StopRecord
      */
     @Override
-    public void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+    public void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
         cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -729,7 +730,7 @@ public class SIPCommander implements ISIPCommander {
 
 
         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);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
     }
 
     /**
@@ -763,7 +764,7 @@ public class SIPCommander implements ISIPCommander {
      * @param guardCmdStr "SetGuard"/"ResetGuard"
      */
     @Override
-    public void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+    public void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
 
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
@@ -778,7 +779,7 @@ public class SIPCommander implements ISIPCommander {
         
 
         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);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
     }
 
     /**
@@ -787,7 +788,7 @@ public class SIPCommander implements ISIPCommander {
      * @param device 视频设备
      */
     @Override
-    public void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+    public void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
 
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
@@ -814,7 +815,7 @@ public class SIPCommander implements ISIPCommander {
 
 
         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);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
     }
 
     /**
@@ -850,12 +851,14 @@ public class SIPCommander implements ISIPCommander {
      * 看守位控制命令
      *
      * @param device      视频设备
+     * @param channelId      通道id,非通道则是设备本身
+     * @param frontCmd     上级平台的指令,如果存在则直接下发
      * @param enabled     看守位使能:1 = 开启,0 = 关闭
      * @param resetTime   自动归位时间间隔,开启看守位时使用,单位:秒(s)
      * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255
      */
     @Override
-    public void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+    public void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
 
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
@@ -890,7 +893,7 @@ public class SIPCommander implements ISIPCommander {
 
 
         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);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
     }
 
     /**

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

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
 
 import com.alibaba.fastjson2.JSON;
+import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.gb28181.SipLayer;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
@@ -61,6 +62,9 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
     @Autowired
     private SIPSender sipSender;
 
+    @Autowired
+    private DynamicTask dynamicTask;
+
     @Override
     public void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException {
         register(parentPlatform, null, null, errorEvent, okEvent, false, true);
@@ -109,13 +113,14 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
     public String keepalive(ParentPlatform parentPlatform,SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException {
             String characterSet = parentPlatform.getCharacterSet();
             StringBuffer keepaliveXml = new StringBuffer(200);
-            keepaliveXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-            keepaliveXml.append("<Notify>\r\n");
-            keepaliveXml.append("<CmdType>Keepalive</CmdType>\r\n");
-            keepaliveXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-            keepaliveXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
-            keepaliveXml.append("<Status>OK</Status>\r\n");
-            keepaliveXml.append("</Notify>\r\n");
+            keepaliveXml.append("<?xml version=\"1.0\" encoding=\"")
+                    .append(characterSet).append("\"?>\r\n")
+                    .append("<Notify>\r\n")
+                    .append("<CmdType>Keepalive</CmdType>\r\n")
+                    .append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n")
+                    .append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n")
+                    .append("<Status>OK</Status>\r\n")
+                    .append("</Notify>\r\n");
 
         CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
 
@@ -133,7 +138,6 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
      * 向上级回复通道信息
      * @param channel 通道信息
      * @param parentPlatform 平台信息
-     * @return
      */
     @Override
     public void catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size) throws SipException, InvalidArgumentException, ParseException {
@@ -160,18 +164,18 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         if ( parentPlatform ==null) {
             return ;
         }
-        sendCatalogResponse(channels, parentPlatform, sn, fromTag, 0);
+        sendCatalogResponse(channels, parentPlatform, sn, fromTag, 0, true);
     }
     private String getCatalogXml(List<DeviceChannel> channels, String sn, ParentPlatform parentPlatform, int size) {
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer catalogXml = new StringBuffer(600);
-        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet +"\"?>\r\n");
-        catalogXml.append("<Response>\r\n");
-        catalogXml.append("<CmdType>Catalog</CmdType>\r\n");
-        catalogXml.append("<SN>" +sn + "</SN>\r\n");
-        catalogXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
-        catalogXml.append("<SumNum>" + size + "</SumNum>\r\n");
-        catalogXml.append("<DeviceList Num=\"" + channels.size() +"\">\r\n");
+        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet +"\"?>\r\n")
+                .append("<Response>\r\n")
+                .append("<CmdType>Catalog</CmdType>\r\n")
+                .append("<SN>" +sn + "</SN>\r\n")
+                .append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n")
+                .append("<SumNum>" + size + "</SumNum>\r\n")
+                .append("<DeviceList Num=\"" + channels.size() +"\">\r\n");
         if (channels.size() > 0) {
             for (DeviceChannel channel : channels) {
                 if (parentPlatform.getServerGBId().equals(channel.getParentId())) {
@@ -222,7 +226,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         return catalogXml.toString();
     }
 
-    private void sendCatalogResponse(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag, int index) throws SipException, InvalidArgumentException, ParseException {
+    private void sendCatalogResponse(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag, int index, boolean sendAfterResponse) throws SipException, InvalidArgumentException, ParseException {
         if (index >= channels.size()) {
             return;
         }
@@ -236,15 +240,49 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         // callid
         CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
 
-        Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, catalogXml, fromTag, SipUtils.getNewViaTag(), callIdHeader);
-        sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, null, eventResult -> {
-            int indexNext = index + parentPlatform.getCatalogGroup();
-            try {
-                sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext);
-            } catch (SipException | InvalidArgumentException | ParseException e) {
-                logger.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage());
-            }
-        });
+        SIPRequest request = (SIPRequest)headerProviderPlatformProvider.createMessageRequest(parentPlatform, catalogXml, fromTag, SipUtils.getNewViaTag(), callIdHeader);
+
+        String timeoutTaskKey = "catalog_task_" + parentPlatform.getServerGBId() + sn;
+
+        String callId = request.getCallIdHeader().getCallId();
+
+        if (sendAfterResponse) {
+            // 默认按照收到200回复后发送下一条, 如果超时收不到回复,就以30毫秒的间隔直接发送。
+            dynamicTask.startDelay(timeoutTaskKey, ()->{
+                sipSubscribe.removeOkSubscribe(callId);
+                int indexNext = index + parentPlatform.getCatalogGroup();
+                try {
+                    sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext, false);
+                } catch (SipException | InvalidArgumentException | ParseException e) {
+                    logger.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage());
+                }
+            }, 3000);
+            sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, eventResult -> {
+                logger.error("[目录推送失败] 国标级联 platform : {}, code: {}, msg: {}, 停止发送", parentPlatform.getServerGBId(), eventResult.statusCode, eventResult.msg);
+                dynamicTask.stop(timeoutTaskKey);
+            }, eventResult -> {
+                dynamicTask.stop(timeoutTaskKey);
+                int indexNext = index + parentPlatform.getCatalogGroup();
+                try {
+                    sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext, true);
+                } catch (SipException | InvalidArgumentException | ParseException e) {
+                    logger.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage());
+                }
+            });
+        }else {
+            sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, eventResult -> {
+                logger.error("[目录推送失败] 国标级联 platform : {}, code: {}, msg: {}, 停止发送", parentPlatform.getServerGBId(), eventResult.statusCode, eventResult.msg);
+                dynamicTask.stop(timeoutTaskKey);
+            }, null);
+            dynamicTask.startDelay(timeoutTaskKey, ()->{
+                int indexNext = index + parentPlatform.getCatalogGroup();
+                try {
+                    sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext, false);
+                } catch (SipException | InvalidArgumentException | ParseException e) {
+                    logger.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage());
+                }
+            }, 30);
+        }
     }
 
     /**
@@ -255,7 +293,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
      * @return
      */
     @Override
-    public void deviceInfoResponse(ParentPlatform parentPlatform, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException {
+    public void deviceInfoResponse(ParentPlatform parentPlatform,Device device, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException {
         if (parentPlatform == null) {
             return;
         }
@@ -265,11 +303,11 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         deviceInfoXml.append("<Response>\r\n");
         deviceInfoXml.append("<CmdType>DeviceInfo</CmdType>\r\n");
         deviceInfoXml.append("<SN>" +sn + "</SN>\r\n");
-        deviceInfoXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
-        deviceInfoXml.append("<DeviceName>" + parentPlatform.getName() + "</DeviceName>\r\n");
-        deviceInfoXml.append("<Manufacturer>wvp</Manufacturer>\r\n");
-        deviceInfoXml.append("<Model>wvp-28181-2.0</Model>\r\n");
-        deviceInfoXml.append("<Firmware>2.0.202107</Firmware>\r\n");
+        deviceInfoXml.append("<DeviceID>" + 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("<Result>OK</Result>\r\n");
         deviceInfoXml.append("</Response>\r\n");
 
@@ -294,15 +332,15 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         String statusStr = (status==1)?"ONLINE":"OFFLINE";
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer deviceStatusXml = new StringBuffer(600);
-        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        deviceStatusXml.append("<Response>\r\n");
-        deviceStatusXml.append("<CmdType>DeviceStatus</CmdType>\r\n");
-        deviceStatusXml.append("<SN>" +sn + "</SN>\r\n");
-        deviceStatusXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-        deviceStatusXml.append("<Result>OK</Result>\r\n");
-        deviceStatusXml.append("<Online>"+statusStr+"</Online>\r\n");
-        deviceStatusXml.append("<Status>OK</Status>\r\n");
-        deviceStatusXml.append("</Response>\r\n");
+        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Response>\r\n")
+                .append("<CmdType>DeviceStatus</CmdType>\r\n")
+                .append("<SN>" +sn + "</SN>\r\n")
+                .append("<DeviceID>" + channelId + "</DeviceID>\r\n")
+                .append("<Result>OK</Result>\r\n")
+                .append("<Online>"+statusStr+"</Online>\r\n")
+                .append("<Status>OK</Status>\r\n")
+                .append("</Response>\r\n");
 
         CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
 
@@ -321,18 +359,18 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
 
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer deviceStatusXml = new StringBuffer(600);
-        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        deviceStatusXml.append("<Notify>\r\n");
-        deviceStatusXml.append("<CmdType>MobilePosition</CmdType>\r\n");
-        deviceStatusXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-        deviceStatusXml.append("<DeviceID>" + gpsMsgInfo.getId() + "</DeviceID>\r\n");
-        deviceStatusXml.append("<Time>" + gpsMsgInfo.getTime() + "</Time>\r\n");
-        deviceStatusXml.append("<Longitude>" + gpsMsgInfo.getLng() + "</Longitude>\r\n");
-        deviceStatusXml.append("<Latitude>" + gpsMsgInfo.getLat() + "</Latitude>\r\n");
-        deviceStatusXml.append("<Speed>" + gpsMsgInfo.getSpeed() + "</Speed>\r\n");
-        deviceStatusXml.append("<Direction>" + gpsMsgInfo.getDirection() + "</Direction>\r\n");
-        deviceStatusXml.append("<Altitude>" + gpsMsgInfo.getAltitude() + "</Altitude>\r\n");
-        deviceStatusXml.append("</Notify>\r\n");
+        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Notify>\r\n")
+                .append("<CmdType>MobilePosition</CmdType>\r\n")
+                .append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n")
+                .append("<DeviceID>" + gpsMsgInfo.getId() + "</DeviceID>\r\n")
+                .append("<Time>" + gpsMsgInfo.getTime() + "</Time>\r\n")
+                .append("<Longitude>" + gpsMsgInfo.getLng() + "</Longitude>\r\n")
+                .append("<Latitude>" + gpsMsgInfo.getLat() + "</Latitude>\r\n")
+                .append("<Speed>" + gpsMsgInfo.getSpeed() + "</Speed>\r\n")
+                .append("<Direction>" + gpsMsgInfo.getDirection() + "</Direction>\r\n")
+                .append("<Altitude>" + gpsMsgInfo.getAltitude() + "</Altitude>\r\n")
+                .append("</Notify>\r\n");
 
        sendNotify(parentPlatform, deviceStatusXml.toString(), subscribeInfo, eventResult -> {
             logger.error("发送NOTIFY通知消息失败。错误:{} {}", eventResult.statusCode, eventResult.msg);
@@ -349,21 +387,21 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                 deviceAlarm.getLongitude(), deviceAlarm.getLatitude(), JSON.toJSONString(deviceAlarm));
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer deviceStatusXml = new StringBuffer(600);
-        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        deviceStatusXml.append("<Notify>\r\n");
-        deviceStatusXml.append("<CmdType>Alarm</CmdType>\r\n");
-        deviceStatusXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-        deviceStatusXml.append("<DeviceID>" + deviceAlarm.getChannelId() + "</DeviceID>\r\n");
-        deviceStatusXml.append("<AlarmPriority>" + deviceAlarm.getAlarmPriority() + "</AlarmPriority>\r\n");
-        deviceStatusXml.append("<AlarmMethod>" + deviceAlarm.getAlarmMethod() + "</AlarmMethod>\r\n");
-        deviceStatusXml.append("<AlarmTime>" + deviceAlarm.getAlarmTime() + "</AlarmTime>\r\n");
-        deviceStatusXml.append("<AlarmDescription>" + deviceAlarm.getAlarmDescription() + "</AlarmDescription>\r\n");
-        deviceStatusXml.append("<Longitude>" + deviceAlarm.getLongitude() + "</Longitude>\r\n");
-        deviceStatusXml.append("<Latitude>" + deviceAlarm.getLatitude() + "</Latitude>\r\n");
-        deviceStatusXml.append("<info>\r\n");
-        deviceStatusXml.append("<AlarmType>" + deviceAlarm.getAlarmType() + "</AlarmType>\r\n");
-        deviceStatusXml.append("</info>\r\n");
-        deviceStatusXml.append("</Notify>\r\n");
+        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Notify>\r\n")
+                .append("<CmdType>Alarm</CmdType>\r\n")
+                .append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n")
+                .append("<DeviceID>" + deviceAlarm.getChannelId() + "</DeviceID>\r\n")
+                .append("<AlarmPriority>" + deviceAlarm.getAlarmPriority() + "</AlarmPriority>\r\n")
+                .append("<AlarmMethod>" + deviceAlarm.getAlarmMethod() + "</AlarmMethod>\r\n")
+                .append("<AlarmTime>" + deviceAlarm.getAlarmTime() + "</AlarmTime>\r\n")
+                .append("<AlarmDescription>" + deviceAlarm.getAlarmDescription() + "</AlarmDescription>\r\n")
+                .append("<Longitude>" + deviceAlarm.getLongitude() + "</Longitude>\r\n")
+                .append("<Latitude>" + deviceAlarm.getLatitude() + "</Latitude>\r\n")
+                .append("<info>\r\n")
+                .append("<AlarmType>" + deviceAlarm.getAlarmType() + "</AlarmType>\r\n")
+                .append("</info>\r\n")
+                .append("</Notify>\r\n");
 
         CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
 
@@ -422,13 +460,13 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         StringBuffer catalogXml = new StringBuffer(600);
 
         String characterSet = parentPlatform.getCharacterSet();
-        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        catalogXml.append("<Notify>\r\n");
-        catalogXml.append("<CmdType>Catalog</CmdType>\r\n");
-        catalogXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        catalogXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
-        catalogXml.append("<SumNum>1</SumNum>\r\n");
-        catalogXml.append("<DeviceList Num=\"" + channels.size() + "\">\r\n");
+        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Notify>\r\n")
+                .append("<CmdType>Catalog</CmdType>\r\n")
+                .append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n")
+                .append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n")
+                .append("<SumNum>1</SumNum>\r\n")
+                .append("<DeviceList Num=\"" + channels.size() + "\">\r\n");
         if (channels.size() > 0) {
             for (DeviceChannel channel : channels) {
                 if (parentPlatform.getServerGBId().equals(channel.getParentId())) {
@@ -449,16 +487,16 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                 catalogXml.append("<Parental>" + channel.getParental() + "</Parental>\r\n");
                 if (channel.getParental() == 0) {
                     // 通道项
-                    catalogXml.append("<Manufacturer>" + channel.getManufacture() + "</Manufacturer>\r\n");
-                    catalogXml.append("<Secrecy>" + channel.getSecrecy() + "</Secrecy>\r\n");
-                    catalogXml.append("<RegisterWay>" + channel.getRegisterWay() + "</RegisterWay>\r\n");
-                    catalogXml.append("<Status>" + (channel.getStatus() == 0 ? "OFF" : "ON") + "</Status>\r\n");
+                    catalogXml.append("<Manufacturer>" + channel.getManufacture() + "</Manufacturer>\r\n")
+                            .append("<Secrecy>" + channel.getSecrecy() + "</Secrecy>\r\n")
+                            .append("<RegisterWay>" + channel.getRegisterWay() + "</RegisterWay>\r\n")
+                            .append("<Status>" + (channel.getStatus() == 0 ? "OFF" : "ON") + "</Status>\r\n");
 
                     if (channel.getChannelType() != 2) {  // 业务分组/虚拟组织/行政区划 不设置以下属性
-                        catalogXml.append("<Model>" + channel.getModel() + "</Model>\r\n");
-                        catalogXml.append("<Owner> " + channel.getOwner()+ "</Owner>\r\n");
-                        catalogXml.append("<CivilCode>" + channel.getCivilCode() + "</CivilCode>\r\n");
-                        catalogXml.append("<Address>" + channel.getAddress() + "</Address>\r\n");
+                        catalogXml.append("<Model>" + channel.getModel() + "</Model>\r\n")
+                                .append("<Owner> " + channel.getOwner()+ "</Owner>\r\n")
+                                .append("<CivilCode>" + channel.getCivilCode() + "</CivilCode>\r\n")
+                                .append("<Address>" + channel.getAddress() + "</Address>\r\n");
                     }
                     if (!"presence".equals(subscribeInfo.getEventType())) {
                         catalogXml.append("<Event>" + type + "</Event>\r\n");
@@ -468,8 +506,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                 catalogXml.append("</Item>\r\n");
             }
         }
-        catalogXml.append("</DeviceList>\r\n");
-        catalogXml.append("</Notify>\r\n");
+        catalogXml.append("</DeviceList>\r\n")
+                .append("</Notify>\r\n");
         return catalogXml.toString();
     }
 
@@ -515,26 +553,26 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
 
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer catalogXml = new StringBuffer(600);
-        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        catalogXml.append("<Notify>\r\n");
-        catalogXml.append("<CmdType>Catalog</CmdType>\r\n");
-        catalogXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        catalogXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
-        catalogXml.append("<SumNum>1</SumNum>\r\n");
-        catalogXml.append("<DeviceList Num=\" " + channels.size() + " \">\r\n");
+        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Notify>\r\n")
+                .append("<CmdType>Catalog</CmdType>\r\n")
+                .append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n")
+                .append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n")
+                .append("<SumNum>1</SumNum>\r\n")
+                .append("<DeviceList Num=\" " + channels.size() + " \">\r\n");
         if (channels.size() > 0) {
             for (DeviceChannel channel : channels) {
                 if (parentPlatform.getServerGBId().equals(channel.getParentId())) {
                     channel.setParentId(parentPlatform.getDeviceGBId());
                 }
-                catalogXml.append("<Item>\r\n");
-                catalogXml.append("<DeviceID>" + channel.getChannelId() + "</DeviceID>\r\n");
-                catalogXml.append("<Event>" + type + "</Event>\r\n");
-                catalogXml.append("</Item>\r\n");
+                catalogXml.append("<Item>\r\n")
+                        .append("<DeviceID>" + channel.getChannelId() + "</DeviceID>\r\n")
+                        .append("<Event>" + type + "</Event>\r\n")
+                        .append("</Item>\r\n");
             }
         }
-        catalogXml.append("</DeviceList>\r\n");
-        catalogXml.append("</Notify>\r\n");
+        catalogXml.append("</DeviceList>\r\n")
+                .append("</Notify>\r\n");
         return catalogXml.toString();
     }
     @Override
@@ -544,12 +582,12 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         }
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer recordXml = new StringBuffer(600);
-        recordXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        recordXml.append("<Response>\r\n");
-        recordXml.append("<CmdType>RecordInfo</CmdType>\r\n");
-        recordXml.append("<SN>" +recordInfo.getSn() + "</SN>\r\n");
-        recordXml.append("<DeviceID>" + recordInfo.getDeviceId() + "</DeviceID>\r\n");
-        recordXml.append("<SumNum>" + recordInfo.getSumNum() + "</SumNum>\r\n");
+        recordXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Response>\r\n")
+                .append("<CmdType>RecordInfo</CmdType>\r\n")
+                .append("<SN>" +recordInfo.getSn() + "</SN>\r\n")
+                .append("<DeviceID>" + recordInfo.getDeviceId() + "</DeviceID>\r\n")
+                .append("<SumNum>" + recordInfo.getSumNum() + "</SumNum>\r\n");
         if (recordInfo.getRecordList() == null ) {
             recordXml.append("<RecordList Num=\"0\">\r\n");
         }else {
@@ -558,12 +596,12 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                 for (RecordItem recordItem : recordInfo.getRecordList()) {
                     recordXml.append("<Item>\r\n");
                     if (deviceChannel != null) {
-                        recordXml.append("<DeviceID>" + recordItem.getDeviceId() + "</DeviceID>\r\n");
-                        recordXml.append("<Name>" + recordItem.getName() + "</Name>\r\n");
-                        recordXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getStartTime()) + "</StartTime>\r\n");
-                        recordXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getEndTime()) + "</EndTime>\r\n");
-                        recordXml.append("<Secrecy>" + recordItem.getSecrecy() + "</Secrecy>\r\n");
-                        recordXml.append("<Type>" + recordItem.getType() + "</Type>\r\n");
+                        recordXml.append("<DeviceID>" + recordItem.getDeviceId() + "</DeviceID>\r\n")
+                                .append("<Name>" + recordItem.getName() + "</Name>\r\n")
+                                .append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getStartTime()) + "</StartTime>\r\n")
+                                .append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getEndTime()) + "</EndTime>\r\n")
+                                .append("<Secrecy>" + recordItem.getSecrecy() + "</Secrecy>\r\n")
+                                .append("<Type>" + recordItem.getType() + "</Type>\r\n");
                         if (!ObjectUtils.isEmpty(recordItem.getFileSize())) {
                             recordXml.append("<FileSize>" + recordItem.getFileSize() + "</FileSize>\r\n");
                         }
@@ -576,8 +614,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
             }
         }
 
-        recordXml.append("</RecordList>\r\n");
-        recordXml.append("</Response>\r\n");
+        recordXml.append("</RecordList>\r\n")
+                .append("</Response>\r\n");
 
         // callid
         CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
@@ -596,13 +634,13 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
 
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer mediaStatusXml = new StringBuffer(200);
-        mediaStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        mediaStatusXml.append("<Notify>\r\n");
-        mediaStatusXml.append("<CmdType>MediaStatus</CmdType>\r\n");
-        mediaStatusXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-        mediaStatusXml.append("<DeviceID>" + sendRtpItem.getChannelId() + "</DeviceID>\r\n");
-        mediaStatusXml.append("<NotifyType>121</NotifyType>\r\n");
-        mediaStatusXml.append("</Notify>\r\n");
+        mediaStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Notify>\r\n")
+                .append("<CmdType>MediaStatus</CmdType>\r\n")
+                .append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n")
+                .append("<DeviceID>" + sendRtpItem.getChannelId() + "</DeviceID>\r\n")
+                .append("<NotifyType>121</NotifyType>\r\n")
+                .append("</Notify>\r\n");
 
         SIPRequest messageRequest = (SIPRequest)headerProviderPlatformProvider.createMessageRequest(parentPlatform, mediaStatusXml.toString(),
                 sendRtpItem);

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

@@ -16,7 +16,6 @@ import org.springframework.beans.factory.annotation.Autowired;
 
 import javax.sip.*;
 import javax.sip.address.Address;
-import javax.sip.address.AddressFactory;
 import javax.sip.address.SipURI;
 import javax.sip.header.ContentTypeHeader;
 import javax.sip.header.ExpiresHeader;
@@ -42,15 +41,6 @@ public abstract class SIPRequestProcessorParent {
 	@Autowired
 	private SIPSender sipSender;
 
-	public AddressFactory getAddressFactory() {
-		try {
-			return SipFactory.getInstance().createAddressFactory();
-		} catch (PeerUnavailableException e) {
-			e.printStackTrace();
-		}
-		return null;
-	}
-
 	public HeaderFactory getHeaderFactory() {
 		try {
 			return SipFactory.getInstance().createHeaderFactory();

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

@@ -275,7 +275,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                     }
                     return;
                 } else {
-                    logger.info("通道不存在,返回404");
+                    logger.info("通道不存在,返回404: {}", channelId);
                     try {
                         // 通道不存在,发404,资源不存在
                         responseAck(request, Response.NOT_FOUND);

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

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 
+import com.genersoft.iot.vmp.conf.ServiceInfo;
 import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper;
@@ -82,6 +83,19 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
             RequestEventExt evtExt = (RequestEventExt) evt;
             String requestAddress = evtExt.getRemoteIpAddress() + ":" + evtExt.getRemotePort();
             logger.info("[注册请求] 开始处理: {}", requestAddress);
+//            MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
+//            QueryExp protocol = Query.match(Query.attr("protocol"), Query.value("HTTP/1.1"));
+////            ObjectName name = new ObjectName("*:type=Connector,*");
+//            ObjectName name = new ObjectName("*:*");
+//            Set<ObjectName> objectNames = beanServer.queryNames(name, protocol);
+//            for (ObjectName objectName : objectNames) {
+//                String catalina = objectName.getDomain();
+//                if ("Catalina".equals(catalina)) {
+//                    System.out.println(objectName.getKeyProperty("port"));
+//                }
+//            }
+
+            System.out.println(ServiceInfo.getServerPort());
             SIPRequest request = (SIPRequest)evt.getRequest();
             Response response = null;
             boolean passwordCorrect = false;

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

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

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

@@ -181,11 +181,14 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
                             }
                         }
                         logger.info("[收到报警通知]内容:{}", JSON.toJSONString(deviceAlarm));
-                        if ("7".equals(deviceAlarm.getAlarmMethod()) ) {
+                        // 作者自用判断,其他小伙伴需要此消息可以自行修改,但是不要提在pr里
+                        if (DeviceAlarmMethod.Other.getVal() == Integer.parseInt(deviceAlarm.getAlarmMethod())) {
                             // 发送给平台的报警信息。 发送redis通知
+                            logger.info("[发送给平台的报警信息]内容:{}", JSONObject.toJSONString(deviceAlarm));
                             AlarmChannelMessage alarmChannelMessage = new AlarmChannelMessage();
                             alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod()));
                             alarmChannelMessage.setAlarmDescription(deviceAlarm.getAlarmDescription());
+                            alarmChannelMessage.setAlarmType(Integer.parseInt(deviceAlarm.getAlarmType()));
                             alarmChannelMessage.setGbId(channelId);
                             redisCatchStorage.sendAlarmMsg(alarmChannelMessage);
                             continue;
@@ -264,6 +267,7 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
             alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod()));
             alarmChannelMessage.setAlarmDescription(deviceAlarm.getAlarmDescription());
             alarmChannelMessage.setGbId(channelId);
+            alarmChannelMessage.setAlarmType(Integer.parseInt(deviceAlarm.getAlarmType()));
             redisCatchStorage.sendAlarmMsg(alarmChannelMessage);
             return;
         }

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

@@ -1,9 +1,10 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.cmd;
 
 import com.genersoft.iot.vmp.conf.SipConfig;
-import com.genersoft.iot.vmp.gb28181.bean.*;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
+import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
-import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
@@ -63,7 +64,6 @@ public class CatalogQueryMessageHandler extends SIPRequestProcessorParent implem
     @Override
     public void handForPlatform(RequestEvent evt, ParentPlatform parentPlatform, Element rootElement) {
 
-        String key = DeferredResultHolder.CALLBACK_CMD_CATALOG + parentPlatform.getServerGBId();
         FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME);
         try {
             // 回复200 OK

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

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

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

@@ -102,8 +102,9 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
                         Element recordListElement = rootElementForCharset.element("RecordList");
                         if (recordListElement == null || sumNum == 0) {
                             logger.info("无录像数据");
+                            int count = recordDataCatch.put(take.getDevice().getDeviceId(),channelId, sn, sumNum, new ArrayList<>());
+                            recordInfo.setCount(count);
                             eventPublisher.recordEndEventPush(recordInfo);
-                            recordDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, new ArrayList<>());
                             releaseRequest(take.getDevice().getDeviceId(), sn);
                         } else {
                             Iterator<Element> recordListIterator = recordListElement.elementIterator();
@@ -137,12 +138,11 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
                                     recordList.add(record);
                                 }
                                 recordInfo.setRecordList(recordList);
+                                int count = recordDataCatch.put(take.getDevice().getDeviceId(),channelId, sn, sumNum, recordList);recordInfo.setCount(count);
+                                logger.info("[国标录像], {}->{}: {}/{}", take.getDevice().getDeviceId(), sn, count, sumNum);
                                 // 发送消息,如果是上级查询此录像,则会通过这里通知给上级
                                 eventPublisher.recordEndEventPush(recordInfo);
-                                int count = recordDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, recordList);
-                                logger.info("[国标录像], {}->{}: {}/{}", take.getDevice().getDeviceId(), sn, count, sumNum);
                             }
-
                             if (recordDataCatch.isComplete(take.getDevice().getDeviceId(), sn)){
                                 releaseRequest(take.getDevice().getDeviceId(), sn);
                             }

+ 17 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/utils/MessageElement.java

@@ -0,0 +1,17 @@
+package com.genersoft.iot.vmp.gb28181.utils;
+
+import java.lang.annotation.*;
+
+/**
+ * @author gaofuwang
+ * @version 1.0
+ * @date 2022/6/28 14:58
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface MessageElement {
+    String value();
+
+    String subVal() default "";
+}

+ 78 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.utils;
 
+import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONArray;
 import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
@@ -15,12 +16,16 @@ import org.dom4j.io.SAXReader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
+import org.springframework.util.ReflectionUtils;
 
 import javax.sip.RequestEvent;
 import javax.sip.message.Request;
 import java.io.ByteArrayInputStream;
 import java.io.StringReader;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
 import java.util.*;
 
 /**
@@ -411,4 +416,76 @@ public class XmlUtil {
         }
         return deviceChannel;
     }
+
+    /**
+     * 新增方法支持内部嵌套
+     *
+     * @param element xmlElement
+     * @param clazz 结果类
+     * @param <T> 泛型
+     * @return 结果对象
+     * @throws NoSuchMethodException
+     * @throws InvocationTargetException
+     * @throws InstantiationException
+     * @throws IllegalAccessException
+     */
+    public static <T> T loadElement(Element element, Class<T> clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
+        Field[] fields = clazz.getDeclaredFields();
+        T t = clazz.getDeclaredConstructor().newInstance();
+        for (Field field : fields) {
+            ReflectionUtils.makeAccessible(field);
+            MessageElement annotation = field.getAnnotation(MessageElement.class);
+            if (annotation == null) {
+                continue;
+            }
+            String value = annotation.value();
+            String subVal = annotation.subVal();
+            Element element1 = element.element(value);
+            if (element1 == null) {
+                continue;
+            }
+            if ("".equals(subVal)) {
+                // 无下级数据
+                Object fieldVal = element1.isTextOnly() ? element1.getText() : loadElement(element1, field.getType());
+                Object o = simpleTypeDeal(field.getType(), fieldVal);
+                ReflectionUtils.setField(field, t,  o);
+            } else {
+                // 存在下级数据
+                ArrayList<Object> list = new ArrayList<>();
+                Type genericType = field.getGenericType();
+                if (!(genericType instanceof ParameterizedType)) {
+                    continue;
+                }
+                Class<?> aClass = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
+                for (Element element2 : element1.elements(subVal)) {
+                    list.add(loadElement(element2, aClass));
+                }
+                ReflectionUtils.setField(field, t, list);
+            }
+        }
+        return t;
+    }
+
+    /**
+     * 简单类型处理
+     *
+     * @param tClass
+     * @param val
+     * @return
+     */
+    private static Object simpleTypeDeal(Class<?> tClass, Object val) {
+        if (tClass.equals(String.class)) {
+            return val.toString();
+        }
+        if (tClass.equals(Integer.class)) {
+            return Integer.valueOf(val.toString());
+        }
+        if (tClass.equals(Double.class)) {
+            return Double.valueOf(val.toString());
+        }
+        if (tClass.equals(Long.class)) {
+            return Long.valueOf(val.toString());
+        }
+        return val;
+    }
 }

File diff suppressed because it is too large
+ 534 - 536
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java


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

@@ -36,7 +36,7 @@ public class ZLMRESTfulUtils {
             // 设置连接超时时间
             httpClientBuilder.connectTimeout(5,TimeUnit.SECONDS);
             // 设置读取超时时间
-            httpClientBuilder.readTimeout(15,TimeUnit.SECONDS);
+            httpClientBuilder.readTimeout(10,TimeUnit.SECONDS);
             // 设置连接池
             httpClientBuilder.connectionPool(new ConnectionPool(16, 5, TimeUnit.MINUTES));
             if (logger.isDebugEnabled()) {
@@ -189,6 +189,7 @@ public class ZLMRESTfulUtils {
                     FileOutputStream outStream = new FileOutputStream(snapFile);
 
                     outStream.write(Objects.requireNonNull(response.body()).bytes());
+                    outStream.flush();
                     outStream.close();
                 } else {
                     logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message()));

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

@@ -0,0 +1,36 @@
+package com.genersoft.iot.vmp.media.zlm.dto.hook;
+
+public class HookResult {
+
+    private int code;
+    private String msg;
+
+
+    public HookResult() {
+    }
+
+    public HookResult(int code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public static HookResult SUCCESS(){
+        return new HookResult(0, "success");
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+}

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

@@ -0,0 +1,44 @@
+package com.genersoft.iot.vmp.media.zlm.dto.hook;
+
+public class HookResultForOnPublish extends HookResult{
+
+    private boolean enable_audio;
+    private boolean enable_mp4;
+    private int mp4_max_second;
+
+    public HookResultForOnPublish() {
+    }
+
+    public static HookResultForOnPublish SUCCESS(){
+        return new HookResultForOnPublish(0, "success");
+    }
+
+    public HookResultForOnPublish(int code, String msg) {
+        setCode(code);
+        setMsg(msg);
+    }
+
+    public boolean isEnable_audio() {
+        return enable_audio;
+    }
+
+    public void setEnable_audio(boolean enable_audio) {
+        this.enable_audio = enable_audio;
+    }
+
+    public boolean isEnable_mp4() {
+        return enable_mp4;
+    }
+
+    public void setEnable_mp4(boolean enable_mp4) {
+        this.enable_mp4 = enable_mp4;
+    }
+
+    public int getMp4_max_second() {
+        return mp4_max_second;
+    }
+
+    public void setMp4_max_second(int mp4_max_second) {
+        this.mp4_max_second = mp4_max_second;
+    }
+}

+ 4 - 0
src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java

@@ -152,6 +152,10 @@ public class GbStreamServiceImpl implements IGbStreamService {
 
     @Override
     public void sendCatalogMsg(GbStream gbStream, String type) {
+        if (gbStream == null || type == null) {
+            logger.warn("[发送目录订阅]类型:流信息或类型为NULL");
+            return;
+        }
         List<GbStream> gbStreams = new ArrayList<>();
         if (gbStream.getGbId() != null) {
             gbStreams.add(gbStream);

+ 10 - 4
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java

@@ -33,6 +33,7 @@ import com.genersoft.iot.vmp.service.bean.SSRCInfo;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.dao.MediaServerMapper;
 import com.genersoft.iot.vmp.utils.DateUtil;
+import com.genersoft.iot.vmp.utils.JsonUtil;
 import com.genersoft.iot.vmp.utils.redis.RedisUtil;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import okhttp3.OkHttpClient;
@@ -241,7 +242,10 @@ public class MediaServerServiceImpl implements IMediaServerService {
         String onlineKey = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetting.getServerId();
         for (Object mediaServerKey : mediaServerKeys) {
             String key = (String) mediaServerKey;
-            MediaServerItem mediaServerItem = (MediaServerItem) RedisUtil.get(key);
+            MediaServerItem mediaServerItem = JsonUtil.redisJsonToObject(key, MediaServerItem.class);
+            if (Objects.isNull(mediaServerItem)) {
+                continue;
+            }
             // 检查状态
             Double aDouble = RedisUtil.zScore(onlineKey, mediaServerItem.getId());
             if (aDouble != null) {
@@ -293,7 +297,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
             return null;
         }
         String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + mediaServerId;
-        return (MediaServerItem)RedisUtil.get(key);
+        return JsonUtil.redisJsonToObject(key, MediaServerItem.class);
     }
 
 
@@ -410,8 +414,10 @@ public class MediaServerServiceImpl implements IMediaServerService {
             SsrcConfig ssrcConfig = new SsrcConfig(zlmServerConfig.getGeneralMediaServerId(), null, sipConfig.getDomain());
             serverItem.setSsrcConfig(ssrcConfig);
         }else {
-            MediaServerItem mediaServerItemInRedis = (MediaServerItem)RedisUtil.get(key);
-            serverItem.setSsrcConfig(mediaServerItemInRedis.getSsrcConfig());
+            MediaServerItem mediaServerItemInRedis = JsonUtil.redisJsonToObject(key, MediaServerItem.class);
+            if (Objects.nonNull(mediaServerItemInRedis)) {
+                serverItem.setSsrcConfig(mediaServerItemInRedis.getSsrcConfig());
+            }
         }
         RedisUtil.set(key, serverItem);
         resetOnlineServerItem(serverItem);

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

@@ -184,7 +184,9 @@ public class StreamPushServiceImpl implements IStreamPushService {
     @Override
     public boolean stop(String app, String streamId) {
         StreamPushItem streamPushItem = streamPushMapper.selectOne(app, streamId);
-        gbStreamService.sendCatalogMsg(streamPushItem, CatalogEvent.DEL);
+        if (streamPushItem != null) {
+            gbStreamService.sendCatalogMsg(streamPushItem, CatalogEvent.DEL);
+        }
 
         platformGbStreamMapper.delByAppAndStream(app, streamId);
         gbStreamMapper.del(app, streamId);

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

@@ -62,16 +62,16 @@ public class RedisAlarmMsgListener implements MessageListener {
                         }
                         String gbId = alarmChannelMessage.getGbId();
 
-                    DeviceAlarm deviceAlarm = new DeviceAlarm();
-                    deviceAlarm.setCreateTime(DateUtil.getNow());
-                    deviceAlarm.setChannelId(gbId);
-                    deviceAlarm.setAlarmDescription(alarmChannelMessage.getAlarmDescription());
-                    deviceAlarm.setAlarmMethod("" + alarmChannelMessage.getAlarmSn());
-                    deviceAlarm.setAlarmPriority("1");
-                    deviceAlarm.setAlarmTime(DateUtil.getNowForISO8601());
-                    deviceAlarm.setAlarmType("1");
-                    deviceAlarm.setLongitude(0D);
-                    deviceAlarm.setLatitude(0D);
+                        DeviceAlarm deviceAlarm = new DeviceAlarm();
+                        deviceAlarm.setCreateTime(DateUtil.getNow());
+                        deviceAlarm.setChannelId(gbId);
+                        deviceAlarm.setAlarmDescription(alarmChannelMessage.getAlarmDescription());
+                        deviceAlarm.setAlarmMethod("" + alarmChannelMessage.getAlarmSn());
+                        deviceAlarm.setAlarmType("" + alarmChannelMessage.getAlarmType());
+                        deviceAlarm.setAlarmPriority("1");
+                        deviceAlarm.setAlarmTime(DateUtil.getNowForISO8601());
+                        deviceAlarm.setLongitude(0);
+                        deviceAlarm.setLatitude(0);
 
                         if (ObjectUtils.isEmpty(gbId)) {
                             // 发送给所有的上级

+ 9 - 2
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java

@@ -2,7 +2,6 @@ package com.genersoft.iot.vmp.storager;
 
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
-import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
 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;
@@ -186,7 +185,13 @@ public interface IVideoManagerStorage {
 
     Device queryVideoDeviceByPlatformIdAndChannelId(String platformId, String channelId);
 
-
+	/**
+	 * 针对deviceinfo指令的查询接口
+	 * @param platformId 平台id
+	 * @param channelId 通道id
+	 * @return 设备信息
+	 */
+	Device queryDeviceInfoByPlatformIdAndChannelId(String platformId, String channelId);
 	/**
 	 * 添加Mobile Position设备移动位置
 	 * @param mobilePosition
@@ -324,6 +329,8 @@ public interface IVideoManagerStorage {
 	 */
 	boolean resetChannels(String deviceId, List<DeviceChannel> deviceChannelList);
 
+	boolean updateChannels(String deviceId, List<DeviceChannel> deviceChannelList);
+
 	/**
 	 * 获取目录信息
 	 * @param platformId

+ 5 - 4
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceAlarmMapper.java

@@ -1,9 +1,10 @@
 package com.genersoft.iot.vmp.storager.dao;
 
 import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
-import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
-import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
-import org.apache.ibatis.annotations.*;
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
 import org.springframework.stereotype.Repository;
 
 import java.util.List;
@@ -20,7 +21,7 @@ public interface DeviceAlarmMapper {
     int add(DeviceAlarm alarm);
 
 
-    @Select(value = {" <script>" +
+    @Select( value = {" <script>" +
             " SELECT * FROM device_alarm " +
             " WHERE 1=1 " +
             " <if test=\"deviceId != null\" >  AND deviceId = #{deviceId}</if>" +

+ 10 - 0
src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformChannelMapper.java

@@ -107,4 +107,14 @@ public interface PlatformChannelMapper {
             "DELETE FROM platform_gb_channel WHERE platformId=#{platformId} and catalogId=#{catalogId}"  +
             "</script>")
     int delChannelForGBByCatalogId(String platformId, String catalogId);
+
+    @Select("select dc.channelId deviceId,dc.name,d.manufacturer,d.model,d.firmware\n" +
+            "from platform_gb_channel pgc\n" +
+            "         left join device_channel dc on dc.id = pgc.deviceChannelId\n" +
+            "         left join device d on dc.deviceId = d.deviceId\n" +
+            "where dc.channelId = #{channelId} and pgc.platformId=#{platformId}")
+    List<Device> queryDeviceInfoByPlatformIdAndChannelId(String platformId, String channelId);
+
+    @Select("SELECT pgc.platformId FROM platform_gb_channel pgc left join device_channel dc on dc.id = pgc.deviceChannelId WHERE dc.channelId='${channelId}'")
+    List<String> queryParentPlatformByChannelId(String channelId);
 }

+ 15 - 12
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java

@@ -17,6 +17,7 @@ import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
 import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
 import com.genersoft.iot.vmp.utils.DateUtil;
+import com.genersoft.iot.vmp.utils.JsonUtil;
 import com.genersoft.iot.vmp.utils.SystemInfoUtils;
 import com.genersoft.iot.vmp.utils.redis.RedisUtil;
 import org.slf4j.Logger;
@@ -157,7 +158,10 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
         }
         for (Object player : players) {
             String key = (String) player;
-            StreamInfo streamInfo = (StreamInfo) RedisUtil.get(key);
+            StreamInfo streamInfo = JsonUtil.redisJsonToObject(key, StreamInfo.class);
+            if (Objects.isNull(streamInfo)) {
+                continue;
+            }
             streamInfos.put(streamInfo.getDeviceID() + "_" + streamInfo.getChannelId(), streamInfo);
         }
         return streamInfos;
@@ -624,8 +628,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
     @Override
     public ThirdPartyGB queryMemberNoGBId(String queryKey) {
         String key = VideoManagerConstants.WVP_STREAM_GB_ID_PREFIX + queryKey;
-        JSONObject jsonObject = (JSONObject)RedisUtil.get(key);
-        return  jsonObject.to(ThirdPartyGB.class);
+        return JsonUtil.redisJsonToObject(key, ThirdPartyGB.class);
     }
 
     @Override
@@ -664,7 +667,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
     @Override
     public Device getDevice(String deviceId) {
         String key = VideoManagerConstants.DEVICE_PREFIX + userSetting.getServerId() + "_" + deviceId;
-        return (Device)RedisUtil.get(key);
+        return JsonUtil.redisJsonToObject(key, Device.class);
     }
 
     @Override
@@ -676,7 +679,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
     @Override
     public GPSMsgInfo getGpsMsgInfo(String gbId) {
         String key = VideoManagerConstants.WVP_STREAM_GPS_MSG_PREFIX + userSetting.getServerId() + "_" + gbId;
-        return (GPSMsgInfo)RedisUtil.get(key);
+        return JsonUtil.redisJsonToObject(key, GPSMsgInfo.class);
     }
 
     @Override
@@ -686,9 +689,9 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
         List<Object> keys = RedisUtil.scan(scanKey);
         for (Object o : keys) {
             String key = (String) o;
-            GPSMsgInfo gpsMsgInfo = (GPSMsgInfo) RedisUtil.get(key);
-            if (!gpsMsgInfo.isStored()) { // 只取没有存过得
-                result.add((GPSMsgInfo) RedisUtil.get(key));
+            GPSMsgInfo gpsMsgInfo = JsonUtil.redisJsonToObject(key, GPSMsgInfo.class);
+            if (Objects.nonNull(gpsMsgInfo) && !gpsMsgInfo.isStored()) { // 只取没有存过得
+                result.add(JsonUtil.redisJsonToObject(key, GPSMsgInfo.class));
             }
         }
 
@@ -710,7 +713,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
     @Override
     public StreamAuthorityInfo getStreamAuthorityInfo(String app, String stream) {
         String key = VideoManagerConstants.MEDIA_STREAM_AUTHORITY + userSetting.getServerId() + "_" + app+ "_" + stream ;
-        return (StreamAuthorityInfo) RedisUtil.get(key);
+        return JsonUtil.redisJsonToObject(key, StreamAuthorityInfo.class);
 
     }
 
@@ -721,7 +724,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
         List<Object> keys = RedisUtil.scan(scanKey);
         for (Object o : keys) {
             String key = (String) o;
-            result.add((StreamAuthorityInfo) RedisUtil.get(key));
+            result.add(JsonUtil.redisJsonToObject(key, StreamAuthorityInfo.class));
         }
         return result;
     }
@@ -735,7 +738,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
         List<Object> keys = RedisUtil.scan(scanKey);
         if (keys.size() > 0) {
             String key = (String) keys.get(0);
-            result = (OnStreamChangedHookParam)RedisUtil.get(key);
+            result = JsonUtil.redisJsonToObject(key, OnStreamChangedHookParam.class);
         }
 
         return result;
@@ -827,7 +830,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
 
     @Override
     public void sendAlarmMsg(AlarmChannelMessage msg) {
-        String key = VideoManagerConstants.VM_MSG_SUBSCRIBE_ALARM;
+        String key = VideoManagerConstants.VM_MSG_SUBSCRIBE_ALARM_RECEIVE;
         logger.info("[redis发送通知] 报警{}: {}", key, JSON.toJSON(msg));
         RedisUtil.convertAndSend(key, (JSONObject)JSON.toJSON(msg));
     }

+ 136 - 0
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java

@@ -126,6 +126,15 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 					if (allChannelMap.containsKey(deviceChannel.getChannelId())) {
 						deviceChannel.setStreamId(allChannelMap.get(deviceChannel.getChannelId()).getStreamId());
 						deviceChannel.setHasAudio(allChannelMap.get(deviceChannel.getChannelId()).isHasAudio());
+						if (allChannelMap.get(deviceChannel.getChannelId()).getStatus() !=deviceChannel.getStatus()){
+							List<String> strings = platformChannelMapper.queryParentPlatformByChannelId(deviceChannel.getChannelId());
+							if (!CollectionUtils.isEmpty(strings)){
+								strings.forEach(platformId->{
+									eventPublisher.catalogEventPublish(platformId, deviceChannel, deviceChannel.getStatus()==1?CatalogEvent.ON:CatalogEvent.OFF);
+								});
+							}
+
+						}
 					}
 					channels.add(deviceChannel);
 					if (!ObjectUtils.isEmpty(deviceChannel.getParentId())) {
@@ -187,6 +196,119 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 
 	}
 
+
+	@Override
+	public boolean updateChannels(String deviceId, List<DeviceChannel> deviceChannelList) {
+		if (CollectionUtils.isEmpty(deviceChannelList)) {
+			return false;
+		}
+		List<DeviceChannel> allChannels = deviceChannelMapper.queryAllChannels(deviceId);
+		Map<String,DeviceChannel> allChannelMap = new ConcurrentHashMap<>();
+		if (allChannels.size() > 0) {
+			for (DeviceChannel deviceChannel : allChannels) {
+				allChannelMap.put(deviceChannel.getChannelId(), deviceChannel);
+			}
+		}
+		List<DeviceChannel> addChannels = new ArrayList<>();
+		List<DeviceChannel> updateChannels = new ArrayList<>();
+
+
+		TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
+		// 数据去重
+		StringBuilder stringBuilder = new StringBuilder();
+		Map<String, Integer> subContMap = new HashMap<>();
+		if (deviceChannelList.size() > 0) {
+			// 数据去重
+			Set<String> gbIdSet = new HashSet<>();
+			for (DeviceChannel deviceChannel : deviceChannelList) {
+				if (!gbIdSet.contains(deviceChannel.getChannelId())) {
+					gbIdSet.add(deviceChannel.getChannelId());
+					if (allChannelMap.containsKey(deviceChannel.getChannelId())) {
+						deviceChannel.setStreamId(allChannelMap.get(deviceChannel.getChannelId()).getStreamId());
+						deviceChannel.setHasAudio(allChannelMap.get(deviceChannel.getChannelId()).isHasAudio());
+						updateChannels.add(deviceChannel);
+					}else {
+						addChannels.add(deviceChannel);
+					}
+					if (!ObjectUtils.isEmpty(deviceChannel.getParentId())) {
+						if (subContMap.get(deviceChannel.getParentId()) == null) {
+							subContMap.put(deviceChannel.getParentId(), 1);
+						}else {
+							Integer count = subContMap.get(deviceChannel.getParentId());
+							subContMap.put(deviceChannel.getParentId(), count++);
+						}
+					}
+				}else {
+					stringBuilder.append(deviceChannel.getChannelId()).append(",");
+				}
+			}
+			if (addChannels.size() > 0) {
+				for (DeviceChannel channel : addChannels) {
+					if (subContMap.get(channel.getChannelId()) != null){
+						channel.setSubCount(subContMap.get(channel.getChannelId()));
+					}
+				}
+			}
+			if (updateChannels.size() > 0) {
+				for (DeviceChannel channel : updateChannels) {
+					if (subContMap.get(channel.getChannelId()) != null){
+						channel.setSubCount(subContMap.get(channel.getChannelId()));
+					}
+				}
+			}
+
+		}
+		if (stringBuilder.length() > 0) {
+			logger.info("[目录查询]收到的数据存在重复: {}" , stringBuilder);
+		}
+		if(CollectionUtils.isEmpty(updateChannels) && CollectionUtils.isEmpty(addChannels) ){
+			logger.info("通道更新,数据为空={}" , deviceChannelList);
+			return false;
+		}
+		try {
+			int limitCount = 300;
+			boolean result = false;
+			if (addChannels.size() > 0) {
+				if (addChannels.size() > limitCount) {
+					for (int i = 0; i < addChannels.size(); i += limitCount) {
+						int toIndex = i + limitCount;
+						if (i + limitCount > addChannels.size()) {
+							toIndex = addChannels.size();
+						}
+						result = result || deviceChannelMapper.batchAdd(addChannels.subList(i, toIndex)) < 0;
+					}
+				}else {
+					result = result || deviceChannelMapper.batchAdd(addChannels) < 0;
+				}
+			}
+			if (updateChannels.size() > 0) {
+				if (updateChannels.size() > limitCount) {
+					for (int i = 0; i < updateChannels.size(); i += limitCount) {
+						int toIndex = i + limitCount;
+						if (i + limitCount > updateChannels.size()) {
+							toIndex = updateChannels.size();
+						}
+						result = result || deviceChannelMapper.batchUpdate(updateChannels.subList(i, toIndex)) < 0;
+					}
+				}else {
+					result = result || deviceChannelMapper.batchUpdate(updateChannels) < 0;
+				}
+			}
+			if (result) {
+				//事务回滚
+				dataSourceTransactionManager.rollback(transactionStatus);
+			}else {
+				//手动提交
+				dataSourceTransactionManager.commit(transactionStatus);
+			}
+			return true;
+		}catch (Exception e) {
+			e.printStackTrace();
+			dataSourceTransactionManager.rollback(transactionStatus);
+			return false;
+		}
+	}
+
 	@Override
 	public void deviceChannelOnline(String deviceId, String channelId) {
 		deviceChannelMapper.online(deviceId, channelId);
@@ -464,6 +586,20 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 
 	}
 
+	@Override
+	public Device queryDeviceInfoByPlatformIdAndChannelId(String platformId, String channelId) {
+		List<Device> devices = platformChannelMapper.queryDeviceInfoByPlatformIdAndChannelId(platformId, channelId);
+		if (devices.size() > 1) {
+			// 出现长度大于0的时候肯定是国标通道的ID重复了
+			logger.warn("国标ID存在重复:{}", channelId);
+		}
+		if (devices.size() == 0) {
+			return null;
+		}else {
+			return devices.get(0);
+		}
+	}
+
 	/**
 	 * 查询最新移动位置
 	 * @param deviceId

+ 36 - 0
src/main/java/com/genersoft/iot/vmp/utils/JsonUtil.java

@@ -0,0 +1,36 @@
+package com.genersoft.iot.vmp.utils;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.genersoft.iot.vmp.utils.redis.RedisUtil;
+
+import java.util.Objects;
+
+/**
+ * JsonUtil
+ *
+ * @author KunLong-Luo
+ * @version 1.0.0
+ * @since 2023/2/2 15:24
+ */
+public final class JsonUtil {
+
+    private JsonUtil() {
+    }
+
+    /**
+     * safe json type conversion
+     *
+     * @param key   redis key
+     * @param clazz cast type
+     * @param <T>
+     * @return result type
+     */
+    public static <T> T redisJsonToObject(String key, Class<T> clazz) {
+        Object jsonObject = RedisUtil.get(key);
+        if (Objects.isNull(jsonObject)) {
+            return null;
+        }
+        return clazz.cast(jsonObject);
+    }
+}

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

@@ -110,7 +110,7 @@ public class DeviceControl {
 				msg.setKey(key);
 				msg.setData(String.format("开始/停止录像操作失败,错误码: %s, %s", event.statusCode, event.msg));
 				resultHolder.invokeAllResult(msg);
-			});
+			},null);
 		} catch (InvalidArgumentException | SipException | ParseException e) {
 			logger.error("[命令发送失败] 开始/停止录像: {}", e.getMessage());
 			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
@@ -143,7 +143,7 @@ public class DeviceControl {
 				msg.setKey(key);
 				msg.setData(String.format("布防/撤防操作失败,错误码: %s, %s", event.statusCode, event.msg));
 				resultHolder.invokeResult(msg);
-			});
+			},null);
 		} catch (InvalidArgumentException | SipException | ParseException e) {
 			logger.error("[命令发送失败] 布防/撤防操作: {}", e.getMessage());
 			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage());
@@ -192,7 +192,7 @@ public class DeviceControl {
 				msg.setKey(key);
 				msg.setData(String.format("报警复位操作失败,错误码: %s, %s", event.statusCode, event.msg));
 				resultHolder.invokeResult(msg);
-			});
+			},null);
 		} catch (InvalidArgumentException | SipException | ParseException e) {
 			logger.error("[命令发送失败] 报警复位: {}", e.getMessage());
 			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
@@ -274,7 +274,7 @@ public class DeviceControl {
 				msg.setKey(key);
 				msg.setData(String.format("看守位控制操作失败,错误码: %s, %s", event.statusCode, event.msg));
 				resultHolder.invokeResult(msg);
-			});
+			},null);
 		} catch (InvalidArgumentException | SipException | ParseException e) {
 			logger.error("[命令发送失败] 看守位控制: {}", e.getMessage());
 			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());

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

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.vmanager.gb28181.playback;
 
 import com.genersoft.iot.vmp.common.StreamInfo;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
 import com.genersoft.iot.vmp.conf.exception.ServiceException;
 import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
@@ -64,13 +65,16 @@ public class PlaybackController {
 	@Autowired
 	private DeferredResultHolder resultHolder;
 
+	@Autowired
+	private UserSetting userSetting;
+
 	@Operation(summary = "开始视频回放")
 	@Parameter(name = "deviceId", description = "设备国标编号", required = true)
 	@Parameter(name = "channelId", description = "通道国标编号", required = true)
 	@Parameter(name = "startTime", description = "开始时间", required = true)
 	@Parameter(name = "endTime", description = "结束时间", required = true)
 	@GetMapping("/start/{deviceId}/{channelId}")
-	public DeferredResult<WVPResult<StreamContent>> play(@PathVariable String deviceId, @PathVariable String channelId,
+	public DeferredResult<WVPResult<StreamContent>> start(@PathVariable String deviceId, @PathVariable String channelId,
 														 String startTime, String endTime) {
 
 		if (logger.isDebugEnabled()) {
@@ -79,7 +83,7 @@ public class PlaybackController {
 
 		String uuid = UUID.randomUUID().toString();
 		String key = DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId;
-		DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>(30000L);
+		DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
 		resultHolder.put(key, uuid, result);
 
 		WVPResult<StreamContent> wvpResult = new WVPResult<>();

+ 1 - 5
src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java

@@ -11,17 +11,13 @@ 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.tags.Tag;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.util.DigestUtils;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 
 import javax.security.sasl.AuthenticationException;
@@ -90,7 +86,7 @@ public class UserController {
 
 
     @PostMapping("/add")
-    @Operation(summary = "停止视频回放")
+    @Operation(summary = "添加用户")
     @Parameter(name = "username", description = "用户名", required = true)
     @Parameter(name = "password", description = "密码(未md5加密的密码)", required = true)
     @Parameter(name = "roleId", description = "角色ID", required = true)

+ 1 - 1
src/main/resources/all-application.yml

@@ -167,7 +167,7 @@ user-settings:
     senior-sdp: false
     # 保存移动位置历史轨迹:true:保留历史数据,false:仅保留最后的位置(默认)
     save-position-history: false
-    # 点播等待超时时间,单位:毫秒
+    # 点播/录像回放 等待超时时间,单位:毫秒
     play-timeout: 18000
     # 上级点播等待超时时间,单位:毫秒
     platform-play-timeout: 60000

+ 74 - 74
src/main/resources/application-dev.yml

@@ -1,86 +1,86 @@
 spring:
-    # [可选]上传文件大小限制
-    servlet:
-        multipart:
-            max-file-size: 10MB
-            max-request-size: 100MB
-    # REDIS数据库配置
-    redis:
-        # [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1
-        host: 127.0.0.1
-        # [必须修改] 端口号
-        port: 6379
-        # [可选] 数据库 DB
-        database: 6
-        # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接
-        password: face2020
-        # [可选] 超时时间
-        timeout: 10000
-        # mysql数据源
-    datasource:
-        type: com.alibaba.druid.pool.DruidDataSource
-        driver-class-name: com.mysql.cj.jdbc.Driver
-        url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true
-        username: root
-        password: 123456
-        druid:
-            initialSize: 10                       # 连接池初始化连接数
-            maxActive: 200                        # 连接池最大连接数
-            minIdle: 5                            # 连接池最小空闲连接数
-            maxWait: 60000                        # 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
-            keepAlive: true                       # 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作。
-            validationQuery: select 1             # 检测连接是否有效sql,要求是查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
-            testWhileIdle: true                   # 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
-            testOnBorrow: false                   # 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
-            testOnReturn: false                   # 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
-            poolPreparedStatements: false         # 是否開啟PSCache,並且指定每個連線上PSCache的大小
-            timeBetweenEvictionRunsMillis: 60000  # 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒
-            minEvictableIdleTimeMillis: 300000    # 配置一個連線在池中最小生存的時間,單位是毫秒
-            filters: stat,slf4j             # 配置监控统计拦截的filters,监控统计用的filter:sta, 日志用的filter:log4j
-            useGlobalDataSourceStat: true         # 合并多个DruidDataSource的监控数据
-            # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
-            connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=1000
-            #stat-view-servlet.url-pattern: /admin/druid/*
+  # [可选]上传文件大小限制
+  servlet:
+    multipart:
+      max-file-size: 10MB
+      max-request-size: 100MB
+  # REDIS数据库配置
+  redis:
+    # [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1
+    host: 127.0.0.1
+    # [必须修改] 端口号
+    port: 6379
+    # [可选] 数据库 DB
+    database: 6
+    # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接
+    password: face2020
+    # [可选] 超时时间
+    timeout: 10000
+    # mysql数据源
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    url: jdbc:mysql://127.0.0.1:3306/wvp2?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true
+    username: root
+    password: 123456
+    druid:
+      initialSize: 10                       # 连接池初始化连接数
+      maxActive: 200                        # 连接池最大连接数
+      minIdle: 5                            # 连接池最小空闲连接数
+      maxWait: 60000                        # 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
+      keepAlive: true                       # 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作。
+      validationQuery: select 1             # 检测连接是否有效sql,要求是查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
+      testWhileIdle: true                   # 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
+      testOnBorrow: false                   # 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
+      testOnReturn: false                   # 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
+      poolPreparedStatements: false         # 是否開啟PSCache,並且指定每個連線上PSCache的大小
+      timeBetweenEvictionRunsMillis: 60000  # 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒
+      minEvictableIdleTimeMillis: 300000    # 配置一個連線在池中最小生存的時間,單位是毫秒
+      filters: stat,slf4j             # 配置监控统计拦截的filters,监控统计用的filter:sta, 日志用的filter:log4j
+      useGlobalDataSourceStat: true         # 合并多个DruidDataSource的监控数据
+      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
+      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=1000
+      #stat-view-servlet.url-pattern: /admin/druid/*
 
 #[可选] WVP监听的HTTP端口, 网页和接口调用都是这个端口
 server:
-    port: 18080
+  port: 18080
 
 # 作为28181服务器的配置
 sip:
-    # [必须修改] 本机的IP
-    ip: 192.168.41.16
-    # [可选] 28181服务监听的端口
-    port: 5060
-    # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007)
-    # 后两位为行业编码,定义参照附录D.3
-    # 3701020049标识山东济南历下区 信息行业接入
-    # [可选]
-    domain: 4401020049
-    # [可选]
-    id: 44010200492000000001
-    # [可选] 默认设备认证密码,后续扩展使用设备单独密码, 移除密码将不进行校验
-    password: admin123
+  # [必须修改] 本机的IP
+  ip: 192.168.41.16
+  # [可选] 28181服务监听的端口
+  port: 5060
+  # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007)
+  # 后两位为行业编码,定义参照附录D.3
+  # 3701020049标识山东济南历下区 信息行业接入
+  # [可选]
+  domain: 4401020049
+  # [可选]
+  id: 44010200492000000001
+  # [可选] 默认设备认证密码,后续扩展使用设备单独密码, 移除密码将不进行校验
+  password: admin123
 
 #zlm 默认服务器配置
 media:
-    id: FQ3TF8yT83wh5Wvz
-    # [必须修改] zlm服务器的内网IP
-    ip: 192.168.41.16
-    # [必须修改] zlm服务器的http.port
-    http-port: 8091
-    # [可选] zlm服务器的hook.admin_params=secret
-    secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc
-    # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试
-    rtp:
-        # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输
-        enable: true
-        # [可选] 在此范围内选择端口用于媒体流传输, 必须提前在zlm上配置该属性,不然自动配置此属性可能不成功
-        port-range: 30000,30500 # 端口范围
-        # [可选] 国标级联在此范围内选择端口发送媒体流,
-        send-port-range: 30000,30500 # 端口范围
-    # 录像辅助服务, 部署此服务可以实现zlm录像的管理与下载, 0 表示不使用
-    record-assist-port: 18081
+  id: FQ3TF8yT83wh5Wvz
+  # [必须修改] zlm服务器的内网IP
+  ip: 192.168.41.16
+  # [必须修改] zlm服务器的http.port
+  http-port: 8091
+  # [可选] zlm服务器的hook.admin_params=secret
+  secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc
+  # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试
+  rtp:
+    # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输
+    enable: true
+    # [可选] 在此范围内选择端口用于媒体流传输, 必须提前在zlm上配置该属性,不然自动配置此属性可能不成功
+    port-range: 30000,30500 # 端口范围
+    # [可选] 国标级联在此范围内选择端口发送媒体流,
+    send-port-range: 30000,30500 # 端口范围
+  # 录像辅助服务, 部署此服务可以实现zlm录像的管理与下载, 0 表示不使用
+  record-assist-port: 18081
 # [可选] 日志配置, 一般不需要改
 logging:
-    config: classpath:logback-spring-local.xml
+  config: classpath:logback-spring-local.xml

+ 13 - 0
src/main/resources/application.yml

@@ -1,3 +1,16 @@
 spring:
+  application:
+    name: wvp
   profiles:
     active: local
+  # flayway相关配置
+  flyway:
+    enabled: true   #是否启用flyway(默认true)
+    locations: classpath:db/migration   #这个路径指的是fly版本控制的sql语句存放的路径,可以多个,可以给每个环境使用不同位置,比如classpath:db/migration,classpath:test/db/migration
+    baseline-on-migrate: true   #开启自动创建flyway元数据表标识 默认: false
+    # 与 baseline-on-migrate: true 搭配使用,将当前数据库初始版本设置为0
+    baseline-version: 0
+    clean-disabled: true    #禁止flyway执行清理
+    # 假如已经执行了版本1和版本3,如果增加了一个版本2,下面这个选项将会允许执行版本2的脚本
+    out-of-order: true
+    table: flyway_schema_history_${spring.application.name}  #用于记录所有的版本变化记录

+ 1 - 0
sql/mysql.sql

@@ -39,6 +39,7 @@ CREATE TABLE `device` (
                           `updateTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
                           `port` int DEFAULT NULL,
                           `expires` int DEFAULT NULL,
+                          `keepaliveIntervalTime` int DEFAULT NULL,
                           `subscribeCycleForCatalog` int DEFAULT NULL,
                           `hostAddress` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
                           `charset` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,

+ 2 - 1
web_src/build/utils.js

@@ -47,7 +47,8 @@ exports.cssLoaders = function (options) {
     if (options.extract) {
       return ExtractTextPlugin.extract({
         use: loaders,
-        fallback: 'vue-style-loader'
+        fallback: 'vue-style-loader',
+        publicPath: '../../'
       })
     } else {
       return ['vue-style-loader'].concat(loaders)

+ 3 - 3
web_src/config/index.js

@@ -8,8 +8,8 @@ module.exports = {
   dev: {
 
     // Paths
-    assetsSubDirectory: 'static',
-    assetsPublicPath: '/',
+    assetsSubDirectory: './static',
+    assetsPublicPath: './',
     proxyTable: {
       '/debug': {
         target: 'https://default.wvp-pro.cn:18080',
@@ -61,7 +61,7 @@ module.exports = {
     // Paths
     assetsRoot: path.resolve(__dirname, '../../src/main/resources/static/'),
     assetsSubDirectory: './static',
-    assetsPublicPath: '/',
+    assetsPublicPath: './',
 
     /**
      * Source Maps

+ 2 - 2
web_src/src/components/CloudRecord.vue

@@ -133,7 +133,7 @@
         let that = this;
         this.$axios({
           method: 'get',
-          url:`/record_proxy/${that.mediaServerId}/api/record/list`,
+          url:`./record_proxy/${that.mediaServerId}/api/record/list`,
           params: {
             page: that.currentPage,
             count: that.count
@@ -185,7 +185,7 @@
         let that = this;
         this.$axios({
           method: 'delete',
-          url:`/record_proxy/api/record/delete`,
+          url:`./record_proxy/api/record/delete`,
           params: {
             page: that.currentPage,
             count: that.count

+ 35 - 25
web_src/src/components/CloudRecordDetail.vue

@@ -1,14 +1,15 @@
 <template>
 	<div id="recordDetail">
 		<el-container>
-
-      <el-aside width="300px">
-
+      <el-aside width="260px">
         <div class="record-list-box-box">
-          <el-date-picker size="mini" v-model="chooseDate" :picker-options="pickerOptions" type="date" value-format="yyyy-MM-dd" placeholder="日期" @change="dateChange()"></el-date-picker>
+          <div style="margin-top: 20px">
+            <el-date-picker size="mini"  style="width: 160px" v-model="chooseDate" :picker-options="pickerOptions" type="date" value-format="yyyy-MM-dd" placeholder="日期" @change="dateChange()"></el-date-picker>
+            <el-button size="mini" type="primary" icon="fa fa-cloud-download" style="margin: auto; margin-left: 12px " title="裁剪合并" @click="drawerOpen"></el-button>
+          </div>
           <div class="record-list-box" :style="recordListStyle">
             <ul v-if="detailFiles.length >0" class="infinite-list record-list" v-infinite-scroll="infiniteScroll" >
-              <li v-for="item in detailFiles" class="infinite-list-item record-list-item" >
+              <li v-for="(item,index) in detailFiles" :key="index" class="infinite-list-item record-list-item" >
                 <el-tag v-if="choosedFile != item" @click="chooseFile(item)">
                   <i class="el-icon-video-camera"  ></i>
                   {{ item.substring(0,17)}}
@@ -24,9 +25,7 @@
           <div v-if="detailFiles.length ==0" class="record-list-no-val" >暂无数据</div>
         </div>
 
-        <div class="record-list-option">
-          <el-button size="mini" type="primary" icon="fa fa-cloud-download" style="margin: auto; " title="裁剪合并" @click="drawerOpen"></el-button>
-        </div>
+
       </el-aside>
 			<el-main style="padding: 22px">
         <div class="playBox" :style="playerStyle">
@@ -45,7 +44,7 @@
             :marks="playTimeSliderMarks">
           </el-slider>
           <div class="slider-val-box">
-            <div class="slider-val" v-for="item of detailFiles" :style="'width:'  +  getDataWidth(item) + '%; left:' + getDataLeft(item) + '%'"></div>
+            <div class="slider-val" v-for="(item,index) of detailFiles" :key="index" :style="'width:'  +  getDataWidth(item) + '%; left:' + getDataLeft(item) + '%'"></div>
           </div>
         </div>
 
@@ -62,7 +61,7 @@
           <el-tab-pane name="running">
             <span slot="label"><i class="el-icon-scissors"></i>进行中</span>
             <ul class="task-list">
-              <li class="task-list-item" v-for="item in taskListForRuning">
+              <li class="task-list-item" v-for="(item,index) in taskListForRuning" :key="index">
                 <div class="task-list-item-box">
                   <span>{{ item.startTime.substr(10) }}-{{item.endTime.substr(10)}}</span>
                   <el-progress :percentage="(parseFloat(item.percentage)*100).toFixed(1)"></el-progress>
@@ -74,10 +73,10 @@
           <el-tab-pane name="ended">
             <span slot="label"><i class="el-icon-finished"></i>已完成</span>
             <ul class="task-list">
-              <li class="task-list-item" v-for="item in taskListEnded">
+              <li class="task-list-item" v-for="(item, index) in taskListEnded" :key="index">
                 <div class="task-list-item-box" style="height: 2rem;line-height: 2rem;">
                   <span>{{ item.startTime.substr(10) }}-{{item.endTime.substr(10)}}</span>
-                  <a class="el-icon-download download-btn" :href="basePath  + '/download.html?url=../' + item.recordFile" target="_blank">
+                  <a class="el-icon-download download-btn" :href="mediaServerPath  + '/download.html?url=../' + item.recordFile" target="_blank">
                   </a>
                 </div>
               </li>
@@ -116,7 +115,7 @@
     props: ['recordFile', 'mediaServerId', 'dateFiles', 'mediaServerPath'],
 		data() {
 			return {
-        basePath: `${this.mediaServerPath}`,
+        basePath: `${this.mediaServerPath}/record`,
 			  dateFilesObj: [],
 			  detailFiles: [],
         chooseDate: null,
@@ -147,6 +146,7 @@
 			    "margin-bottom": "20px",
           "height": this.winHeight + "px",
         },
+        timeFormat:'00:00:00',
         winHeight: window.innerHeight - 240,
         playTime: 0,
         playTimeSliderMarks: {
@@ -213,7 +213,7 @@
         this.currentPage = 1;
         this.sliderMIn= 0;
         this.sliderMax= 86400;
-        let chooseFullDate = new Date(this.chooseDate + " " + "00:00:00");
+        let chooseFullDate = new Date(this.chooseDate +" " + this.timeFormat);
         if (chooseFullDate.getFullYear() !== this.queryDate.getFullYear()
           || chooseFullDate.getMonth() !== this.queryDate.getMonth()){
           // this.getDateInYear()
@@ -222,8 +222,8 @@
           if (this.detailFiles.length > 0){
             let timeForFile = this.getTimeForFile(this.detailFiles[0]);
             let lastTimeForFile = this.getTimeForFile(this.detailFiles[this.detailFiles.length - 1]);
-            let timeNum = timeForFile[0].getTime() - new Date(this.chooseDate + " " + "00:00:00").getTime()
-            let lastTimeNum = lastTimeForFile[1].getTime() - new Date(this.chooseDate + " " + "00:00:00").getTime()
+            let timeNum = timeForFile[0].getTime() - new Date(this.chooseDate + " " + this.timeFormat).getTime()
+            let lastTimeNum = lastTimeForFile[1].getTime() - new Date(this.chooseDate + " " + this.timeFormat).getTime()
 
             this.playTime = parseInt(timeNum/1000)
             this.sliderMIn = parseInt(timeNum/1000 - timeNum/1000%(60*60))
@@ -241,7 +241,7 @@
         let that = this;
         that.$axios({
           method: 'get',
-          url:`/record_proxy/${that.mediaServerId}/api/record/file/list`,
+          url:`./record_proxy/${that.mediaServerId}/api/record/file/list`,
           params: {
             app: that.recordFile.app,
             stream: that.recordFile.stream,
@@ -281,14 +281,14 @@
       },
       getDataLeft(item){
         let timeForFile = this.getTimeForFile(item);
-        let differenceTime = timeForFile[0].getTime() - new Date(this.chooseDate + " 00:00:00").getTime()
+        let differenceTime = timeForFile[0].getTime() - new Date(this.chooseDate + " " + this.timeFormat).getTime()
         return parseFloat((differenceTime - this.sliderMIn * 1000)/((this.sliderMax - this.sliderMIn)*1000))*100   ;
       },
       playTimeChange(val){
         let minTime = this.getTimeForFile(this.detailFiles[0])[0]
         let maxTime = this.getTimeForFile(this.detailFiles[this.detailFiles.length - 1])[1];
         this.chooseFile(null);
-        let timeMilli = new Date(this.chooseDate + " 00:00:00").getTime() + val*1000
+        let timeMilli = new Date(this.chooseDate + " " + this.timeFormat).getTime() + val*1000
         if (timeMilli >= minTime.getTime() && timeMilli <= maxTime.getTime()){
           for (let i = 0; i < this.detailFiles.length; i++) {
             let timeForFile = this.getTimeForFile(this.detailFiles[i]);
@@ -302,10 +302,20 @@
       },
       getTimeForFile(file){
         let timeStr = file.substring(0,17);
-        let starTime = new Date(this.chooseDate + " " + timeStr.split("-")[0]);
-        let endTime = new Date(this.chooseDate + " " + timeStr.split("-")[1]);
+        if(timeStr.indexOf("~") > 0){
+          timeStr = timeStr.replaceAll("-",":")
+        }
+        let timeArr = timeStr.split("~");
+        let starTime = new Date(this.chooseDate + " " + timeArr[0]);
+        let endTime = new Date(this.chooseDate + " " + timeArr[1]);
+        if(this.checkIsOver24h(starTime,endTime)){
+           endTime = new Date(this.chooseDate + " " + "23:59:59");
+        }
         return [starTime, endTime, endTime.getTime() - starTime.getTime()];
       },
+      checkIsOver24h(starTime,endTime){
+        return starTime > endTime;
+      },
       playTimeFormat(val){
         let h = parseInt(val/3600);
         let m = parseInt((val - h*3600)/60);
@@ -330,7 +340,7 @@
         let that = this;
         this.$axios({
           method: 'delete',
-          url:`/record_proxy/${that.mediaServerId}/api/record/delete`,
+          url:`./record_proxy/${that.mediaServerId}/api/record/delete`,
           params: {
             page: that.currentPage,
             count: that.count
@@ -349,7 +359,7 @@
         that.dateFilesObj = {};
         this.$axios({
           method: 'get',
-          url:`/record_proxy/${that.mediaServerId}/api/record/date/list`,
+          url:`./record_proxy/${that.mediaServerId}/api/record/date/list`,
           params: {
             app: that.recordFile.app,
             stream: that.recordFile.stream
@@ -398,7 +408,7 @@
         let that = this;
         this.$axios({
           method: 'get',
-          url:`/record_proxy/${that.mediaServerId}/api/record/file/download/task/add`,
+          url:`./record_proxy/${that.mediaServerId}/api/record/file/download/task/add`,
           params: {
             app: that.recordFile.app,
             stream: that.recordFile.stream,
@@ -423,7 +433,7 @@
         let that = this;
         this.$axios({
           method: 'get',
-          url:`/record_proxy/${that.mediaServerId}/api/record/file/download/task/list`,
+          url:`./record_proxy/${that.mediaServerId}/api/record/file/download/task/list`,
           params: {
             isEnd: isEnd,
           }

+ 5 - 5
web_src/src/components/DeviceList.vue

@@ -152,7 +152,7 @@ export default {
       this.getDeviceListLoading = true;
       this.$axios({
         method: 'get',
-        url: `/api/device/query/devices`,
+        url: `./api/device/query/devices`,
         params: {
           page: this.currentPage,
           count: this.count
@@ -182,7 +182,7 @@ export default {
       }).then(() => {
         this.$axios({
           method: 'delete',
-          url: `/api/device/query/devices/${row.deviceId}/delete`
+          url: `./api/device/query/devices/${row.deviceId}/delete`
         }).then((res) => {
           this.getDeviceList();
         }).catch((error) => {
@@ -208,7 +208,7 @@ export default {
       let that = this;
       this.$axios({
         method: 'get',
-        url: '/api/device/query/devices/' + itemData.deviceId + '/sync'
+        url: './api/device/query/devices/' + itemData.deviceId + '/sync'
       }).then((res) => {
         console.log("刷新设备结果:" + JSON.stringify(res));
         if (res.data.code !== 0) {
@@ -242,7 +242,7 @@ export default {
       await this.$axios({
         method: 'get',
         async: false,
-        url: `/api/device/query/${deviceId}/sync_status/`,
+        url: `./api/device/query/${deviceId}/sync_status/`,
       }).then((res) => {
         if (res.data.code == 0) {
           if (res.data.data.errorMsg !== null) {
@@ -261,7 +261,7 @@ export default {
       let that = this;
       this.$axios({
         method: 'post',
-        url: '/api/device/query/transport/' + row.deviceId + '/' + row.streamMode
+        url: './api/device/query/transport/' + row.deviceId + '/' + row.streamMode
       }).then(function (res) {
 
       }).catch(function (e) {

+ 8 - 8
web_src/src/components/GBRecordDetail.vue

@@ -197,7 +197,7 @@
         this.detailFiles = [];
         this.$axios({
           method: 'get',
-          url: '/api/gb_record/query/' + this.deviceId + '/' + this.channelId + '?startTime=' + this.startTime + '&endTime=' + this.endTime
+          url: './api/gb_record/query/' + this.deviceId + '/' + this.channelId + '?startTime=' + this.startTime + '&endTime=' + this.endTime
         }).then((res)=>{
           this.recordsLoading = false;
           if(res.data.code === 0) {
@@ -249,7 +249,7 @@
         } else {
           this.$axios({
             method: 'get',
-            url: '/api/playback/start/' + this.deviceId + '/' + this.channelId + '?startTime=' + this.startTime + '&endTime=' +
+            url: './api/playback/start/' + this.deviceId + '/' + this.channelId + '?startTime=' + this.startTime + '&endTime=' +
               this.endTime
           }).then((res)=> {
             if (res.data.code === 0) {
@@ -273,7 +273,7 @@
         console.log('前端控制:播放');
         this.$axios({
           method: 'get',
-          url: '/api/playback/resume/' + this.streamId
+          url: './api/playback/resume/' + this.streamId
         }).then((res)=> {
           this.$refs["recordVideoPlayer"].play(this.videoUrl)
         });
@@ -282,14 +282,14 @@
         console.log('前端控制:暂停');
         this.$axios({
           method: 'get',
-          url: '/api/playback/pause/' + this.streamId
+          url: './api/playback/pause/' + this.streamId
         }).then(function (res) {});
       },
       gbScale(command){
         console.log('前端控制:倍速 ' + command);
         this.$axios({
           method: 'get',
-          url: `/api/playback/speed/${this.streamId }/${command}`
+          url: `./api/playback/speed/${this.streamId }/${command}`
         }).then(function (res) {});
       },
       downloadRecord: function (row) {
@@ -311,7 +311,7 @@
         }else {
           this.$axios({
             method: 'get',
-            url: '/api/gb_record/download/start/' + this.deviceId + '/' + this.channelId + '?startTime=' + row.startTime + '&endTime=' +
+            url: './api/gb_record/download/start/' + this.deviceId + '/' + this.channelId + '?startTime=' + row.startTime + '&endTime=' +
               row.endTime + '&downloadSpeed=4'
           }).then( (res)=> {
             if (res.data.code === 0) {
@@ -332,7 +332,7 @@
         this.videoUrl = '';
         this.$axios({
           method: 'get',
-          url: '/api/gb_record/download/stop/' + this.deviceId + "/" + this.channelId+ "/" + this.streamId
+          url: './api/gb_record/download/stop/' + this.deviceId + "/" + this.channelId+ "/" + this.streamId
         }).then((res)=> {
           if (callback) callback(res)
         });
@@ -342,7 +342,7 @@
         this.videoUrl = '';
         this.$axios({
           method: 'get',
-          url: '/api/playback/stop/' + this.deviceId + "/" + this.channelId + "/" + this.streamId
+          url: './api/playback/stop/' + this.deviceId + "/" + this.channelId + "/" + this.streamId
         }).then(function (res) {
           if (callback) callback()
         });

+ 1 - 1
web_src/src/components/Login.vue

@@ -81,7 +81,7 @@ export default {
 
       this.$axios({
       	method: 'get',
-        url:"/api/user/login",
+        url:"./api/user/login",
         params: loginParam
       }).then(function (res) {
         window.clearTimeout(timeoutTask)

+ 2 - 2
web_src/src/components/ParentPlatformList.vue

@@ -128,7 +128,7 @@ export default {
         var that = this;
         that.$axios({
           method: 'delete',
-          url:`/api/platform/delete/${platform.serverGBId}`
+          url:`./api/platform/delete/${platform.serverGBId}`
         }).then(function (res) {
             if (res.data.code === 0) {
                 that.$message({
@@ -162,7 +162,7 @@ export default {
 
       this.$axios({
       	method: 'get',
-        url:`/api/platform/query/${that.count}/${that.currentPage}`
+        url:`./api/platform/query/${that.count}/${that.currentPage}`
       }).then(function (res) {
         if (res.data.code === 0) {
           that.total = res.data.data.total;

+ 5 - 5
web_src/src/components/PushVideoList.vue

@@ -171,7 +171,7 @@ export default {
       this.getDeviceListLoading = true;
       this.$axios({
         method: 'get',
-        url: `/api/push/list`,
+        url: `./api/push/list`,
         params: {
           page: that.currentPage,
           count: that.count,
@@ -197,7 +197,7 @@ export default {
       this.getListLoading = true;
       this.$axios({
         method: 'get',
-        url: '/api/push/getPlayUrl',
+        url: './api/push/getPlayUrl',
         params: {
           app: row.app,
           stream: row.stream,
@@ -223,7 +223,7 @@ export default {
       let that = this;
       that.$axios({
         method: "post",
-        url: "/api/push/stop",
+        url: "./api/push/stop",
         params: {
           app: row.app,
           streamId: row.stream
@@ -247,7 +247,7 @@ export default {
       let that = this;
       that.$axios({
         method: "delete",
-        url: "/api/push/remove_form_gb",
+        url: "./api/push/remove_form_gb",
         data: row
       }).then((res) => {
         if (res.data.code === 0) {
@@ -274,7 +274,7 @@ export default {
         let that = this;
         that.$axios({
           method: "delete",
-          url: "/api/push/batchStop",
+          url: "./api/push/batchStop",
           data: {
             gbStreams: this.multipleSelection
           }

+ 6 - 6
web_src/src/components/StreamProxyList.vue

@@ -167,7 +167,7 @@
 				let that = this;
 				this.$axios({
 					method: 'get',
-					url:`/api/proxy/list`,
+					url:`./api/proxy/list`,
 					params: {
 						page: that.currentPage,
 						count: that.count
@@ -190,7 +190,7 @@
       addOnvif: function(){
         this.$axios({
           method: 'get',
-          url:`/api/onvif/search?timeout=3000`,
+          url:`./api/onvif/search?timeout=3000`,
         }).then((res) =>{
           if (res.data.code === 0 ){
             if (res.data.data.length > 0) {
@@ -218,7 +218,7 @@
 				let that = this;
 				this.$axios({
 					method: 'get',
-					url:`/api/push/getPlayUrl`,
+					url:`./api/push/getPlayUrl`,
 					params: {
 						app: row.app,
 						stream: row.stream,
@@ -247,7 +247,7 @@
 				let that = this;
 				that.$axios({
                     method:"delete",
-                    url:"/api/proxy/del",
+                    url:"./api/proxy/del",
                     params:{
                       app: row.app,
                       stream: row.stream
@@ -263,7 +263,7 @@
         this.$set(row, 'startBtnLoading', true)
 				this.$axios({
 					method: 'get',
-					url:`/api/proxy/start`,
+					url:`./api/proxy/start`,
 					params: {
 						app: row.app,
 						stream: row.stream
@@ -295,7 +295,7 @@
 				let that = this;
 				this.$axios({
 					method: 'get',
-					url:`/api/proxy/stop`,
+					url:`./api/proxy/stop`,
 					params: {
 						app: row.app,
 						stream: row.stream

+ 2 - 2
web_src/src/components/UserManager.vue

@@ -99,7 +99,7 @@ export default {
       this.getUserListLoading = true;
       this.$axios({
         method: 'get',
-        url: `/api/user/users`,
+        url: `./api/user/users`,
         params: {
           page: that.currentPage,
           count: that.count
@@ -141,7 +141,7 @@ export default {
       }).then(() => {
         this.$axios({
           method: 'delete',
-          url: `/api/user/delete?id=${row.id}`
+          url: `./api/user/delete?id=${row.id}`
         }).then((res) => {
           this.getUserList();
         }).catch((error) => {

+ 6 - 6
web_src/src/components/channelList.vue

@@ -206,7 +206,7 @@ export default {
       if (typeof (this.$route.params.deviceId) == "undefined") return;
       this.$axios({
         method: 'get',
-        url: `/api/device/query/devices/${this.$route.params.deviceId}/channels`,
+        url: `./api/device/query/devices/${this.$route.params.deviceId}/channels`,
         params: {
           page: that.currentPage,
           count: that.count,
@@ -238,7 +238,7 @@ export default {
       let that = this;
       this.$axios({
         method: 'get',
-        url: '/api/play/start/' + deviceId + '/' + channelId
+        url: './api/play/start/' + deviceId + '/' + channelId
       }).then(function (res) {
         console.log(res)
         that.isLoging = false;
@@ -278,7 +278,7 @@ export default {
       var that = this;
       this.$axios({
         method: 'get',
-        url: '/api/play/stop/' + this.deviceId + "/" + itemData.channelId
+        url: './api/play/stop/' + this.deviceId + "/" + itemData.channelId
       }).then(function (res) {
         that.initData();
       }).catch(function (error) {
@@ -334,7 +334,7 @@ export default {
       if (!this.showTree) {
         this.$axios({
           method: 'get',
-          url: `/api/device/query/sub_channels/${this.deviceId}/${this.parentChannelId}/channels`,
+          url: `./api/device/query/sub_channels/${this.deviceId}/${this.parentChannelId}/channels`,
           params: {
             page: this.currentPage,
             count: this.count,
@@ -358,7 +358,7 @@ export default {
       }else {
         this.$axios({
           method: 'get',
-          url: `/api/device/query/tree/channel/${this.deviceId}`,
+          url: `./api/device/query/tree/channel/${this.deviceId}`,
           params: {
             parentId: this.parentChannelId,
             page: this.currentPage,
@@ -387,7 +387,7 @@ export default {
     updateChannel: function (row) {
       this.$axios({
         method: 'post',
-        url: `/api/device/query/channel/update/${this.deviceId}`,
+        url: `./api/device/query/channel/update/${this.deviceId}`,
         params: row
       }).then(function (res) {
         console.log(JSON.stringify(res));

+ 4 - 4
web_src/src/components/console.vue

@@ -114,7 +114,7 @@ export default {
     getSystemInfo: function (){
       this.$axios({
         method: 'get',
-        url: `/api/server/system/info`,
+        url: `./api/server/system/info`,
       }).then( (res)=> {
         if (res.data.code === 0) {
           this.$refs.consoleCPU.setData(res.data.data.cpu)
@@ -128,7 +128,7 @@ export default {
     getLoad: function (){
       this.$axios({
         method: 'get',
-        url: `/api/server/media_server/load`,
+        url: `./api/server/media_server/load`,
       }).then( (res)=> {
         if (res.data.code === 0) {
           this.$refs.consoleNodeLoad.setData(res.data.data)
@@ -139,7 +139,7 @@ export default {
     getResourceInfo: function (){
       this.$axios({
         method: 'get',
-        url: `/api/server/resource/info`,
+        url: `./api/server/resource/info`,
       }).then( (res)=> {
         if (res.data.code === 0) {
           this.$refs.consoleResource.setData(res.data.data)
@@ -151,7 +151,7 @@ export default {
 
       this.$axios({
         method: 'get',
-        url: `/api/server/system/configInfo`,
+        url: `./api/server/system/configInfo`,
       }).then( (res)=> {
         console.log(res)
         if (res.data.code === 0) {

+ 1 - 1
web_src/src/components/dialog/MediaServerEdit.vue

@@ -335,7 +335,7 @@ export default {
       var that = this;
       await that.$axios({
         method: 'get',
-        url:`/api/platform/exit/${deviceGbId}`
+        url:`./api/platform/exit/${deviceGbId}`
       }).then(function (res) {
         result = res.data;
       }).catch(function (error) {

+ 4 - 4
web_src/src/components/dialog/StreamProxyEdit.vue

@@ -195,7 +195,7 @@ export default {
       let that = this;
       this.$axios({
         method: 'get',
-        url:`/api/platform/query/10000/1`
+        url:`./api/platform/query/10000/1`
       }).then(function (res) {
         that.platformList = res.data.data.list;
       }).catch(function (error) {
@@ -212,7 +212,7 @@ export default {
       if (that.proxyParam.mediaServerId !== "auto"){
         that.$axios({
           method: 'get',
-          url:`/api/proxy/ffmpeg_cmd/list`,
+          url:`./api/proxy/ffmpeg_cmd/list`,
           params: {
             mediaServerId: that.proxyParam.mediaServerId
           }
@@ -230,7 +230,7 @@ export default {
       this.noneReaderHandler();
       this.$axios({
         method: 'post',
-        url:`/api/proxy/save`,
+        url:`./api/proxy/save`,
         data: this.proxyParam
       }).then((res)=> {
         this.dialogLoading = false;
@@ -261,7 +261,7 @@ export default {
       var that = this;
       await that.$axios({
         method: 'get',
-        url:`/api/platform/exit/${deviceGbId}`
+        url:`./api/platform/exit/${deviceGbId}`
       }).then(function (res) {
         result = res.data;
       }).catch(function (error) {

+ 1 - 1
web_src/src/components/dialog/SyncChannelProgress.vue

@@ -55,7 +55,7 @@ export default {
     getProgress(){
       this.$axios({
         method: 'get',
-        url:`/api/device/query/${this.deviceId}/sync_status/`,
+        url:`./api/device/query/${this.deviceId}/sync_status/`,
       }).then((res) => {
         if (res.data.code === 0) {
           if (!this.syncFlag) {

+ 2 - 2
web_src/src/components/dialog/addUser.vue

@@ -100,7 +100,7 @@ export default {
     onSubmit: function () {
       this.$axios({
         method: 'post',
-        url: "/api/user/add",
+        url: "./api/user/add",
         params: {
           username: this.username,
           password: this.password,
@@ -139,7 +139,7 @@ export default {
 
       this.$axios({
         method: 'get',
-        url: "/api/role/all"
+        url: "./api/role/all"
       }).then((res) => {
         this.loading = true;
         if (res.data.code === 0) {

+ 1 - 1
web_src/src/components/dialog/catalogEdit.vue

@@ -116,7 +116,7 @@ export default {
       console.log(this.form);
       this.$axios({
         method:"post",
-        url:`/api/platform/catalog/${!this.isEdit? "add":"edit"}`,
+        url:`./api/platform/catalog/${!this.isEdit? "add":"edit"}`,
         data: this.form
       }).then((res)=> {
           if (res.data.code === 0) {

+ 1 - 1
web_src/src/components/dialog/changePassword.vue

@@ -90,7 +90,7 @@ export default {
     onSubmit: function () {
       this.$axios({
         method: 'post',
-        url:"/api/user/changePassword",
+        url:"./api/user/changePassword",
         params: {
           oldPassword: crypto.createHash('md5').update(this.oldPassword, "utf8").digest('hex'),
           password: this.newPassword

+ 1 - 1
web_src/src/components/dialog/changePasswordForAdmin.vue

@@ -85,7 +85,7 @@ export default {
     onSubmit: function () {
       this.$axios({
         method: 'post',
-        url:"/api/user/changePasswordForAdmin",
+        url:"./api/user/changePasswordForAdmin",
         params: {
           password: this.newPassword,
           userId: this.form.id,

+ 1 - 1
web_src/src/components/dialog/changePushKey.vue

@@ -65,7 +65,7 @@ export default {
     onSubmit: function () {
       this.$axios({
         method: 'post',
-        url:"/api/user/changePushKey",
+        url:"./api/user/changePushKey",
         params: {
           pushKey: this.newPushKey,
           userId: this.form.id,

+ 1 - 1
web_src/src/components/dialog/channelMapInfobox.vue

@@ -44,7 +44,7 @@ export default {
       let that = this;
       this.$axios({
         method: 'get',
-        url: '/api/play/start/' + deviceId + '/' + channelId
+        url: './api/play/start/' + deviceId + '/' + channelId
       }).then(function (res) {
         that.isLoging = false;
         if (res.data.code === 0) {

+ 1 - 1
web_src/src/components/dialog/chooseChannel.vue

@@ -98,7 +98,7 @@ export default {
 
             this.$axios({
                 method:"post",
-                url:"/api/platform/update_channel_for_gb",
+                url:"./api/platform/update_channel_for_gb",
                 data:{
                     platformId:  that.platformId,
                     channelReduces:  that.chooseData

+ 4 - 4
web_src/src/components/dialog/chooseChannelForCatalog.vue

@@ -82,7 +82,7 @@ export default {
             let that = this;
             this.$axios({
                     method:"get",
-                    url:`/api/platform/catalog`,
+                    url:`./api/platform/catalog`,
                     params: {
                         platformId: that.platformId,
                         parentId: parentId
@@ -134,7 +134,7 @@ export default {
         removeCatalog: function (id, node){
           this.$axios({
             method:"delete",
-            url:`/api/platform/catalog/del`,
+            url:`./api/platform/catalog/del`,
             params: {
               id: id,
               platformId: this.platformId,
@@ -156,7 +156,7 @@ export default {
         setDefaultCatalog: function (id){
           this.$axios({
             method:"post",
-            url:`/api/platform/catalog/default/update`,
+            url:`./api/platform/catalog/default/update`,
             params: {
               platformId: this.platformId,
               catalogId: id,
@@ -201,7 +201,7 @@ export default {
                   onClick: () => {
                     this.$axios({
                       method:"delete",
-                      url:"/api/platform/catalog/relation/del",
+                      url:"./api/platform/catalog/relation/del",
                       data: data
                     }).then((res)=>{
                       console.log("移除成功")

+ 5 - 5
web_src/src/components/dialog/chooseChannelForGb.vue

@@ -121,7 +121,7 @@ export default {
           this.getCatalogFromUser((catalogId)=> {
             this.$axios({
               method:"post",
-              url:"/api/platform/update_channel_for_gb",
+              url:"./api/platform/update_channel_for_gb",
               data:{
                 platformId:  this.platformId,
                 all: all,
@@ -149,7 +149,7 @@ export default {
 
             this.$axios({
               method:"delete",
-              url:"/api/platform/del_channel_for_gb",
+              url:"./api/platform/del_channel_for_gb",
               data:{
                 platformId:  this.platformId,
                 all: all,
@@ -248,7 +248,7 @@ export default {
 
             this.$axios({
                     method:"get",
-                    url:`/api/platform/channel_list`,
+                    url:`./api/platform/channel_list`,
                     params: {
                         page: that.currentPage,
                         count: that.count,
@@ -290,7 +290,7 @@ export default {
         }).then(() => {
           this.$axios({
             method:"delete",
-            url:"/api/platform/del_channel_for_gb",
+            url:"./api/platform/del_channel_for_gb",
             data:{
               platformId:  this.platformId,
               channelReduces: this.multipleSelection
@@ -310,7 +310,7 @@ export default {
 
           this.$axios({
             method: "post",
-            url: "/api/platform/update_channel_for_gb",
+            url: "./api/platform/update_channel_for_gb",
             data: {
               platformId: this.platformId,
               channelReduces: this.multipleSelection,

+ 5 - 5
web_src/src/components/dialog/chooseChannelForStream.vue

@@ -134,7 +134,7 @@ export default {
           this.getCatalogFromUser((catalogId)=>{
             this.$axios({
               method:"post",
-              url:"/api/gbStream/add",
+              url:"./api/gbStream/add",
               data:{
                 platformId: this.platformId,
                 catalogId: catalogId,
@@ -163,7 +163,7 @@ export default {
 
             this.$axios({
               method:"delete",
-              url:"/api/gbStream/del",
+              url:"./api/gbStream/del",
               data:{
                 platformId: this.platformId,
                 all: all,
@@ -186,7 +186,7 @@ export default {
 
             this.$axios({
                 method: 'get',
-                url:`/api/gbStream/list`,
+                url:`./api/gbStream/list`,
                 params: {
                     page: that.currentPage,
                     count: that.count,
@@ -222,7 +222,7 @@ export default {
           }).then(() => {
             this.$axios({
               method:"delete",
-              url:"/api/gbStream/del",
+              url:"./api/gbStream/del",
               data:{
                 platformId: this.platformId,
                 gbStreams:  this.multipleSelection,
@@ -242,7 +242,7 @@ export default {
           this.getCatalogFromUser((catalogId)=>{
             this.$axios({
               method:"post",
-              url:"/api/gbStream/add",
+              url:"./api/gbStream/add",
               data:{
                 platformId: this.platformId,
                 catalogId: catalogId,

+ 1 - 1
web_src/src/components/dialog/deviceEdit.vue

@@ -134,7 +134,7 @@ export default {
       this.form.mobilePositionSubmissionInterval = this.form.mobilePositionSubmissionInterval||0
       this.$axios({
         method: 'post',
-        url:`/api/device/query/device/${this.isEdit?'update':'add'}/`,
+        url:`./api/device/query/device/${this.isEdit?'update':'add'}/`,
         params: this.form
       }).then((res) => {
         console.log(res.data)

+ 2 - 2
web_src/src/components/dialog/getCatalog.vue

@@ -89,7 +89,7 @@ export default {
             let that = this;
             this.$axios({
                     method:"get",
-                    url:`/api/platform/catalog`,
+                    url:`./api/platform/catalog`,
                     params: {
                         platformId: that.platformId,
                         parentId: parentId
@@ -111,7 +111,7 @@ export default {
           if (node.level === 0) {
             this.$axios({
               method:"get",
-              url:`/api/platform/info/` + this.platformId,
+              url:`./api/platform/info/` + this.platformId,
             })
               .then((res)=> {
                 if (res.data.code === 0) {

+ 1 - 1
web_src/src/components/dialog/importChannel.vue

@@ -60,7 +60,7 @@ export default {
       console.log(this.form);
       this.$axios({
         method:"post",
-        url:`/api/platform/catalog/${!this.isEdit? "add":"edit"}`,
+        url:`./api/platform/catalog/${!this.isEdit? "add":"edit"}`,
         data: this.form
       })
         .then((res)=> {

+ 1 - 1
web_src/src/components/dialog/onvifEdit.vue

@@ -81,7 +81,7 @@ export default {
       console.log(this.form);
       this.$axios({
         method: 'get',
-        url:`api/onvif/rtsp`,
+        url:`./api/onvif/rtsp`,
         params: {
           hostname: this.form.hostName,
           timeout: 3000,

+ 3 - 3
web_src/src/components/dialog/platformEdit.vue

@@ -138,7 +138,7 @@ export default {
       showDialog: false,
       isLoging: false,
       onSubmit_text: "立即创建",
-      saveUrl: "/api/platform/save",
+      saveUrl: "./api/platform/save",
 
       platform: {
         id: null,
@@ -192,7 +192,7 @@ export default {
         this.saveUrl = "/api/platform/add";
         this.$axios({
           method: 'get',
-          url:`/api/platform/server_config`
+          url:`./api/platform/server_config`
         }).then(function (res) {
           console.log(res);
           if (res.data.code === 0) {
@@ -315,7 +315,7 @@ export default {
       var that = this;
       await that.$axios({
                 method: 'get',
-                url:`/api/platform/exit/${deviceGbId}`})
+                url:`./api/platform/exit/${deviceGbId}`})
         .then(function (res) {
             if (res.data.code === 0) {
               result = res.data.data;

+ 3 - 3
web_src/src/components/dialog/pushStreamEdit.vue

@@ -109,7 +109,7 @@ export default {
       if (this.edit) {
         this.$axios({
           method:"post",
-          url:`/api/push/save_to_gb`,
+          url:`./api/push/save_to_gb`,
           data: this.proxyParam
         }).then( (res) => {
           if (res.data.code === 0) {
@@ -129,7 +129,7 @@ export default {
       }else {
         this.$axios({
           method:"post",
-          url:`/api/push/add`,
+          url:`./api/push/add`,
           data: this.proxyParam
         }).then( (res) => {
           if (res.data.code === 0) {
@@ -159,7 +159,7 @@ export default {
       var that = this;
       await that.$axios({
         method:"get",
-        url:`/api/platform/exit/${deviceGbId}`
+        url:`./api/platform/exit/${deviceGbId}`
       }).then(function (res) {
         result = res.data;
       }).catch(function (error) {

+ 1 - 1
web_src/src/components/dialog/queryTrace.vue

@@ -72,7 +72,7 @@ export default {
     onSubmit: function () {
       console.log("onSubmit");
       this.isLoging = true;
-      let url = `/api/position/history/${this.channel.deviceId}?start=${this.searchFrom}&end=${this.searchTo}`;
+      let url = `./api/position/history/${this.channel.deviceId}?start=${this.searchFrom}&end=${this.searchTo}`;
       if (this.channel.channelId) {
         url+="&channelId=${this.channel.channelId}"
       }

+ 4 - 4
web_src/src/components/dialog/recordDownload.vue

@@ -71,7 +71,7 @@ export default {
         getProgress: function (callback){
           this.$axios({
             method: 'get',
-            url: `/api/gb_record/download/progress/${this.deviceId}/${this.channelId}/${this.stream}`
+            url: `./api/gb_record/download/progress/${this.deviceId}/${this.channelId}/${this.stream}`
           }).then((res)=> {
             console.log(res)
               if (res.data.code === 0) {
@@ -124,7 +124,7 @@ export default {
         stopDownloadRecord: function (callback) {
           this.$axios({
             method: 'get',
-            url: '/api/gb_record/download/stop/' + this.deviceId + "/" + this.channelId+ "/" + this.stream
+            url: './api/gb_record/download/stop/' + this.deviceId + "/" + this.channelId+ "/" + this.stream
           }).then((res)=> {
             if (callback) callback(res)
           });
@@ -132,7 +132,7 @@ export default {
         getFileDownload: function (){
           this.$axios({
             method: 'get',
-            url:`/record_proxy/${this.mediaServerId}/api/record/file/download/task/add`,
+            url:`./record_proxy/${this.mediaServerId}/api/record/file/download/task/add`,
             params: {
               app: this.app,
               stream: this.stream,
@@ -164,7 +164,7 @@ export default {
         getProgressForFile: function (callback){
           this.$axios({
             method: 'get',
-            url:`/record_proxy/${this.mediaServerId}/api/record/file/download/task/list`,
+            url:`./record_proxy/${this.mediaServerId}/api/record/file/download/task/list`,
             params: {
               app: this.app,
               stream: this.stream,

+ 1 - 1
web_src/src/components/live.vue

@@ -135,7 +135,7 @@ export default {
       this.loading = true
       this.$axios({
         method: 'get',
-        url: '/api/play/start/' + deviceId + '/' + channelId
+        url: './api/play/start/' + deviceId + '/' + channelId
       }).then(function (res) {
         if (res.data.code === 0 && res.data.data) {
           let videoUrl;

+ 1 - 1
web_src/src/components/map.vue

@@ -298,7 +298,7 @@ export default {
       let that = this;
       this.$axios({
         method: 'get',
-        url: '/api/play/start/' + deviceId + '/' + channelId
+        url: './api/play/start/' + deviceId + '/' + channelId
       }).then(function (res) {
         that.isLoging = false;
         if (res.data.code === 0) {

+ 5 - 5
web_src/src/components/service/DeviceService.js

@@ -9,7 +9,7 @@ class DeviceService{
   getDeviceList(currentPage, count, callback, errorCallback){
     this.$axios({
       method: 'get',
-      url:`/api/device/query/devices`,
+      url:`./api/device/query/devices`,
       params: {
         page: currentPage,
         count: count
@@ -25,7 +25,7 @@ class DeviceService{
   getDevice(deviceId, callback, errorCallback){
     this.$axios({
       method: 'get',
-      url:`/api/device/query/devices/${deviceId}`,
+      url:`./api/device/query/devices/${deviceId}`,
     }).then((res) => {
       if (typeof (callback) == "function") callback(res.data)
     }).catch((error) => {
@@ -82,7 +82,7 @@ class DeviceService{
   getChanel(isCatalog, catalogUnderDevice, deviceId, currentPage, count, callback, errorCallback) {
     this.$axios({
       method: 'get',
-      url: `/api/device/query/devices/${deviceId}/channels`,
+      url: `./api/device/query/devices/${deviceId}/channels`,
       params:{
         page: currentPage,
         count: count,
@@ -121,7 +121,7 @@ class DeviceService{
   getSubChannel(isCatalog, deviceId, channelId, currentPage, count, callback, errorCallback) {
     this.$axios({
       method: 'get',
-      url: `/api/device/query/sub_channels/${deviceId}/${channelId}/channels`,
+      url: `./api/device/query/sub_channels/${deviceId}/${channelId}/channels`,
       params:{
         page: currentPage,
         count: count,
@@ -161,7 +161,7 @@ class DeviceService{
     }
     this.$axios({
       method: 'get',
-      url: `/api/device/query/tree/${deviceId}`,
+      url: `./api/device/query/tree/${deviceId}`,
       params:{
         page: currentPage,
         count: count,

+ 7 - 7
web_src/src/components/service/MediaServer.js

@@ -9,7 +9,7 @@ class MediaServer{
   getOnlineMediaServerList(callback){
     this.$axios({
       method: 'get',
-      url:`/api/server/media_server/online/list`,
+      url:`./api/server/media_server/online/list`,
     }).then((res) => {
       if (typeof (callback) == "function") callback(res.data)
     }).catch((error) => {
@@ -19,7 +19,7 @@ class MediaServer{
   getMediaServerList(callback){
     this.$axios({
       method: 'get',
-      url:`/api/server/media_server/list`,
+      url:`./api/server/media_server/list`,
     }).then(function (res) {
       if (typeof (callback) == "function") callback(res.data)
     }).catch(function (error) {
@@ -30,7 +30,7 @@ class MediaServer{
   getMediaServer(id, callback){
     this.$axios({
       method: 'get',
-      url:`/api/server/media_server/one/` + id,
+      url:`./api/server/media_server/one/` + id,
     }).then(function (res) {
       if (typeof (callback) == "function") callback(res.data)
     }).catch(function (error) {
@@ -41,7 +41,7 @@ class MediaServer{
   checkServer(param, callback){
     this.$axios({
       method: 'get',
-      url:`/api/server/media_server/check`,
+      url:`./api/server/media_server/check`,
       params: {
         ip: param.ip,
         port: param.httpPort,
@@ -57,7 +57,7 @@ class MediaServer{
   checkRecordServer(param, callback){
     this.$axios({
       method: 'get',
-      url:`/api/server/media_server/record/check`,
+      url:`./api/server/media_server/record/check`,
       params: {
         ip: param.ip,
         port: param.recordAssistPort
@@ -72,7 +72,7 @@ class MediaServer{
   addServer(param, callback){
     this.$axios({
       method: 'post',
-      url:`/api/server/media_server/save`,
+      url:`./api/server/media_server/save`,
       data: param
     }).then(function (res) {
       if (typeof (callback) == "function") callback(res.data)
@@ -84,7 +84,7 @@ class MediaServer{
   delete(id, callback) {
     this.$axios({
       method: 'delete',
-      url:`/api/server/media_server/delete`,
+      url:`./api/server/media_server/delete`,
       params: {
         id: id
       }