648540858
2023-02-10 f2c6210539cfc5f423a773b4d897c37a9066f821
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
84个文件已修改
8个文件已添加
1 文件已重命名
2个文件已删除
3217 ■■■■■ 已修改文件
README.md 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/_content/introduction/compile.md 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/_content/introduction/config.md 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/_content/introduction/deployment.md 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/update.sql 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/common/enums/DeviceControlType.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/ServiceInfo.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/security/UrlTokenHandler.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/AlarmChannelMessage.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarmMethod.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DragZoomRequest.java 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/HomePositionRequest.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEventListener.java 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/session/RecordDataCatch.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java 104 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java 276 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java 274 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceInfoQueryMessageHandler.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/utils/MessageElement.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java 974 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResult.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceAlarmMapper.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformChannelMapper.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/utils/JsonUtil.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/all-application.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 148 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application.yml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/db/migration/V2.6.7_20230201__初始化.sql 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/build/utils.js 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/config/index.js 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/CloudRecord.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/CloudRecordDetail.vue 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/DeviceList.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/GBRecordDetail.vue 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/Login.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/ParentPlatformList.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/PushVideoList.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/StreamProxyList.vue 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/UserManager.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/channelList.vue 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/console.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/MediaServerEdit.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/StreamProxyEdit.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/SyncChannelProgress.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/addUser.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/catalogEdit.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/changePassword.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/changePasswordForAdmin.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/changePushKey.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/channelMapInfobox.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/chooseChannel.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/chooseChannelForCatalog.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/chooseChannelForGb.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/chooseChannelForStream.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/deviceEdit.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/getCatalog.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/importChannel.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/onvifEdit.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/platformEdit.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/pushStreamEdit.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/queryTrace.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/recordDownload.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/live.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/map.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/service/DeviceService.js 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/service/MediaServer.js 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md
@@ -98,6 +98,7 @@
- [X] æ”¯æŒæŽ¨æµé‰´æƒ
- [X] æ”¯æŒæŽ¥å£é‰´æƒ
- [X] äº‘端录像,推流/代理/国标视频均可以录制在云端服务器,支持预览和下载
- [X] æ”¯æŒæ‰“包可执行jar和war
 
# é‡åˆ°é—®é¢˜å¦‚何解决
doc/_content/introduction/compile.md
@@ -77,13 +77,18 @@
**编译完成一般是这个样子,中间没有报红的错误信息**
![编译成功](_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)
  
doc/_content/introduction/config.md
@@ -31,6 +31,7 @@
## 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 @@
如果配置信息无误,你可以启动zlm,再启动wvp来测试了,启动成功的话,你可以在wvp的日志下看到zlm已连接的提示。
接下来[部署到服务器](./_content/introduction/deployment.md), å¦‚何你只是本地运行直接再本地运行即可。
接下来[部署到服务器](./_content/introduction/deployment.md), å¦‚何你只是本地运行直接再本地运行即可。
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 &
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>
@@ -55,6 +56,42 @@
        <asciidoctor.html.output.directory>${project.build.directory}/asciidoc/html</asciidoctor.html.output.directory>
        <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>
@@ -242,8 +279,8 @@
            <artifactId>spring-boot-starter-test</artifactId>
<!--            <scope>test</scope>-->
        </dependency>
    </dependencies>
    </dependencies>
    <build>
sql/update.sql
File was deleted
src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java
@@ -1,19 +1,23 @@
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 @@
@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 @@
        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);
    }
}
src/main/java/com/genersoft/iot/vmp/common/enums/DeviceControlType.java
New file
@@ -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;
    }
}
src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java
@@ -10,6 +10,7 @@
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 @@
 * @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 @@
        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);
src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java
@@ -2,7 +2,6 @@
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.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 @@
            } 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 @@
            } catch (IOException ioException) {
                if (ioException instanceof ConnectException) {
                    logger.error("录像服务 è¿žæŽ¥å¤±è´¥");
                } else if (ioException instanceof ClientAbortException) {
                    logger.error("录像服务:用户已中断连接,代理终止");
                } else {
                }else {
                    logger.error("录像服务 ä»£ç†å¤±è´¥ï¼š ", e);
                }
            } catch (RuntimeException exception){
src/main/java/com/genersoft/iot/vmp/conf/ServiceInfo.java
New file
@@ -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;
    }
}
src/main/java/com/genersoft/iot/vmp/conf/security/UrlTokenHandler.java
File was deleted
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 @@
     * å›½æ ‡ç¼–号
     */
    private String gbId;
    /**
     * æŠ¥è­¦ç¼–号
     */
    private int alarmSn;
    /**
     * å‘Šè­¦ç±»åž‹
     */
    private int alarmType;
    /**
     * æŠ¥è­¦æè¿°
@@ -36,6 +39,14 @@
        this.alarmSn = alarmSn;
    }
    public int getAlarmType() {
        return alarmType;
    }
    public void setAlarmType(int alarmType) {
        this.alarmType = alarmType;
    }
    public String getAlarmDescription() {
        return alarmDescription;
    }
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarmMethod.java
@@ -37,4 +37,18 @@
    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;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DragZoomRequest.java
New file
@@ -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;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/bean/HomePositionRequest.java
New file
@@ -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;
    }
}
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;
@@ -19,6 +20,8 @@
    private String name;
    
    private int sumNum;
    private int count;
    private Instant lastTime;
    
@@ -79,4 +82,12 @@
    public void setLastTime(Instant lastTime) {
        this.lastTime = lastTime;
    }
    public int getCount() {
        return count;
    }
    public void setCount(int count) {
        this.count = count;
    }
}
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 @@
    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);
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java
@@ -109,12 +109,18 @@
        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 = "同步失败,等待回复超时";
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 @@
    @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 @@
                msg.setKey(msgKey);
                msg.setData(recordInfo);
                deferredResultHolder.invokeAllResult(msg);
                recordEndEventListener.delEndEventHandler(recordInfo.getDeviceId(),recordInfo.getChannelId());
                data.remove(key);
            }
        }
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java
@@ -4,6 +4,7 @@
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 @@
        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;
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java
@@ -47,61 +47,65 @@
    }
    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();
            }
        }
        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;
            }
            if (message instanceof Request) {
                tcpSipProvider.sendRequest((Request)message);
            }else if (message instanceof Response) {
                tcpSipProvider.sendResponse((Response)message);
            if (message.getHeader(UserAgentHeader.NAME) == null) {
                try {
                    message.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
                } catch (ParseException e) {
                    logger.error("添加UserAgentHeader失败", e);
                }
            }
        } else if ("UDP".equals(transport)) {
            SipProviderImpl sipProvider = sipLayer.getUdpSipProvider(ip);
            if (sipProvider == null) {
                logger.error("[发送信息失败] æœªæ‰¾åˆ°udp://{}的监听信息", 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) {
                sipProvider.sendRequest((Request)message);
            }else if (message instanceof Response) {
                sipProvider.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);
                }
            }
        } finally {
            logger.info("[SEND]:SUCCESS:{}", message);
        }
    }
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
@@ -182,7 +182,7 @@
     * @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 @@
     * 
     * @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 @@
     * @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 @@
     * @param channelId  é¢„览通道
     */
    void iFrameCmd(Device device, String channelId) throws InvalidArgumentException, SipException, ParseException;
    /**
     * çœ‹å®ˆä½æŽ§åˆ¶å‘½ä»¤
     *
     * @param device        è§†é¢‘设备
     * @param enabled        çœ‹å®ˆä½ä½¿èƒ½ï¼š1 = å¼€å¯ï¼Œ0 = å…³é—­
     * @param resetTime        è‡ªåŠ¨å½’ä½æ—¶é—´é—´éš”ï¼Œå¼€å¯çœ‹å®ˆä½æ—¶ä½¿ç”¨ï¼Œå•ä½:秒(s)
     * @param presetIndex    è°ƒç”¨é¢„置位编号,开启看守位时使用,取值范围0~255
     *
     * @param device      è§†é¢‘设备
     * @param channelId      é€šé“id,非通道则是设备本身
     * @param 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) throws InvalidArgumentException, SipException, ParseException;
    void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
    /**
     * è®¾å¤‡é…ç½®å‘½ä»¤
     * 
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
@@ -51,11 +51,11 @@
    /**
     * å‘上级回复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查询信息
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -32,6 +32,7 @@
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 @@
     * @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 @@
        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 @@
     * @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 @@
        
        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 @@
     * @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 @@
        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 @@
     * çœ‹å®ˆä½æŽ§åˆ¶å‘½ä»¤
     *
     * @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 @@
        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);
    }
    /**
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 @@
    @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 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 @@
     * å‘上级回复通道信息
     * @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 @@
        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 @@
        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 @@
        // 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 @@
     * @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 @@
        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 @@
        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 @@
        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 @@
                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 @@
        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 @@
                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 @@
                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 @@
        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 @@
        }
        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 @@
                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 @@
            }
        }
        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 @@
        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);
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java
@@ -16,7 +16,6 @@
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;
@@ -41,15 +40,6 @@
    @Autowired
    private SIPSender sipSender;
    public AddressFactory getAddressFactory() {
        try {
            return SipFactory.getInstance().createAddressFactory();
        } catch (PeerUnavailableException e) {
            e.printStackTrace();
        }
        return null;
    }
    public HeaderFactory getHeaderFactory() {
        try {
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
@@ -275,7 +275,7 @@
                    }
                    return;
                } else {
                    logger.info("通道不存在,返回404");
                    logger.info("通道不存在,返回404: {}", channelId);
                    try {
                        // é€šé“不存在,发404,资源不存在
                        responseAck(request, Response.NOT_FOUND);
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 @@
            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;
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.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 @@
                } catch (InvalidArgumentException | ParseException | SipException e) {
                    logger.error("[命令发送失败] å›½æ ‡çº§è” æ³¨é”€: {}", e.getMessage());
                }
                taskExecutor.execute(()->{
                taskExecutor.execute(() -> {
                    // è¿œç¨‹å¯åЍ
//                    try {
//                        Thread.sleep(3000);
@@ -101,13 +101,12 @@
//                        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 @@
                }
                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());
        }
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java
@@ -181,11 +181,14 @@
                            }
                        }
                        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 @@
            alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod()));
            alarmChannelMessage.setAlarmDescription(deviceAlarm.getAlarmDescription());
            alarmChannelMessage.setGbId(channelId);
            alarmChannelMessage.setAlarmType(Integer.parseInt(deviceAlarm.getAlarmType()));
            redisCatchStorage.sendAlarmMsg(alarmChannelMessage);
            return;
        }
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 @@
    @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
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.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.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 @@
    @Autowired
    private SIPCommanderFroPlatform cmderFroPlatform;
    @Autowired
    private IVideoManagerStorage storager;
    @Override
    public void afterPropertiesSet() throws Exception {
@@ -52,10 +57,20 @@
            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());
        }
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java
@@ -102,8 +102,9 @@
                        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 @@
                                    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);
                            }
src/main/java/com/genersoft/iot/vmp/gb28181/utils/MessageElement.java
New file
@@ -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 "";
}
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.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 @@
        }
        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;
    }
}
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
@@ -10,6 +10,8 @@
import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.media.zlm.dto.HookType;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
@@ -19,7 +21,10 @@
import com.genersoft.iot.vmp.service.*;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.vmanager.bean.DeferredResultEx;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -27,6 +32,7 @@
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import javax.servlet.http.HttpServletRequest;
import javax.sip.InvalidArgumentException;
@@ -35,20 +41,21 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
/**
 * @description:针对 ZLMediaServer的hook事件监听
 * @author: swwheihei
 * @date:   2020å¹´5月8日 ä¸Šåˆ10:46:48
 * @date: 2020å¹´5月8日 ä¸Šåˆ10:46:48
 */
@RestController
@RequestMapping("/index/hook")
public class ZLMHttpHookListener {
    private final static Logger logger = LoggerFactory.getLogger(ZLMHttpHookListener.class);
    private final static Logger logger = LoggerFactory.getLogger(ZLMHttpHookListener.class);
    @Autowired
    private SIPCommander cmder;
    @Autowired
    private SIPCommander cmder;
    @Autowired
    private ISIPCommanderForPlatform commanderFroPlatform;
@@ -59,265 +66,243 @@
    @Autowired
    private ZLMRTPServerFactory zlmrtpServerFactory;
    @Autowired
    private IPlayService playService;
    @Autowired
    private IPlayService playService;
    @Autowired
    private IVideoManagerStorage storager;
    @Autowired
    private IVideoManagerStorage storager;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private IDeviceService deviceService;
    @Autowired
    private IDeviceService deviceService;
    @Autowired
    private IMediaServerService mediaServerService;
    @Autowired
    private IMediaServerService mediaServerService;
    @Autowired
    private IStreamProxyService streamProxyService;
    @Autowired
    private IStreamProxyService streamProxyService;
    @Autowired
    private IStreamPushService streamPushService;
    @Autowired
    private DeferredResultHolder resultHolder;
    @Autowired
    private IMediaService mediaService;
    @Autowired
    private IMediaService mediaService;
    @Autowired
    private EventPublisher eventPublisher;
    @Autowired
    private EventPublisher eventPublisher;
     @Autowired
     private ZLMMediaListManager zlmMediaListManager;
    @Autowired
    private ZLMMediaListManager zlmMediaListManager;
    @Autowired
    private ZlmHttpHookSubscribe subscribe;
    @Autowired
    private ZlmHttpHookSubscribe subscribe;
    @Autowired
    private UserSetting userSetting;
    @Autowired
    private UserSetting userSetting;
    @Autowired
    private IUserService userService;
    @Autowired
    private IUserService userService;
    @Autowired
    private VideoStreamSessionManager sessionManager;
    @Autowired
    private VideoStreamSessionManager sessionManager;
    @Autowired
    private AssistRESTfulUtils assistRESTfulUtils;
    @Autowired
    private AssistRESTfulUtils assistRESTfulUtils;
    @Qualifier("taskExecutor")
    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;
    @Qualifier("taskExecutor")
    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;
    /**
     * æœåŠ¡å™¨å®šæ—¶ä¸ŠæŠ¥æ—¶é—´ï¼Œä¸ŠæŠ¥é—´éš”å¯é…ç½®ï¼Œé»˜è®¤10s上报一次
     *
     */
    @ResponseBody
    @PostMapping(value = "/on_server_keepalive", produces = "application/json;charset=UTF-8")
    public JSONObject onServerKeepalive(@RequestBody OnServerKeepaliveHookParam param){
    /**
     * æœåŠ¡å™¨å®šæ—¶ä¸ŠæŠ¥æ—¶é—´ï¼Œä¸ŠæŠ¥é—´éš”å¯é…ç½®ï¼Œé»˜è®¤10s上报一次
     */
    @ResponseBody
    @PostMapping(value = "/on_server_keepalive", produces = "application/json;charset=UTF-8")
    public HookResult onServerKeepalive(@RequestBody OnServerKeepaliveHookParam param) {
        logger.info("[ZLM HOOK] æ”¶åˆ°zlm心跳:" + param.getMediaServerId());
        logger.info("[ZLM HOOK] æ”¶åˆ°zlm心跳:" + param.getMediaServerId());
        taskExecutor.execute(()->{
            List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_keepalive);
            JSONObject json = (JSONObject) JSON.toJSON(param);
            if (subscribes != null  && subscribes.size() > 0) {
                for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
                    subscribe.response(null, json);
                }
            }
        });
        mediaServerService.updateMediaServerKeepalive(param.getMediaServerId(), param.getData());
        taskExecutor.execute(() -> {
            List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_keepalive);
            JSONObject json = (JSONObject) JSON.toJSON(param);
            if (subscribes != null && subscribes.size() > 0) {
                for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
                    subscribe.response(null, json);
                }
            }
        });
        mediaServerService.updateMediaServerKeepalive(param.getMediaServerId(), param.getData());
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("msg", "success");
        return HookResult.SUCCESS();
    }
        return ret;
    }
    /**
     * æ’­æ”¾å™¨é‰´æƒäº‹ä»¶ï¼Œrtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件。
     *
     */
    @ResponseBody
    @PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8")
    public JSONObject onPlay(@RequestBody OnPlayHookParam param){
        if (logger.isDebugEnabled()) {
            logger.debug("[ZLM HOOK] æ’­æ”¾é‰´æƒï¼š{}->{}" + param.getMediaServerId(), param);
        }
        String mediaServerId = param.getMediaServerId();
    /**
     * æ’­æ”¾å™¨é‰´æƒäº‹ä»¶ï¼Œrtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件。
     */
    @ResponseBody
    @PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8")
    public HookResult onPlay(@RequestBody OnPlayHookParam param) {
        if (logger.isDebugEnabled()) {
            logger.debug("[ZLM HOOK] æ’­æ”¾é‰´æƒï¼š{}->{}" + param.getMediaServerId(), param);
        }
        String mediaServerId = param.getMediaServerId();
        taskExecutor.execute(()->{
            JSONObject json = (JSONObject) JSON.toJSON(param);
            ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_play, json);
            if (subscribe != null ) {
                MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
                if (mediaInfo != null) {
                    subscribe.response(mediaInfo, json);
                }
            }
        });
        JSONObject ret = new JSONObject();
        if (!"rtp".equals(param.getApp())) {
            Map<String, String> paramMap = urlParamToMap(param.getParams());
            StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
            if (streamAuthorityInfo != null && streamAuthorityInfo.getCallId() != null && !streamAuthorityInfo.getCallId().equals(paramMap.get("callId"))) {
                ret.put("code", 401);
                ret.put("msg", "Unauthorized");
                return ret;
            }
        }
        taskExecutor.execute(() -> {
            JSONObject json = (JSONObject) JSON.toJSON(param);
            ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_play, json);
            if (subscribe != null) {
                MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
                if (mediaInfo != null) {
                    subscribe.response(mediaInfo, json);
                }
            }
        });
        if (!"rtp".equals(param.getApp())) {
            Map<String, String> paramMap = urlParamToMap(param.getParams());
            StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
            if (streamAuthorityInfo != null && streamAuthorityInfo.getCallId() != null && !streamAuthorityInfo.getCallId().equals(paramMap.get("callId"))) {
                return new HookResult(401, "Unauthorized");
            }
        }
        ret.put("code", 0);
        ret.put("msg", "success");
        return ret;
    }
    /**
     * rtsp/rtmp/rtp推流鉴权事件。
     *
     */
    @ResponseBody
    @PostMapping(value = "/on_publish", produces = "application/json;charset=UTF-8")
    public JSONObject onPublish(@RequestBody OnPublishHookParam param) {
        return HookResult.SUCCESS();
    }
        JSONObject json = (JSONObject) JSON.toJSON(param);
    /**
     * rtsp/rtmp/rtp推流鉴权事件。
     */
    @ResponseBody
    @PostMapping(value = "/on_publish", produces = "application/json;charset=UTF-8")
    public HookResultForOnPublish onPublish(@RequestBody OnPublishHookParam param) {
        logger.info("[ZLM HOOK]推流鉴权:{}->{}",  param.getMediaServerId(), param);
        JSONObject ret = new JSONObject();
        String mediaServerId = json.getString("mediaServerId");
        MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
        JSONObject json = (JSONObject) JSON.toJSON(param);
        if (!"rtp".equals(param.getApp())) {
            if (userSetting.getPushAuthority()) {
                // æŽ¨æµé‰´æƒ
                if (param.getParams() == null) {
                    logger.info("推流鉴权失败: ç¼ºå°‘不要参数:sign=md5(user表的pushKey)");
                    ret.put("code", 401);
                    ret.put("msg", "Unauthorized");
                    return ret;
                }
                Map<String, String> paramMap = urlParamToMap(param.getParams());
                String sign = paramMap.get("sign");
                if (sign == null) {
                    logger.info("推流鉴权失败: ç¼ºå°‘不要参数:sign=md5(user表的pushKey)");
                    ret.put("code", 401);
                    ret.put("msg", "Unauthorized");
                    return ret;
                }
                // æŽ¨æµè‡ªå®šä¹‰æ’­æ”¾é‰´æƒç 
                String callId = paramMap.get("callId");
                // é‰´æƒé…ç½®
                boolean hasAuthority = userService.checkPushAuthority(callId, sign);
                if (!hasAuthority) {
                    logger.info("推流鉴权失败: sign æ— æƒé™: callId={}. sign={}", callId, sign);
                    ret.put("code", 401);
                    ret.put("msg", "Unauthorized");
                    return ret;
                }
                StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param);
                streamAuthorityInfo.setCallId(callId);
                streamAuthorityInfo.setSign(sign);
                // é‰´æƒé€šè¿‡
                redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo);
                // é€šçŸ¥assist新的callId
                if (mediaInfo != null && mediaInfo.getRecordAssistPort() > 0) {
                    taskExecutor.execute(()->{
                        assistRESTfulUtils.addStreamCallInfo(mediaInfo, param.getApp(), param.getStream(), callId, null);
                    });
                }
            }
        }else {
            zlmMediaListManager.sendStreamEvent(param.getApp(),param.getStream(), param.getMediaServerId());
        }
        logger.info("[ZLM HOOK]推流鉴权:{}->{}", param.getMediaServerId(), param);
        ret.put("code", 0);
        ret.put("msg", "success");
        String mediaServerId = json.getString("mediaServerId");
        MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
        if (!"rtp".equals(param.getApp())) {
            ret.put("enable_audio", true);
        }
        taskExecutor.execute(()->{
            ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_publish, json);
            if (subscribe != null) {
                if (mediaInfo != null) {
                    subscribe.response(mediaInfo, json);
                }else {
                    ret.put("code", 1);
                    ret.put("msg", "zlm not register");
                }
            }
        });
        if ("rtp".equals(param.getApp())) {
            ret.put("enable_mp4", userSetting.getRecordSip());
        }else {
            ret.put("enable_mp4", userSetting.isRecordPushLive());
        }
        List<SsrcTransaction> ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, param.getStream());
        if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) {
            String deviceId = ssrcTransactionForAll.get(0).getDeviceId();
            String channelId = ssrcTransactionForAll.get(0).getChannelId();
            DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
            if (deviceChannel != null) {
                ret.put("enable_audio", deviceChannel.isHasAudio());
            }
            // å¦‚果是录像下载就设置视频间隔十秒
            if (ssrcTransactionForAll.get(0).getType() == VideoStreamSessionManager.SessionType.download) {
                ret.put("mp4_max_second", 10);
                ret.put("enable_mp4", true);
                ret.put("enable_audio", true);
            }
        }
        return ret;
    }
    /**
     * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。
     *
     */
    @ResponseBody
    @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8")
    public JSONObject onStreamChanged(@RequestBody OnStreamChangedHookParam param){
        if (param.isRegist()) {
            logger.info("[ZLM HOOK] æµæ³¨å†Œ, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
        }else {
            logger.info("[ZLM HOOK] æµæ³¨é”€, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
        }
        if (!"rtp".equals(param.getApp())) {
            if (userSetting.getPushAuthority()) {
                // æŽ¨æµé‰´æƒ
                if (param.getParams() == null) {
                    logger.info("推流鉴权失败: ç¼ºå°‘不要参数:sign=md5(user表的pushKey)");
                    return new HookResultForOnPublish(401, "Unauthorized");
                }
                Map<String, String> paramMap = urlParamToMap(param.getParams());
                String sign = paramMap.get("sign");
                if (sign == null) {
                    logger.info("推流鉴权失败: ç¼ºå°‘不要参数:sign=md5(user表的pushKey)");
                    return new HookResultForOnPublish(401, "Unauthorized");
                }
                // æŽ¨æµè‡ªå®šä¹‰æ’­æ”¾é‰´æƒç 
                String callId = paramMap.get("callId");
                // é‰´æƒé…ç½®
                boolean hasAuthority = userService.checkPushAuthority(callId, sign);
                if (!hasAuthority) {
                    logger.info("推流鉴权失败: sign æ— æƒé™: callId={}. sign={}", callId, sign);
                    return new HookResultForOnPublish(401, "Unauthorized");
                }
                StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param);
                streamAuthorityInfo.setCallId(callId);
                streamAuthorityInfo.setSign(sign);
                // é‰´æƒé€šè¿‡
                redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo);
                // é€šçŸ¥assist新的callId
                if (mediaInfo != null && mediaInfo.getRecordAssistPort() > 0) {
                    taskExecutor.execute(() -> {
                        assistRESTfulUtils.addStreamCallInfo(mediaInfo, param.getApp(), param.getStream(), callId, null);
                    });
                }
            }
        } else {
            zlmMediaListManager.sendStreamEvent(param.getApp(), param.getStream(), param.getMediaServerId());
        }
        JSONObject json = (JSONObject) JSON.toJSON(param);
        taskExecutor.execute(()-> {
            ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json);
            if (subscribe != null) {
                MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId());
                if (mediaInfo != null) {
                    subscribe.response(mediaInfo, json);
                }
            }
            // æµæ¶ˆå¤±ç§»é™¤redis play
            List<OnStreamChangedHookParam.MediaTrack> tracks = param.getTracks();
            if (param.isRegist()) {
                if (param.getOriginType() == OriginType.RTMP_PUSH.ordinal()
                        || param.getOriginType() == OriginType.RTSP_PUSH.ordinal()
                        || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) {
        HookResultForOnPublish result = HookResultForOnPublish.SUCCESS();
        if (!"rtp".equals(param.getApp())) {
            result.setEnable_audio(true);
        }
                    StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
                    if (streamAuthorityInfo == null) {
                        streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param);
                    } else {
                        streamAuthorityInfo.setOriginType(param.getOriginType());
                        streamAuthorityInfo.setOriginTypeStr(param.getOriginTypeStr());
                    }
                    redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo);
                }
            } else {
                redisCatchStorage.removeStreamAuthorityInfo(param.getApp(), param.getStream());
            }
        });
        taskExecutor.execute(() -> {
            ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_publish, json);
            if (subscribe != null) {
                if (mediaInfo != null) {
                    subscribe.response(mediaInfo, json);
                } else {
                    new HookResultForOnPublish(1, "zlm not register");
                }
            }
        });
        if ("rtp".equals(param.getApp())) {
            result.setEnable_mp4(userSetting.getRecordSip());
        } else {
            result.setEnable_mp4(userSetting.isRecordPushLive());
        }
        List<SsrcTransaction> ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, param.getStream());
        if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) {
            String deviceId = ssrcTransactionForAll.get(0).getDeviceId();
            String channelId = ssrcTransactionForAll.get(0).getChannelId();
            DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
            if (deviceChannel != null) {
                result.setEnable_audio(deviceChannel.isHasAudio());
            }
            // å¦‚果是录像下载就设置视频间隔十秒
            if (ssrcTransactionForAll.get(0).getType() == VideoStreamSessionManager.SessionType.download) {
                result.setMp4_max_second(10);
                result.setEnable_audio(true);
                result.setEnable_mp4(true);
            }
        }
        return result;
    }
    /**
     * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。
     */
    @ResponseBody
    @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8")
    public HookResult onStreamChanged(@RequestBody OnStreamChangedHookParam param) {
        if (param.isRegist()) {
            logger.info("[ZLM HOOK] æµæ³¨å†Œ, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
        } else {
            logger.info("[ZLM HOOK] æµæ³¨é”€, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
        }
        JSONObject json = (JSONObject) JSON.toJSON(param);
        taskExecutor.execute(() -> {
            ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json);
            if (subscribe != null) {
                MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId());
                if (mediaInfo != null) {
                    subscribe.response(mediaInfo, json);
                }
            }
            // æµæ¶ˆå¤±ç§»é™¤redis play
            List<OnStreamChangedHookParam.MediaTrack> tracks = param.getTracks();
            if (param.isRegist()) {
                if (param.getOriginType() == OriginType.RTMP_PUSH.ordinal()
                        || param.getOriginType() == OriginType.RTSP_PUSH.ordinal()
                        || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) {
                    StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
                    if (streamAuthorityInfo == null) {
                        streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param);
                    } else {
                        streamAuthorityInfo.setOriginType(param.getOriginType());
                        streamAuthorityInfo.setOriginTypeStr(param.getOriginTypeStr());
                    }
                    redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo);
                }
            } else {
                redisCatchStorage.removeStreamAuthorityInfo(param.getApp(), param.getStream());
            }
        if ("rtsp".equals(param.getSchema())){
            logger.info("流变化:注册->{}, app->{}, stream->{}", param.isRegist(), param.getApp(), param.getStream());
@@ -465,72 +450,57 @@
                                GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream());
                                if (gbStream != null) {
//                                    eventPublisher.catalogEventPublishForStream(null, gbStream, CatalogEvent.OFF);
                                }
                                zlmMediaListManager.removeMedia(param.getApp(), param.getStream());
                            }
                            if (type != null) {
                                // å‘送流变化redis消息
                                JSONObject jsonObject = new JSONObject();
                                jsonObject.put("serverId", userSetting.getServerId());
                                jsonObject.put("app", param.getApp());
                                jsonObject.put("stream", param.getStream());
                                jsonObject.put("register", param.isRegist());
                                jsonObject.put("mediaServerId", param.getMediaServerId());
                                redisCatchStorage.sendStreamChangeMsg(type, jsonObject);
                            }
                        }
                    }
                }
                if (!param.isRegist()) {
                    List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream());
                    if (sendRtpItems.size() > 0) {
                        for (SendRtpItem sendRtpItem : sendRtpItems) {
                            if (sendRtpItem.getApp().equals(param.getApp())) {
                                String platformId = sendRtpItem.getPlatformId();
                                ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);
                                Device device = deviceService.getDevice(platformId);
                                }
                                zlmMediaListManager.removeMedia(param.getApp(), param.getStream());
                            }
                            if (type != null) {
                                // å‘送流变化redis消息
                                JSONObject jsonObject = new JSONObject();
                                jsonObject.put("serverId", userSetting.getServerId());
                                jsonObject.put("app", param.getApp());
                                jsonObject.put("stream", param.getStream());
                                jsonObject.put("register", param.isRegist());
                                jsonObject.put("mediaServerId", param.getMediaServerId());
                                redisCatchStorage.sendStreamChangeMsg(type, jsonObject);
                            }
                        }
                    }
                }
                if (!param.isRegist()) {
                    List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream());
                    if (sendRtpItems.size() > 0) {
                        for (SendRtpItem sendRtpItem : sendRtpItems) {
                            if (sendRtpItem.getApp().equals(param.getApp())) {
                                String platformId = sendRtpItem.getPlatformId();
                                ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);
                                Device device = deviceService.getDevice(platformId);
                            try {
                                if (platform != null) {
                                    commanderFroPlatform.streamByeCmd(platform, sendRtpItem);
                                }else {
                                    if (sendRtpItem.isOnlyAudio()) {
                                        AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
                                        if (audioBroadcastCatch != null) {
//                                            playService.stopAudioBroadcast(device.getDeviceId(), sendRtpItem.getChannelId());
                                            if ("talk".equals(param.getApp())) {
//                                                cmder.streamByeCmd(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null);
                                            }else {
//                                                cmder.streamByeCmd(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null);
                                            }
                                        }
                                    }
                                try {
                                    if (platform != null) {
                                        commanderFroPlatform.streamByeCmd(platform, sendRtpItem);
                                    } else {
                                        cmder.streamByeCmd(device, sendRtpItem.getChannelId(), param.getStream(), sendRtpItem.getCallId());
                                    }
                                } catch (SipException | InvalidArgumentException | ParseException |
                                         SsrcTransactionNotFoundException e) {
                                    logger.error("[命令发送失败] å›½æ ‡çº§è” å‘送BYE: {}", e.getMessage());
                                }
                            }
                        }
                    }
                }
            }
        });
        return HookResult.SUCCESS();
    }
                                }
                            } catch (SipException | InvalidArgumentException | ParseException e) {
                                logger.error("[命令发送失败] å›½æ ‡çº§è” å‘送BYE: {}", e.getMessage());
                            }
                        }
                    }
                }
            }
        }
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("msg", "success");
        return ret;
    }
    /**
     * æµæ— äººè§‚看时事件,用户可以通过此事件选择是否关闭无人看的流。
     *
     */
    @ResponseBody
    @PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8")
    public JSONObject onStreamNoneReader(@RequestBody OnStreamNoneReaderHookParam param){
    /**
     * æµæ— äººè§‚看时事件,用户可以通过此事件选择是否关闭无人看的流。
     */
    @ResponseBody
    @PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8")
    public JSONObject onStreamNoneReader(@RequestBody OnStreamNoneReaderHookParam param) {
        logger.info("[ZLM HOOK]流无人观看:{]->{}->{}/{}" + param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
        JSONObject ret = new JSONObject();
@@ -571,215 +541,243 @@
                    }
                }
                redisCatchStorage.stopPlay(streamInfoForPlayCatch);
                storager.stopPlay(streamInfoForPlayCatch.getDeviceID(), streamInfoForPlayCatch.getChannelId());
                return ret;
            }
            // å½•像回放
            StreamInfo streamInfoForPlayBackCatch = redisCatchStorage.queryPlayback(null, null, param.getStream(), null);
            if (streamInfoForPlayBackCatch != null ) {
                if (streamInfoForPlayBackCatch.isPause()) {
                    ret.put("close", false);
                }else {
                    Device device = deviceService.getDevice(streamInfoForPlayBackCatch.getDeviceID());
                    if (device != null) {
                        try {
                            cmder.streamByeCmd(device,streamInfoForPlayBackCatch.getChannelId(),
                                    streamInfoForPlayBackCatch.getStream(), null);
                        } catch (InvalidArgumentException | ParseException | SipException |
                                 SsrcTransactionNotFoundException e) {
                            logger.error("[无人观看]回放, å‘送BYE失败 {}", e.getMessage());
                        }
                    }
                    redisCatchStorage.stopPlayback(streamInfoForPlayBackCatch.getDeviceID(),
                            streamInfoForPlayBackCatch.getChannelId(), streamInfoForPlayBackCatch.getStream(), null);
                }
                return ret;
            }
            // å½•像下载
            StreamInfo streamInfoForDownload = redisCatchStorage.queryDownload(null, null, param.getStream(), null);
            // è¿›è¡Œå½•像下载时无人观看不断流
            if (streamInfoForDownload != null) {
                ret.put("close", false);
                return ret;
            }
        }else {
            // éžå›½æ ‡æµ æŽ¨æµ/拉流代理
            // æ‹‰æµä»£ç†
            StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream());
            if (streamProxyItem != null ) {
                if (streamProxyItem.isEnable_remove_none_reader()) {
                    // æ— äººè§‚看自动移除
                    ret.put("close", true);
                    streamProxyService.del(param.getApp(), param.getStream());
                    String url = streamProxyItem.getUrl() != null?streamProxyItem.getUrl():streamProxyItem.getSrc_url();
                    logger.info("[{}/{}]<-[{}] æ‹‰æµä»£ç†æ— äººè§‚看已经移除",  param.getApp(), param.getStream(), url);
                }else if (streamProxyItem.isEnable_disable_none_reader()) {
                    // æ— äººè§‚看停用
                    ret.put("close", true);
                    // ä¿®æ”¹æ•°æ®
                    streamProxyService.stop(param.getApp(), param.getStream());
                }else {
                    // æ— äººè§‚看不做处理
                    ret.put("close", false);
                }
                return ret;
            }
            // æŽ¨æµå…·æœ‰ä¸»åŠ¨æ€§ï¼Œæš‚æ—¶ä¸åšå¤„ç†
                redisCatchStorage.stopPlay(streamInfoForPlayCatch);
                storager.stopPlay(streamInfoForPlayCatch.getDeviceID(), streamInfoForPlayCatch.getChannelId());
                return ret;
            }
            // å½•像回放
            StreamInfo streamInfoForPlayBackCatch = redisCatchStorage.queryPlayback(null, null, param.getStream(), null);
            if (streamInfoForPlayBackCatch != null) {
                if (streamInfoForPlayBackCatch.isPause()) {
                    ret.put("close", false);
                } else {
                    Device device = deviceService.getDevice(streamInfoForPlayBackCatch.getDeviceID());
                    if (device != null) {
                        try {
                            cmder.streamByeCmd(device, streamInfoForPlayBackCatch.getChannelId(),
                                    streamInfoForPlayBackCatch.getStream(), null);
                        } catch (InvalidArgumentException | ParseException | SipException |
                                 SsrcTransactionNotFoundException e) {
                            logger.error("[无人观看]回放, å‘送BYE失败 {}", e.getMessage());
                        }
                    }
                    redisCatchStorage.stopPlayback(streamInfoForPlayBackCatch.getDeviceID(),
                            streamInfoForPlayBackCatch.getChannelId(), streamInfoForPlayBackCatch.getStream(), null);
                }
                return ret;
            }
            // å½•像下载
            StreamInfo streamInfoForDownload = redisCatchStorage.queryDownload(null, null, param.getStream(), null);
            // è¿›è¡Œå½•像下载时无人观看不断流
            if (streamInfoForDownload != null) {
                ret.put("close", false);
                return ret;
            }
        } else {
            // éžå›½æ ‡æµ æŽ¨æµ/拉流代理
            // æ‹‰æµä»£ç†
            StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream());
            if (streamProxyItem != null) {
                if (streamProxyItem.isEnable_remove_none_reader()) {
                    // æ— äººè§‚看自动移除
                    ret.put("close", true);
                    streamProxyService.del(param.getApp(), param.getStream());
                    String url = streamProxyItem.getUrl() != null ? streamProxyItem.getUrl() : streamProxyItem.getSrc_url();
                    logger.info("[{}/{}]<-[{}] æ‹‰æµä»£ç†æ— äººè§‚看已经移除", param.getApp(), param.getStream(), url);
                } else if (streamProxyItem.isEnable_disable_none_reader()) {
                    // æ— äººè§‚看停用
                    ret.put("close", true);
                    // ä¿®æ”¹æ•°æ®
                    streamProxyService.stop(param.getApp(), param.getStream());
                } else {
                    // æ— äººè§‚看不做处理
                    ret.put("close", false);
                }
                return ret;
            }
            // æŽ¨æµå…·æœ‰ä¸»åŠ¨æ€§ï¼Œæš‚æ—¶ä¸åšå¤„ç†
//            StreamPushItem streamPushItem = streamPushService.getPush(app, streamId);
//            if (streamPushItem != null) {
//                // TODO å‘送停止
//
//            }
        }
        return ret;
    }
    /**
     * æµæœªæ‰¾åˆ°äº‹ä»¶ï¼Œç”¨æˆ·å¯ä»¥åœ¨æ­¤äº‹ä»¶è§¦å‘时,立即去拉流,这样可以实现按需拉流;此事件对回复不敏感。
     *
     */
    @ResponseBody
    @PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8")
    public JSONObject onStreamNotFound(@RequestBody OnStreamNotFoundHookParam param){
        logger.info("[ZLM HOOK] æµæœªæ‰¾åˆ°ï¼š{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
        taskExecutor.execute(()->{
            MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId());
            if (userSetting.isAutoApplyPlay() && mediaInfo != null) {
                if ("rtp".equals(param.getApp())) {
                    if (mediaInfo.isRtpEnable()) {
                        String[] s = param.getStream().split("_");
                        if (s.length == 2) {
                            String deviceId = s[0];
                            String channelId = s[1];
                            Device device = redisCatchStorage.getDevice(deviceId);
                            if (device != null) {
                                playService.play(mediaInfo,deviceId, channelId, null, null, null);
                            }
                        }
                    }
                }else {
                    // æ‹‰æµä»£ç†
                    StreamProxyItem streamProxyByAppAndStream = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream());
                    if (streamProxyByAppAndStream != null && streamProxyByAppAndStream.isEnable_disable_none_reader()) {
                        streamProxyService.start(param.getApp(), param.getStream());
                    }
                }
            }
        });
        }
        return ret;
    }
    /**
     * æµæœªæ‰¾åˆ°äº‹ä»¶ï¼Œç”¨æˆ·å¯ä»¥åœ¨æ­¤äº‹ä»¶è§¦å‘时,立即去拉流,这样可以实现按需拉流;此事件对回复不敏感。
     */
    @ResponseBody
    @PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8")
    public DeferredResult<HookResult> onStreamNotFound(@RequestBody OnStreamNotFoundHookParam param) {
        logger.info("[ZLM HOOK] æµæœªæ‰¾åˆ°ï¼š{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("msg", "success");
        return ret;
    }
    /**
     * æœåŠ¡å™¨å¯åŠ¨äº‹ä»¶ï¼Œå¯ä»¥ç”¨äºŽç›‘å¬æœåŠ¡å™¨å´©æºƒé‡å¯ï¼›æ­¤äº‹ä»¶å¯¹å›žå¤ä¸æ•æ„Ÿã€‚
     *
     */
    @ResponseBody
    @PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8")
    public JSONObject onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject){
        DeferredResult<HookResult> defaultResult = new DeferredResult<>();
        jsonObject.put("ip", request.getRemoteAddr());
        ZLMServerConfig zlmServerConfig = JSON.to(ZLMServerConfig.class, jsonObject);
        zlmServerConfig.setIp(request.getRemoteAddr());
        logger.info("[ZLM HOOK] zlm å¯åЍ " + zlmServerConfig.getGeneralMediaServerId());
        taskExecutor.execute(()->{
            List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_started);
            if (subscribes != null  && subscribes.size() > 0) {
                for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
                    subscribe.response(null, jsonObject);
                }
            }
            mediaServerService.zlmServerOnline(zlmServerConfig);
        });
        MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId());
        if (!userSetting.isAutoApplyPlay() || mediaInfo == null) {
            defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg()));
            return defaultResult;
        }
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("msg", "success");
        return ret;
    }
        if ("rtp".equals(param.getApp())) {
            String[] s = param.getStream().split("_");
            if (!mediaInfo.isRtpEnable() || s.length != 2) {
                defaultResult.setResult(HookResult.SUCCESS());
                return defaultResult;
            }
            String deviceId = s[0];
            String channelId = s[1];
            Device device = redisCatchStorage.getDevice(deviceId);
            if (device == null) {
                defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg()));
                return defaultResult;
            }
            DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
            if (deviceChannel == null) {
                defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg()));
                return defaultResult;
            }
            logger.info("[ZLM HOOK] æµæœªæ‰¾åˆ°, å‘起自动点播:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
            RequestMessage msg = new RequestMessage();
            String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId;
            boolean exist = resultHolder.exist(key, null);
            msg.setKey(key);
            String uuid = UUID.randomUUID().toString();
            msg.setId(uuid);
            DeferredResult<HookResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
            DeferredResultEx<HookResult> deferredResultEx = new DeferredResultEx<>(result);
    /**
     * å‘送rtp(startSendRtp)被动关闭时回调
     */
    @ResponseBody
    @PostMapping(value = "/on_send_rtp_stopped", produces = "application/json;charset=UTF-8")
    public JSONObject onSendRtpStopped(HttpServletRequest request, @RequestBody OnSendRtpStoppedHookParam param){
            result.onTimeout(() -> {
                logger.info("点播接口等待超时");
                // é‡Šæ”¾rtpserver
                msg.setData(new HookResult(ErrorCode.ERROR100.getCode(), "点播超时"));
                resultHolder.invokeResult(msg);
            });
            // TODO åœ¨ç‚¹æ’­æœªæˆåŠŸçš„æƒ…å†µä¸‹åœ¨æ­¤è°ƒç”¨æŽ¥å£ç‚¹æ’­ä¼šå¯¼è‡´è¿”å›žçš„æµåœ°å€ip错误
            deferredResultEx.setFilter(result1 -> {
                WVPResult<StreamInfo> wvpResult1 = (WVPResult<StreamInfo>) result1;
                HookResult resultForEnd = new HookResult();
                resultForEnd.setCode(wvpResult1.getCode());
                resultForEnd.setMsg(wvpResult1.getMsg());
                return resultForEnd;
            });
        logger.info("[ZLM HOOK] rtp发送关闭:{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream());
            // å½•像查询以channelId作为deviceId查询
            resultHolder.put(key, uuid, deferredResultEx);
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("msg", "success");
            if (!exist) {
                playService.play(mediaInfo, deviceId, channelId, null, eventResult -> {
                    msg.setData(new HookResult(eventResult.statusCode, eventResult.msg));
                    resultHolder.invokeResult(msg);
                }, null);
            }
            return result;
        } else {
            // æ‹‰æµä»£ç†
            StreamProxyItem streamProxyByAppAndStream = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream());
            if (streamProxyByAppAndStream != null && streamProxyByAppAndStream.isEnable_disable_none_reader()) {
                streamProxyService.start(param.getApp(), param.getStream());
            }
            DeferredResult<HookResult> result = new DeferredResult<>();
            result.setResult(HookResult.SUCCESS());
            return result;
        }
    }
        // æŸ¥æ‰¾å¯¹åº”的上级推流,发送停止
        if (!"rtp".equals(param.getApp())) {
            return ret;
        }
        taskExecutor.execute(()->{
            List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream());
            if (sendRtpItems.size() > 0) {
                for (SendRtpItem sendRtpItem : sendRtpItems) {
                    ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
                    try {
                        commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
                    } catch (SipException | InvalidArgumentException | ParseException e) {
                        logger.error("[命令发送失败] å›½æ ‡çº§è” å‘送BYE: {}", e.getMessage());
                    }
                    redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
                            sendRtpItem.getCallId(), sendRtpItem.getStreamId());
                }
            }
        });
    /**
     * æœåŠ¡å™¨å¯åŠ¨äº‹ä»¶ï¼Œå¯ä»¥ç”¨äºŽç›‘å¬æœåŠ¡å™¨å´©æºƒé‡å¯ï¼›æ­¤äº‹ä»¶å¯¹å›žå¤ä¸æ•æ„Ÿã€‚
     */
    @ResponseBody
    @PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8")
    public HookResult onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject) {
        jsonObject.put("ip", request.getRemoteAddr());
        ZLMServerConfig zlmServerConfig = JSON.to(ZLMServerConfig.class, jsonObject);
        zlmServerConfig.setIp(request.getRemoteAddr());
        logger.info("[ZLM HOOK] zlm å¯åЍ " + zlmServerConfig.getGeneralMediaServerId());
        taskExecutor.execute(() -> {
            List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_started);
            if (subscribes != null && subscribes.size() > 0) {
                for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
                    subscribe.response(null, jsonObject);
                }
            }
            mediaServerService.zlmServerOnline(zlmServerConfig);
        });
        return ret;
    }
        return HookResult.SUCCESS();
    }
    /**
     * rtpServer收流超时
     */
    @ResponseBody
    @PostMapping(value = "/on_rtp_server_timeout", produces = "application/json;charset=UTF-8")
    public JSONObject onRtpServerTimeout(HttpServletRequest request, @RequestBody OnRtpServerTimeoutHookParam param){
        logger.info("[ZLM HOOK] rtpServer rtp超时:{}->{}({})", param.getMediaServerId(), param.getStream_id(), param.getSsrc());
    /**
     * å‘送rtp(startSendRtp)被动关闭时回调
     */
    @ResponseBody
    @PostMapping(value = "/on_send_rtp_stopped", produces = "application/json;charset=UTF-8")
    public HookResult onSendRtpStopped(HttpServletRequest request, @RequestBody OnSendRtpStoppedHookParam param) {
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("msg", "success");
        logger.info("[ZLM HOOK] rtp发送关闭:{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream());
        taskExecutor.execute(()->{
            JSONObject json = (JSONObject) JSON.toJSON(param);
            List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_rtp_server_timeout);
            if (subscribes != null  && subscribes.size() > 0) {
                for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
                    subscribe.response(null, json);
                }
            }
        });
        // æŸ¥æ‰¾å¯¹åº”的上级推流,发送停止
        if (!"rtp".equals(param.getApp())) {
            return HookResult.SUCCESS();
        }
        taskExecutor.execute(() -> {
            List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream());
            if (sendRtpItems.size() > 0) {
                for (SendRtpItem sendRtpItem : sendRtpItems) {
                    ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
                    try {
                        commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
                    } catch (SipException | InvalidArgumentException | ParseException e) {
                        logger.error("[命令发送失败] å›½æ ‡çº§è” å‘送BYE: {}", e.getMessage());
                    }
                    redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
                            sendRtpItem.getCallId(), sendRtpItem.getStreamId());
                }
            }
        });
        return ret;
    }
        return HookResult.SUCCESS();
    }
    private Map<String, String> urlParamToMap(String params) {
        HashMap<String, String> map = new HashMap<>();
        if (ObjectUtils.isEmpty(params)) {
            return map;
        }
        String[] paramsArray = params.split("&");
        if (paramsArray.length == 0) {
            return map;
        }
        for (String param : paramsArray) {
            String[] paramArray = param.split("=");
            if (paramArray.length == 2){
                map.put(paramArray[0], paramArray[1]);
            }
        }
        return map;
    }
    /**
     * rtpServer收流超时
     */
    @ResponseBody
    @PostMapping(value = "/on_rtp_server_timeout", produces = "application/json;charset=UTF-8")
    public HookResult onRtpServerTimeout(HttpServletRequest request, @RequestBody OnRtpServerTimeoutHookParam param) {
        logger.info("[ZLM HOOK] rtpServer收流超时:{}->{}({})", param.getMediaServerId(), param.getStream_id(), param.getSsrc());
        taskExecutor.execute(() -> {
            JSONObject json = (JSONObject) JSON.toJSON(param);
            List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_rtp_server_timeout);
            if (subscribes != null && subscribes.size() > 0) {
                for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
                    subscribe.response(null, json);
                }
            }
        });
        return HookResult.SUCCESS();
    }
    private Map<String, String> urlParamToMap(String params) {
        HashMap<String, String> map = new HashMap<>();
        if (ObjectUtils.isEmpty(params)) {
            return map;
        }
        String[] paramsArray = params.split("&");
        if (paramsArray.length == 0) {
            return map;
        }
        for (String param : paramsArray) {
            String[] paramArray = param.split("=");
            if (paramArray.length == 2) {
                map.put(paramArray[0], paramArray[1]);
            }
        }
        return map;
    }
}
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
@@ -36,7 +36,7 @@
            // è®¾ç½®è¿žæŽ¥è¶…æ—¶æ—¶é—´
            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 @@
                    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()));
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResult.java
New file
@@ -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;
    }
}
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java
New file
@@ -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;
    }
}
src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java
@@ -152,6 +152,10 @@
    @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);
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
@@ -33,6 +33,7 @@
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 @@
        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 @@
            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 @@
            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);
src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java
@@ -184,7 +184,9 @@
    @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);
src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java
@@ -62,16 +62,16 @@
                        }
                        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)) {
                            // å‘送给所有的上级
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java
@@ -2,7 +2,6 @@
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 @@
    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 @@
     */
    boolean resetChannels(String deviceId, List<DeviceChannel> deviceChannelList);
    boolean updateChannels(String deviceId, List<DeviceChannel> deviceChannelList);
    /**
     * èŽ·å–ç›®å½•ä¿¡æ¯
     * @param platformId
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 @@
    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>" +
src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformChannelMapper.java
@@ -107,4 +107,14 @@
            "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);
}
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
@@ -17,6 +17,7 @@
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 @@
        }
        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 @@
    @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 @@
    @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 @@
    @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 @@
        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 @@
    @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 @@
        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 @@
        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 @@
    @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));
    }
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
@@ -126,6 +126,15 @@
                    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())) {
@@ -185,6 +194,119 @@
            return false;
        }
    }
    @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
@@ -464,6 +586,20 @@
    }
    @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
src/main/java/com/genersoft/iot/vmp/utils/JsonUtil.java
New file
@@ -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);
    }
}
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java
@@ -110,7 +110,7 @@
                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 @@
                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 @@
                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 @@
                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());
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 @@
    @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 @@
        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<>();
src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java
@@ -11,17 +11,13 @@
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 @@
    @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)
src/main/resources/all-application.yml
@@ -167,7 +167,7 @@
    senior-sdp: false
    # ä¿å­˜ç§»åŠ¨ä½ç½®åŽ†å²è½¨è¿¹ï¼štrue:保留历史数据,false:仅保留最后的位置(默认)
    save-position-history: false
    # ç‚¹æ’­ç­‰å¾…è¶…æ—¶æ—¶é—´,单位:毫秒
    # ç‚¹æ’­/录像回放 ç­‰å¾…è¶…æ—¶æ—¶é—´,单位:毫秒
    play-timeout: 18000
    # ä¸Šçº§ç‚¹æ’­ç­‰å¾…è¶…æ—¶æ—¶é—´,单位:毫秒
    platform-play-timeout: 60000
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
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}  #用于记录所有的版本变化记录
src/main/resources/db/migration/V2.6.7_20230201__³õʼ»¯.sql
File was renamed from sql/mysql.sql
@@ -39,6 +39,7 @@
                          `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,
web_src/build/utils.js
@@ -47,7 +47,8 @@
    if (options.extract) {
      return ExtractTextPlugin.extract({
        use: loaders,
        fallback: 'vue-style-loader'
        fallback: 'vue-style-loader',
        publicPath: '../../'
      })
    } else {
      return ['vue-style-loader'].concat(loaders)
web_src/config/index.js
@@ -8,8 +8,8 @@
  dev: {
    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    assetsSubDirectory: './static',
    assetsPublicPath: './',
    proxyTable: {
      '/debug': {
        target: 'https://default.wvp-pro.cn:18080',
@@ -61,7 +61,7 @@
    // Paths
    assetsRoot: path.resolve(__dirname, '../../src/main/resources/static/'),
    assetsSubDirectory: './static',
    assetsPublicPath: '/',
    assetsPublicPath: './',
    /**
     * Source Maps
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
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,9 +302,19 @@
      },
      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);
@@ -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,
          }
web_src/src/components/DeviceList.vue
@@ -152,7 +152,7 @@
      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 @@
      }).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 @@
      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 @@
      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 @@
      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) {
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()
        });
web_src/src/components/Login.vue
@@ -81,7 +81,7 @@
      this.$axios({
          method: 'get',
        url:"/api/user/login",
        url:"./api/user/login",
        params: loginParam
      }).then(function (res) {
        window.clearTimeout(timeoutTask)
web_src/src/components/ParentPlatformList.vue
@@ -128,7 +128,7 @@
        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 @@
      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;
web_src/src/components/PushVideoList.vue
@@ -171,7 +171,7 @@
      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 @@
      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 @@
      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 @@
      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 @@
        let that = this;
        that.$axios({
          method: "delete",
          url: "/api/push/batchStop",
          url: "./api/push/batchStop",
          data: {
            gbStreams: this.multipleSelection
          }
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
web_src/src/components/UserManager.vue
@@ -99,7 +99,7 @@
      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 @@
      }).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) => {
web_src/src/components/channelList.vue
@@ -206,7 +206,7 @@
      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 @@
      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 @@
      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 @@
      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 @@
      }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 @@
    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));
web_src/src/components/console.vue
@@ -114,7 +114,7 @@
    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 @@
    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 @@
    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 @@
      this.$axios({
        method: 'get',
        url: `/api/server/system/configInfo`,
        url: `./api/server/system/configInfo`,
      }).then( (res)=> {
        console.log(res)
        if (res.data.code === 0) {
web_src/src/components/dialog/MediaServerEdit.vue
@@ -335,7 +335,7 @@
      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) {
web_src/src/components/dialog/StreamProxyEdit.vue
@@ -195,7 +195,7 @@
      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 @@
      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 @@
      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 @@
      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) {
web_src/src/components/dialog/SyncChannelProgress.vue
@@ -55,7 +55,7 @@
    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) {
web_src/src/components/dialog/addUser.vue
@@ -100,7 +100,7 @@
    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 @@
      this.$axios({
        method: 'get',
        url: "/api/role/all"
        url: "./api/role/all"
      }).then((res) => {
        this.loading = true;
        if (res.data.code === 0) {
web_src/src/components/dialog/catalogEdit.vue
@@ -116,7 +116,7 @@
      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) {
web_src/src/components/dialog/changePassword.vue
@@ -90,7 +90,7 @@
    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
web_src/src/components/dialog/changePasswordForAdmin.vue
@@ -85,7 +85,7 @@
    onSubmit: function () {
      this.$axios({
        method: 'post',
        url:"/api/user/changePasswordForAdmin",
        url:"./api/user/changePasswordForAdmin",
        params: {
          password: this.newPassword,
          userId: this.form.id,
web_src/src/components/dialog/changePushKey.vue
@@ -65,7 +65,7 @@
    onSubmit: function () {
      this.$axios({
        method: 'post',
        url:"/api/user/changePushKey",
        url:"./api/user/changePushKey",
        params: {
          pushKey: this.newPushKey,
          userId: this.form.id,
web_src/src/components/dialog/channelMapInfobox.vue
@@ -44,7 +44,7 @@
      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) {
web_src/src/components/dialog/chooseChannel.vue
@@ -98,7 +98,7 @@
            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
web_src/src/components/dialog/chooseChannelForCatalog.vue
@@ -82,7 +82,7 @@
            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 @@
        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 @@
        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 @@
                  onClick: () => {
                    this.$axios({
                      method:"delete",
                      url:"/api/platform/catalog/relation/del",
                      url:"./api/platform/catalog/relation/del",
                      data: data
                    }).then((res)=>{
                      console.log("移除成功")
web_src/src/components/dialog/chooseChannelForGb.vue
@@ -121,7 +121,7 @@
          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 @@
            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 @@
            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 @@
        }).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 @@
          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,
web_src/src/components/dialog/chooseChannelForStream.vue
@@ -134,7 +134,7 @@
          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 @@
            this.$axios({
              method:"delete",
              url:"/api/gbStream/del",
              url:"./api/gbStream/del",
              data:{
                platformId: this.platformId,
                all: all,
@@ -186,7 +186,7 @@
            this.$axios({
                method: 'get',
                url:`/api/gbStream/list`,
                url:`./api/gbStream/list`,
                params: {
                    page: that.currentPage,
                    count: that.count,
@@ -222,7 +222,7 @@
          }).then(() => {
            this.$axios({
              method:"delete",
              url:"/api/gbStream/del",
              url:"./api/gbStream/del",
              data:{
                platformId: this.platformId,
                gbStreams:  this.multipleSelection,
@@ -242,7 +242,7 @@
          this.getCatalogFromUser((catalogId)=>{
            this.$axios({
              method:"post",
              url:"/api/gbStream/add",
              url:"./api/gbStream/add",
              data:{
                platformId: this.platformId,
                catalogId: catalogId,
web_src/src/components/dialog/deviceEdit.vue
@@ -134,7 +134,7 @@
      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)
web_src/src/components/dialog/getCatalog.vue
@@ -89,7 +89,7 @@
            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 @@
          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) {
web_src/src/components/dialog/importChannel.vue
@@ -60,7 +60,7 @@
      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)=> {
web_src/src/components/dialog/onvifEdit.vue
@@ -81,7 +81,7 @@
      console.log(this.form);
      this.$axios({
        method: 'get',
        url:`api/onvif/rtsp`,
        url:`./api/onvif/rtsp`,
        params: {
          hostname: this.form.hostName,
          timeout: 3000,
web_src/src/components/dialog/platformEdit.vue
@@ -138,7 +138,7 @@
      showDialog: false,
      isLoging: false,
      onSubmit_text: "立即创建",
      saveUrl: "/api/platform/save",
      saveUrl: "./api/platform/save",
      platform: {
        id: null,
@@ -192,7 +192,7 @@
        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 @@
      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;
web_src/src/components/dialog/pushStreamEdit.vue
@@ -109,7 +109,7 @@
      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 @@
      }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 @@
      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) {
web_src/src/components/dialog/queryTrace.vue
@@ -72,7 +72,7 @@
    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}"
      }
web_src/src/components/dialog/recordDownload.vue
@@ -71,7 +71,7 @@
        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 @@
        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 @@
        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 @@
        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,
web_src/src/components/live.vue
@@ -135,7 +135,7 @@
      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;
web_src/src/components/map.vue
@@ -298,7 +298,7 @@
      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) {
web_src/src/components/service/DeviceService.js
@@ -9,7 +9,7 @@
  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 @@
  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 @@
  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 @@
  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 @@
    }
    this.$axios({
      method: 'get',
      url: `/api/device/query/tree/${deviceId}`,
      url: `./api/device/query/tree/${deviceId}`,
      params:{
        page: currentPage,
        count: count,
web_src/src/components/service/MediaServer.js
@@ -9,7 +9,7 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  delete(id, callback) {
    this.$axios({
      method: 'delete',
      url:`/api/server/media_server/delete`,
      url:`./api/server/media_server/delete`,
      params: {
        id: id
      }