648540858
2022-09-01 03d6ad289baddf8feed64ffca5f1b13828bea710
Merge branch 'wvp-28181-2.0'

# Conflicts:
# src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
# src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
# src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
# src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
# src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java
61个文件已修改
5个文件已添加
1 文件已重命名
9个文件已删除
1883 ■■■■ 已修改文件
doc/_content/donation.md 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/_sidebar.md 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/RedisKeyExpirationEventMessageListener.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/security/AnonymousAuthenticationEntryPoint.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/security/UrlTokenHandler.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatformCatch.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/platformKeepaliveExpire/PlatformKeepaliveExpireEvent.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/platformKeepaliveExpire/PlatformKeepaliveExpireEventLister.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/platformNotRegister/PlatformCycleRegisterEvent.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/platformNotRegister/PlatformCycleRegisterEventLister.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/platformNotRegister/PlatformNotRegisterEvent.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/platformNotRegister/PlatformNotRegisterEventLister.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeHandlerTask.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java 54 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/utils/HeaderUtils.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZlmHttpHookSubscribe.java 55 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMKeepliveTimeoutListener.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java 61 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java 232 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/RedisAlarmMsgListener.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/RedisGbPlayMsgListener.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/dao/dto/PlatformRegisterInfo.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/utils/SpringBeanFactory.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/platform/PlatformController.java 41 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/streamPush/StreamPushController.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/all-application.yml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/logback-spring-local.xml 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/CloudRecord.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/CloudRecordDetail.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/Login.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/MediaServerEdit.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/StreamProxyEdit.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/devicePlayer.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/platformEdit.vue 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/pushStreamEdit.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/_content/donation.md
File was deleted
doc/_sidebar.md
@@ -29,5 +29,4 @@
  - [设备注册不上来的解决办法](_content/qa/regiser_error.md)
  - [点播超时/报错的解决办法](_content/qa/play_error.md)
* [**免责声明**](_content/disclaimers.md)
* [**捐赠**](_content/donation.md)
* [**关于本文档**](_content/about_doc.md)
pom.xml
@@ -11,7 +11,7 @@
    <groupId>com.genersoft</groupId>
    <artifactId>wvp-pro</artifactId>
    <version>2.3.1</version>
    <version>2.3.2</version>
    <name>web video platform</name>
    <description>国标28181视频平台</description>
src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java
@@ -31,6 +31,8 @@
    private String rtsp;
    private String rtsps;
    private String rtc;
    private String rtcs;
    private String mediaServerId;
    private Object tracks;
    private String startTime;
@@ -302,4 +304,12 @@
    public void setIp(String ip) {
        this.ip = ip;
    }
    public String getRtcs() {
        return rtcs;
    }
    public void setRtcs(String rtcs) {
        this.rtcs = rtcs;
    }
}
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
@@ -14,8 +14,6 @@
    public static final String MEDIA_SERVER_PREFIX = "VMP_MEDIA_SERVER_";
    public static final String MEDIA_SERVER_KEEPALIVE_PREFIX = "VMP_MEDIA_SERVER_KEEPALIVE_";
    public static final String MEDIA_SERVERS_ONLINE_PREFIX = "VMP_MEDIA_ONLINE_SERVERS_";
    public static final String MEDIA_STREAM_PREFIX = "VMP_MEDIA_STREAM";
src/main/java/com/genersoft/iot/vmp/conf/RedisKeyExpirationEventMessageListener.java
File was deleted
src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java
@@ -4,6 +4,7 @@
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatformCatch;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.service.IPlatformService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import org.springframework.beans.factory.annotation.Autowired;
@@ -15,6 +16,7 @@
/**
 * 系统启动时控制上级平台重新注册
 * @author lin
 */
@Component
@Order(value=3)
@@ -27,7 +29,7 @@
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private EventPublisher publisher;
    private IPlatformService platformService;
    @Autowired
    private ISIPCommanderForPlatform sipCommanderForPlatform;
@@ -35,33 +37,26 @@
    @Override
    public void run(String... args) throws Exception {
        // 设置所有平台离线
        storager.outlineForAllParentPlatform();
        // 清理所有平台注册缓存
        redisCatchStorage.cleanPlatformRegisterInfos();
        // 停止所有推流
//        zlmrtpServerFactory.closeAllSendRtpStream();
        // 获取所有启用的平台
        List<ParentPlatform> parentPlatforms = storager.queryEnableParentPlatformList(true);
        for (ParentPlatform parentPlatform : parentPlatforms) {
            redisCatchStorage.updatePlatformRegister(parentPlatform);
            redisCatchStorage.updatePlatformKeepalive(parentPlatform);
            // 更新缓存
            ParentPlatformCatch parentPlatformCatch = new ParentPlatformCatch();
            parentPlatformCatch.setParentPlatform(parentPlatform);
            parentPlatformCatch.setId(parentPlatform.getServerGBId());
            redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
            if (parentPlatform.isStatus()) {
                // 设置所有平台离线
                platformService.offline(parentPlatform);
                // 取消订阅
                sipCommanderForPlatform.unregister(parentPlatform, null, (eventResult)->{
                    platformService.login(parentPlatform);
                });
            }else {
                platformService.login(parentPlatform);
            }
            // 取消订阅
            sipCommanderForPlatform.unregister(parentPlatform, null, (eventResult)->{
                // 发送平台未注册消息
                publisher.platformNotRegisterEventPublish(parentPlatform.getServerGBId());
            });
        }
    }
}
src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
@@ -31,8 +31,6 @@
    private Boolean logInDatebase = Boolean.TRUE;
    private Boolean redisConfig = Boolean.TRUE;
    private String serverId = "000000";
    private String thirdPartyGBIdReg = "[\\s\\S]*";
@@ -121,14 +119,6 @@
    public void setThirdPartyGBIdReg(String thirdPartyGBIdReg) {
        this.thirdPartyGBIdReg = thirdPartyGBIdReg;
    }
    public Boolean getRedisConfig() {
        return redisConfig;
    }
    public void setRedisConfig(Boolean redisConfig) {
        this.redisConfig = redisConfig;
    }
    public Boolean getRecordSip() {
src/main/java/com/genersoft/iot/vmp/conf/security/AnonymousAuthenticationEntryPoint.java
@@ -1,6 +1,8 @@
package com.genersoft.iot.vmp.conf.security;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import org.apache.poi.hssf.eventmodel.ERFListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
@@ -28,8 +30,8 @@
        response.setHeader("Access-Control-Allow-Headers", "token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
        response.setHeader("Content-type", "application/json;charset=UTF-8");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", "-1");
        jsonObject.put("msg", "请登录后重新请求");
        jsonObject.put("code", ErrorCode.ERROR401.getCode());
        jsonObject.put("msg", ErrorCode.ERROR401.getMsg());
        String logUri = "api/user/login";
        if (request.getRequestURI().contains(logUri)){
            jsonObject.put("msg", e.getMessage());
src/main/java/com/genersoft/iot/vmp/conf/security/UrlTokenHandler.java
New file
@@ -0,0 +1,24 @@
package com.genersoft.iot.vmp.conf.security;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.SessionCookieConfig;
import javax.servlet.SessionTrackingMode;
import java.util.Collections;
public class UrlTokenHandler extends SpringBootServletInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        servletContext.setSessionTrackingModes(
                Collections.singleton(SessionTrackingMode.COOKIE)
        );
        SessionCookieConfig sessionCookieConfig = servletContext.getSessionCookieConfig();
        sessionCookieConfig.setHttpOnly(true);
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
@@ -10,14 +10,10 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import javax.sip.*;
import java.util.Properties;
import java.util.TooManyListenersException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Configuration
public class SipLayer{
@@ -52,7 +48,9 @@
         * 完整配置参考 gov.nist.javax.sip.SipStackImpl,需要下载源码
         * gov/nist/javax/sip/SipStackImpl.class
         */
        properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "true");
        if (logger.isDebugEnabled()) {
            properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "false");
        }
        // 接收所有notify请求,即使没有订阅
        properties.setProperty("gov.nist.javax.sip.DELIVER_UNSOLICITED_NOTIFY", "true");
        // 为_NULL _对话框传递_终止的_事件
@@ -63,13 +61,13 @@
        properties.setProperty("gov.nist.javax.sip.RELIABLE_CONNECTION_KEEP_ALIVE_TIMEOUT", "60");
        /**
         * sip_server_log.log 和 sip_debug_log.log public static final int TRACE_NONE =
         * 0; public static final int TRACE_MESSAGES = 16; public static final int
         * TRACE_EXCEPTION = 17; public static final int TRACE_DEBUG = 32;
         * sip_server_log.log 和 sip_debug_log.log ERROR, INFO, WARNING, OFF, DEBUG, TRACE
         */
        properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "0");
        properties.setProperty("gov.nist.javax.sip.SERVER_LOG", "sip_server_log");
        properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "sip_debug_log");
        properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "ERROR");
//        if (logger.isDebugEnabled()) {
//            properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "DEBUG");
//        }
        sipStack = (SipStackImpl) sipFactory.createSipStack(properties);
        return sipStack;
src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java
@@ -84,7 +84,7 @@
     * 注册周期 (秒)
     */
    @Schema(description = "注册周期 (秒)")
    private String expires;
    private int expires;
    /**
     * 心跳周期(秒)
@@ -286,11 +286,11 @@
        this.password = password;
    }
    public String getExpires() {
    public int getExpires() {
        return expires;
    }
    public void setExpires(String expires) {
    public void setExpires(int expires) {
        this.expires = expires;
    }
src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatformCatch.java
@@ -4,7 +4,9 @@
    private String id;
    // 心跳未回复次数
    /**
     * 心跳未回复次数
     */
    private int keepAliveReply;
    // 注册未回复次数
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java
@@ -14,6 +14,9 @@
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
 * @author lin
 */
@Component
public class SubscribeHolder {
src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java
@@ -2,9 +2,6 @@
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.event.device.RequestTimeoutEvent;
import com.genersoft.iot.vmp.gb28181.event.platformKeepaliveExpire.PlatformKeepaliveExpireEvent;
import com.genersoft.iot.vmp.gb28181.event.platformNotRegister.PlatformCycleRegisterEvent;
import com.genersoft.iot.vmp.gb28181.event.platformNotRegister.PlatformNotRegisterEvent;
import com.genersoft.iot.vmp.gb28181.event.record.RecordEndEvent;
import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
import com.genersoft.iot.vmp.media.zlm.event.ZLMOfflineEvent;
@@ -31,36 +28,6 @@
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;
    /**
     * 平台心跳到期事件
     * @param platformGbId
     */
    public void platformKeepaliveExpireEventPublish(String platformGbId){
        PlatformKeepaliveExpireEvent platformKeepaliveExpireEvent = new PlatformKeepaliveExpireEvent(this);
        platformKeepaliveExpireEvent.setPlatformGbID(platformGbId);
        applicationEventPublisher.publishEvent(platformKeepaliveExpireEvent);
    }
    /**
     * 平台未注册事件
     * @param platformGbId
     */
    public void platformNotRegisterEventPublish(String platformGbId){
        PlatformNotRegisterEvent platformNotRegisterEvent = new PlatformNotRegisterEvent(this);
        platformNotRegisterEvent.setPlatformGbID(platformGbId);
        applicationEventPublisher.publishEvent(platformNotRegisterEvent);
    }
    /**
     * 平台周期注册事件
     * @param paltformGbId
     */
    public void platformRegisterCycleEventPublish(String paltformGbId) {
        PlatformCycleRegisterEvent platformCycleRegisterEvent = new PlatformCycleRegisterEvent(this);
        platformCycleRegisterEvent.setPlatformGbID(paltformGbId);
        applicationEventPublisher.publishEvent(platformCycleRegisterEvent);
    }
    
    /**
     * 设备报警事件
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java
@@ -59,9 +59,25 @@
        void response(EventResult eventResult);
    }
    /**
     *
     */
    public enum EventResultType{
        // 超时
        timeout,
        // 回复
        response,
        // 事务已结束
        transactionTerminated,
        // 会话已结束
        dialogTerminated,
        // 设备未找到
        deviceNotFoundEvent
    }
    public static class EventResult<EventObject>{
        public int statusCode;
        public String type;
        public EventResultType type;
        public String msg;
        public String callId;
        public Dialog dialog;
@@ -76,7 +92,7 @@
                ResponseEvent responseEvent = (ResponseEvent)event;
                Response response = responseEvent.getResponse();
                this.dialog = responseEvent.getDialog();
                this.type = "response";
                this.type = EventResultType.response;
                if (response != null) {
                    this.msg = response.getReasonPhrase();
                    this.statusCode = response.getStatusCode();
@@ -85,28 +101,28 @@
            }else if (event instanceof TimeoutEvent) {
                TimeoutEvent timeoutEvent = (TimeoutEvent)event;
                this.type = "timeout";
                this.type = EventResultType.timeout;
                this.msg = "消息超时未回复";
                this.statusCode = -1024;
                this.dialog = timeoutEvent.getClientTransaction().getDialog();
                this.callId = this.dialog != null?timeoutEvent.getClientTransaction().getDialog().getCallId().getCallId(): null;
            }else if (event instanceof TransactionTerminatedEvent) {
                TransactionTerminatedEvent transactionTerminatedEvent = (TransactionTerminatedEvent)event;
                this.type = "transactionTerminated";
                this.type = EventResultType.transactionTerminated;
                this.msg = "事务已结束";
                this.statusCode = -1024;
                this.callId = transactionTerminatedEvent.getClientTransaction().getDialog().getCallId().getCallId();
                this.dialog = transactionTerminatedEvent.getClientTransaction().getDialog();
            }else if (event instanceof DialogTerminatedEvent) {
                DialogTerminatedEvent dialogTerminatedEvent = (DialogTerminatedEvent)event;
                this.type = "dialogTerminated";
                this.type = EventResultType.dialogTerminated;
                this.msg = "会话已结束";
                this.statusCode = -1024;
                this.callId = dialogTerminatedEvent.getDialog().getCallId().getCallId();
                this.dialog = dialogTerminatedEvent.getDialog();
            }else if (event instanceof DeviceNotFoundEvent) {
                DeviceNotFoundEvent deviceNotFoundEvent = (DeviceNotFoundEvent)event;
                this.type = "deviceNotFoundEvent";
                this.type = EventResultType.deviceNotFoundEvent;
                this.msg = "设备未找到";
                this.statusCode = -1024;
                this.dialog = deviceNotFoundEvent.getDialog();
src/main/java/com/genersoft/iot/vmp/gb28181/event/platformKeepaliveExpire/PlatformKeepaliveExpireEvent.java
File was deleted
src/main/java/com/genersoft/iot/vmp/gb28181/event/platformKeepaliveExpire/PlatformKeepaliveExpireEventLister.java
File was deleted
src/main/java/com/genersoft/iot/vmp/gb28181/event/platformNotRegister/PlatformCycleRegisterEvent.java
File was deleted
src/main/java/com/genersoft/iot/vmp/gb28181/event/platformNotRegister/PlatformCycleRegisterEventLister.java
File was deleted
src/main/java/com/genersoft/iot/vmp/gb28181/event/platformNotRegister/PlatformNotRegisterEvent.java
File was deleted
src/main/java/com/genersoft/iot/vmp/gb28181/event/platformNotRegister/PlatformNotRegisterEventLister.java
File was deleted
src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java
@@ -30,22 +30,9 @@
    @Autowired
    private IVideoManagerStorage storager;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private IMediaServerService mediaServerService;
    @Autowired
    private SIPCommanderFroPlatform sipCommanderFroPlatform;
    @Autowired
    private ZLMRTPServerFactory zlmrtpServerFactory;
    @Autowired
    private SipConfig config;
    @Autowired
    private UserSetting userSetting;
    @Autowired
    private IGbStreamService gbStreamService;
src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeHandlerTask.java
@@ -60,7 +60,6 @@
            // TODO 暂时只处理视频流的回复,后续增加对国标设备的支持
            List<DeviceChannel> gbStreams = storager.queryGbStreamListInPlatform(platform.getServerGBId());
            if (gbStreams.size() == 0) {
                logger.info("发送订阅时发现平台已经没有关联的直播流:{}", platform.getServerGBId());
                return;
            }
            for (DeviceChannel deviceChannel : gbStreams) {
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java
@@ -71,7 +71,6 @@
    @Override
    @Async
    public void processRequest(RequestEvent requestEvent) {
        logger.debug("\n收到请求:\n{}", requestEvent.getRequest());
        String method = requestEvent.getRequest().getMethod();
        ISIPRequestProcessor sipRequestProcessor = requestProcessorMap.get(method);
        if (sipRequestProcessor == null) {
@@ -90,7 +89,6 @@
    @Async
    public void processResponse(ResponseEvent responseEvent) {
        Response response = responseEvent.getResponse();
        logger.debug("\n收到响应:\n{}", responseEvent.getResponse());
        int status = response.getStatusCode();
        if (((status >= 200) && (status < 300)) || status == Response.UNAUTHORIZED) { // Success!
@@ -114,7 +112,7 @@
        } else if ((status >= 100) && (status < 200)) {
            // 增加其它无需回复的响应,如101、180等
        } else {
            logger.warn("接收到失败的response响应!status:" + status + ",message:" + response.getReasonPhrase()/* .getContent().toString()*/);
            logger.warn("接收到失败的response响应!status:" + status + ",message:" + response.getReasonPhrase());
            if (responseEvent.getResponse() != null && sipSubscribe.getErrorSubscribesSize() > 0 ) {
                CallIdHeader callIdHeader = (CallIdHeader)responseEvent.getResponse().getHeader(CallIdHeader.NAME);
                if (callIdHeader != null) {
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
@@ -3,7 +3,7 @@
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
import gov.nist.javax.sip.message.SIPRequest;
@@ -98,7 +98,7 @@
     * @param device  视频设备
     * @param channelId  预览通道
     */
    void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent);
    void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent);
    /**
     * 请求回放视频流
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
@@ -15,7 +15,7 @@
     * @return
     */
    boolean register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent);
    boolean register(ParentPlatform parentPlatform, String callId, WWWAuthenticateHeader www, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean registerAgain);
    boolean register(ParentPlatform parentPlatform, String callId, WWWAuthenticateHeader www, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister);
    /**
     * 向上级平台注销
@@ -30,7 +30,7 @@
     * @param parentPlatform
     * @return callId(作为接受回复的判定)
     */
    String keepalive(ParentPlatform parentPlatform);
    String keepalive(ParentPlatform parentPlatform,SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent);
    /**
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java
@@ -3,6 +3,7 @@
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo;
import com.genersoft.iot.vmp.gb28181.utils.HeaderUtils;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import gov.nist.javax.sip.message.MessageFactoryImpl;
import org.springframework.beans.factory.annotation.Autowired;
@@ -75,7 +76,7 @@
    }
    public Request createRegisterRequest(@NotNull ParentPlatform platform, long CSeq, String fromTag, String viaTag, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
    public Request createRegisterRequest(@NotNull ParentPlatform platform, long CSeq, String fromTag, String viaTag, CallIdHeader callIdHeader, boolean isRegister) throws ParseException, InvalidArgumentException, PeerUnavailableException {
        Request request = null;
        String sipAddress = sipConfig.getIp() + ":" + sipConfig.getPort();
        //请求行
@@ -109,18 +110,20 @@
                .createSipURI(platform.getDeviceGBId(), sipAddress));
        request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
        ExpiresHeader expires = sipFactory.createHeaderFactory().createExpiresHeader(Integer.parseInt(platform.getExpires()));
        ExpiresHeader expires = sipFactory.createHeaderFactory().createExpiresHeader(isRegister ? platform.getExpires() : 0);
        request.addHeader(expires);
        UserAgentHeader userAgentHeader = HeaderUtils.createUserAgentHeader(sipFactory);
        request.addHeader(userAgentHeader);
        return request;
    }
    public Request createRegisterRequest(@NotNull ParentPlatform parentPlatform, String fromTag, String viaTag,
                                         String callId, WWWAuthenticateHeader www , CallIdHeader callIdHeader) throws ParseException, PeerUnavailableException, InvalidArgumentException {
                                         String callId, WWWAuthenticateHeader www , CallIdHeader callIdHeader, boolean isRegister) throws ParseException, PeerUnavailableException, InvalidArgumentException {
        Request registerRequest = createRegisterRequest(parentPlatform, redisCatchStorage.getCSEQ(), fromTag, viaTag, callIdHeader);
        Request registerRequest = createRegisterRequest(parentPlatform, redisCatchStorage.getCSEQ(), fromTag, viaTag, callIdHeader, isRegister);
        SipURI requestURI = sipFactory.createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP() + ":" + parentPlatform.getServerPort());
        if (www == null) {
            AuthorizationHeader authorizationHeader = sipFactory.createHeaderFactory().createAuthorizationHeader("Digest");
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java
@@ -12,6 +12,7 @@
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.utils.HeaderUtils;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import gov.nist.javax.sip.SipProviderImpl;
import gov.nist.javax.sip.SipStackImpl;
@@ -266,15 +267,7 @@
        Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory()
                .createSipURI(sipConfig.getId(), sipConfig.getIp() + ":" + sipConfig.getPort()));
        infoRequest.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
        List<String> agentParam = new ArrayList<>();
        agentParam.add("wvp-pro");
        // TODO 添加版本信息以及日期
        UserAgentHeader userAgentHeader = null;
        try {
            userAgentHeader = sipFactory.createHeaderFactory().createUserAgentHeader(agentParam);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
        UserAgentHeader userAgentHeader = HeaderUtils.createUserAgentHeader(sipFactory);
        infoRequest.addHeader(userAgentHeader);
        ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application",
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -10,12 +10,12 @@
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
import com.genersoft.iot.vmp.gb28181.utils.HeaderUtils;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
import com.genersoft.iot.vmp.media.zlm.dto.HookType;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
@@ -33,19 +33,15 @@
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import javax.sip.*;
import javax.sip.address.Address;
import javax.sip.address.SipURI;
import javax.sip.address.URI;
import javax.sip.header.*;
import javax.sip.message.Request;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
/**    
 * @description:设备能力接口,用于定义设备的控制、查询能力   
@@ -88,7 +84,7 @@
    private UserSetting userSetting;
    @Autowired
    private ZLMHttpHookSubscribe subscribe;
    private ZlmHttpHookSubscribe subscribe;
    @Autowired
    private SipSubscribe sipSubscribe;
@@ -351,7 +347,7 @@
    */
    @Override
    public void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
                              ZLMHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) {
                              ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) {
        String stream = ssrcInfo.getStream();
        try {
            if (device == null) {
@@ -640,7 +636,7 @@
                        hookEvent.call(new InviteStreamInfo(mediaServerItem, json, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
                        subscribe.removeSubscribe(hookSubscribe);
                        hookSubscribe.getContent().put("regist", false);
                        hookSubscribe.getContent().put("schema", "rtmp");
                        hookSubscribe.getContent().put("schema", "rtsp");
                        // 添加流注销的订阅,注销了后向设备发送bye
                        subscribe.addSubscribe(hookSubscribe,
                                (MediaServerItem mediaServerItemForEnd, JSONObject jsonForEnd)->{
@@ -780,15 +776,7 @@
            // 增加Contact header
            Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getIp()+":"+sipConfig.getPort()));
            byeRequest.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
            List<String> agentParam = new ArrayList<>();
            agentParam.add("wvp-pro");
            // TODO 添加版本信息以及日期
            UserAgentHeader userAgentHeader = null;
            try {
                userAgentHeader = sipFactory.createHeaderFactory().createUserAgentHeader(agentParam);
            } catch (ParseException e) {
                throw new RuntimeException(e);
            }
            UserAgentHeader userAgentHeader = HeaderUtils.createUserAgentHeader(sipFactory);
            byeRequest.addHeader(userAgentHeader);
            ClientTransaction clientTransaction = null;
            if("TCP".equals(protocol)) {
@@ -1680,14 +1668,11 @@
            clientTransaction = udpSipProvider.getNewClientTransaction(request);
        }
        if (request.getHeader(UserAgentHeader.NAME) == null) {
            List<String> agentParam = new ArrayList<>();
            agentParam.add("wvp-pro");
            // TODO 添加版本信息以及日期
            UserAgentHeader userAgentHeader = null;
            try {
                userAgentHeader = sipFactory.createHeaderFactory().createUserAgentHeader(agentParam);
                userAgentHeader = HeaderUtils.createUserAgentHeader(sipFactory);
            } catch (ParseException e) {
                throw new RuntimeException(e);
                logger.error("添加UserAgentHeader失败", e);
            }
            request.addHeader(userAgentHeader);
        }
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
@@ -4,6 +4,8 @@
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderPlarformProvider;
import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
@@ -75,28 +77,21 @@
    @Override
    public boolean register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) {
        return register(parentPlatform, null, null, errorEvent, okEvent, false);
        return register(parentPlatform, null, null, errorEvent, okEvent, false, true);
    }
    @Override
    public boolean unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) {
        ParentPlatformCatch parentPlatformCatch = redisCatchStorage.queryPlatformCatchInfo(parentPlatform.getServerGBId());
        parentPlatform.setExpires("0");
        if (parentPlatformCatch != null) {
            parentPlatformCatch.setParentPlatform(parentPlatform);
            redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
        }
        return register(parentPlatform, null, null, errorEvent, okEvent, false);
        return register(parentPlatform, null, null, errorEvent, okEvent, false, false);
    }
    @Override
    public boolean register(ParentPlatform parentPlatform, @Nullable String callId, @Nullable WWWAuthenticateHeader www,
                            SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean registerAgain) {
                            SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister) {
        try {
            Request request;
            String tm = Long.toString(System.currentTimeMillis());
            if (!registerAgain ) {
                //        //callid
                CallIdHeader callIdHeader = null;
                if(parentPlatform.getTransport().equals("TCP")) {
                    callIdHeader = tcpSipProvider.getNewCallId();
@@ -107,10 +102,10 @@
                request = headerProviderPlarformProvider.createRegisterRequest(parentPlatform,
                        redisCatchStorage.getCSEQ(), "FromRegister" + tm,
                        "z9hG4bK-" + UUID.randomUUID().toString().replace("-", ""), callIdHeader);
                        "z9hG4bK-" + UUID.randomUUID().toString().replace("-", ""), callIdHeader, isRegister);
                // 将 callid 写入缓存, 等注册成功可以更新状态
                String callIdFromHeader = callIdHeader.getCallId();
                redisCatchStorage.updatePlatformRegisterInfo(callIdFromHeader, parentPlatform.getServerGBId());
                redisCatchStorage.updatePlatformRegisterInfo(callIdFromHeader, PlatformRegisterInfo.getInstance(parentPlatform.getServerGBId(), isRegister));
                sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (event)->{
                    if (event != null) {
@@ -127,7 +122,7 @@
            }else {
                CallIdHeader callIdHeader = parentPlatform.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
                        : udpSipProvider.getNewCallId();
                request = headerProviderPlarformProvider.createRegisterRequest(parentPlatform, "FromRegister" + tm, null, callId, www, callIdHeader);
                request = headerProviderPlarformProvider.createRegisterRequest(parentPlatform, "FromRegister" + tm, null, callId, www, callIdHeader, isRegister);
            }
            transmitRequest(parentPlatform, request, null, okEvent);
@@ -145,7 +140,7 @@
    }
    @Override
    public String keepalive(ParentPlatform parentPlatform) {
    public String keepalive(ParentPlatform parentPlatform,SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) {
        String callId = null;
        try {
            String characterSet = parentPlatform.getCharacterSet();
@@ -168,7 +163,7 @@
                    UUID.randomUUID().toString().replace("-", ""),
                    null,
                    callIdHeader);
            transmitRequest(parentPlatform, request);
            transmitRequest(parentPlatform, request, errorEvent, okEvent);
            callId = callIdHeader.getCallId();
        } catch (ParseException | InvalidArgumentException | SipException e) {
            e.printStackTrace();
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java
@@ -59,6 +59,9 @@
    public ServerTransaction getServerTransaction(RequestEvent evt) {
        Request request = evt.getRequest();
        ServerTransaction serverTransaction = evt.getServerTransaction();
        if (serverTransaction != null) {
            System.out.println(serverTransaction.getState().toString());
        }
        // 判断TCP还是UDP
        boolean isTcp = false;
        ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
@@ -86,6 +89,8 @@
                logger.error(e.getMessage());
            } catch (TransactionUnavailableException e) {
                logger.error(e.getMessage());
            }finally {
            }
        }
        return serverTransaction;
@@ -182,6 +187,10 @@
                sipFactory.createAddressFactory().createSipURI(sipURI.getUser(),  sipURI.getHost()+":"+sipURI.getPort()
                ));
        response.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
        ServerTransaction serverTransaction = getServerTransaction(evt);
        if (serverTransaction == null) {
        }
        getServerTransaction(evt).sendResponse(response);
    }
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
@@ -4,12 +4,14 @@
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
@@ -66,7 +68,7 @@
    private IMediaServerService mediaServerService;
    @Autowired
    private ZLMHttpHookSubscribe subscribe;
    private ZlmHttpHookSubscribe subscribe;
    @Autowired
    private DynamicTask dynamicTask;
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
@@ -19,7 +19,7 @@
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMMediaListManager;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
@@ -50,7 +50,6 @@
import javax.sdp.*;
import javax.sip.*;
import javax.sip.address.SipURI;
import javax.sip.header.CallIdHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;
@@ -337,7 +336,7 @@
                    Long finalStartTime = startTime;
                    Long finalStopTime = stopTime;
                    ZLMHttpHookSubscribe.Event hookEvent = (mediaServerItemInUSe, responseJSON) -> {
                    ZlmHttpHookSubscribe.Event hookEvent = (mediaServerItemInUSe, responseJSON) -> {
                        String app = responseJSON.getString("app");
                        String stream = responseJSON.getString("stream");
                        logger.info("[上级点播]下级已经开始推流。 回复200OK(SDP), {}/{}", app, stream);
@@ -440,6 +439,7 @@
                                streamId = String.format("%s_%s", device.getDeviceId(), channelId);
                            }
                            SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, device.isSsrcCheck(), false);
                            logger.info(JSONObject.toJSONString(ssrcInfo));
                            sendRtpItem.setStreamId(ssrcInfo.getStream());
                            // 写入redis, 超时时回复
                            redisCatchStorage.updateSendRTPSever(sendRtpItem);
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java
@@ -180,7 +180,6 @@
    private void processNotifyCatalogList(RequestEvent evt, Element rootElement) throws SipException {
        System.out.println(evt.getRequest().toString());
        String platformId = SipUtils.getUserIdFromFromHeader(evt.getRequest());
        String deviceId = XmlUtil.getText(rootElement, "DeviceID");
        ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java
@@ -164,7 +164,7 @@
            }
        }
        if (channelId.equals(sipConfig.getId())) {
        if ("7".equals(deviceAlarm.getAlarmMethod()) ) {
            // 发送给平台的报警信息。 发送redis通知
            AlarmChannelMessage alarmChannelMessage = new AlarmChannelMessage();
            alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod()));
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java
@@ -6,6 +6,7 @@
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
import com.genersoft.iot.vmp.gb28181.transmit.event.response.SIPResponseProcessorAbstract;
import com.genersoft.iot.vmp.gb28181.utils.HeaderUtils;
import gov.nist.javax.sip.ResponseEventExt;
import gov.nist.javax.sip.message.SIPResponse;
import gov.nist.javax.sip.stack.SIPDialog;
@@ -103,15 +104,7 @@
                }
                requestURI.setPort(event.getRemotePort());
                reqAck.setRequestURI(requestURI);
                List<String> agentParam = new ArrayList<>();
                agentParam.add("wvp-pro");
                // TODO 添加版本信息以及日期
                UserAgentHeader userAgentHeader = null;
                try {
                    userAgentHeader = sipFactory.createHeaderFactory().createUserAgentHeader(agentParam);
                } catch (ParseException e) {
                    throw new RuntimeException(e);
                }
                UserAgentHeader userAgentHeader = HeaderUtils.createUserAgentHeader(sipFactory);
                reqAck.addHeader(userAgentHeader);
                Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getIp()+":"+sipConfig.getPort()));
                reqAck.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java
@@ -6,8 +6,10 @@
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.gb28181.transmit.event.response.SIPResponseProcessorAbstract;
import com.genersoft.iot.vmp.service.IPlatformService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -44,6 +46,9 @@
    @Autowired
    private SubscribeHolder subscribeHolder;
    @Autowired
    private IPlatformService platformService;
    @Override
    public void afterPropertiesSet() throws Exception {
        // 添加消息处理的订阅
@@ -60,48 +65,39 @@
        Response response = evt.getResponse();
        CallIdHeader callIdHeader = (CallIdHeader) response.getHeader(CallIdHeader.NAME);
        String callId = callIdHeader.getCallId();
        String platformGBId = redisCatchStorage.queryPlatformRegisterInfo(callId);
        if (platformGBId == null) {
            logger.info(String.format("未找到callId: %s 的注册/注销平台id", callId ));
        PlatformRegisterInfo platformRegisterInfo = redisCatchStorage.queryPlatformRegisterInfo(callId);
        if (platformRegisterInfo == null) {
            logger.info(String.format("[国标级联]未找到callId: %s 的注册/注销平台id", callId ));
            return;
        }
        ParentPlatformCatch parentPlatformCatch = redisCatchStorage.queryPlatformCatchInfo(platformGBId);
        ParentPlatformCatch parentPlatformCatch = redisCatchStorage.queryPlatformCatchInfo(platformRegisterInfo.getPlatformId());
        if (parentPlatformCatch == null) {
            logger.warn(String.format("[收到注册/注销%S请求]平台:%s,但是平台缓存信息未查询到!!!", response.getStatusCode(),platformGBId));
            logger.warn(String.format("[国标级联]收到注册/注销%S请求,平台:%s,但是平台缓存信息未查询到!!!", response.getStatusCode(),platformRegisterInfo.getPlatformId()));
            return;
        }
        String action = parentPlatformCatch.getParentPlatform().getExpires().equals("0") ? "注销" : "注册";
        logger.info(String.format("[%s %S响应]%s ", action, response.getStatusCode(), platformGBId ));
        String action = platformRegisterInfo.isRegister() ? "注册" : "注销";
        logger.info(String.format("[国标级联]%s %S响应,%s ", action, response.getStatusCode(), platformRegisterInfo.getPlatformId() ));
        ParentPlatform parentPlatform = parentPlatformCatch.getParentPlatform();
        if (parentPlatform == null) {
            logger.warn(String.format("收到 %s %s的%S请求, 但是平台信息未查询到!!!", platformGBId, action, response.getStatusCode()));
            logger.warn(String.format("[国标级联]收到 %s %s的%S请求, 但是平台信息未查询到!!!", platformRegisterInfo.getPlatformId(), action, response.getStatusCode()));
            return;
        }
        if (response.getStatusCode() == 401) {
        if (response.getStatusCode() == Response.UNAUTHORIZED) {
            WWWAuthenticateHeader www = (WWWAuthenticateHeader)response.getHeader(WWWAuthenticateHeader.NAME);
            sipCommanderForPlatform.register(parentPlatform, callId, www, null, null, true);
        }else if (response.getStatusCode() == 200){
            // 注册/注销成功
            logger.info(String.format("%s %s成功", platformGBId, action));
            sipCommanderForPlatform.register(parentPlatform, callId, www, null, null, true, platformRegisterInfo.isRegister());
        }else if (response.getStatusCode() == Response.OK){
            if (platformRegisterInfo.isRegister()) {
                platformService.online(parentPlatform);
            }else {
                platformService.offline(parentPlatform);
            }
            // 注册/注销成功移除缓存的信息
            redisCatchStorage.delPlatformRegisterInfo(callId);
            redisCatchStorage.delPlatformCatchInfo(platformGBId);
            // 取回Expires设置,避免注销过程中被置为0
            ParentPlatform parentPlatformTmp = storager.queryParentPlatByServerGBId(platformGBId);
            if (parentPlatformTmp != null) {
                parentPlatformTmp.setStatus("注册".equals(action));
                redisCatchStorage.updatePlatformRegister(parentPlatformTmp);
                redisCatchStorage.updatePlatformKeepalive(parentPlatformTmp);
                parentPlatformCatch.setParentPlatform(parentPlatformTmp);
            }
            redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
            storager.updateParentPlatformStatus(platformGBId, "注册".equals(action));
            if ("注销".equals(action)) {
                subscribeHolder.removeCatalogSubscribe(platformGBId);
                subscribeHolder.removeMobilePositionSubscribe(platformGBId);
            }
        }
    }
src/main/java/com/genersoft/iot/vmp/gb28181/utils/HeaderUtils.java
New file
@@ -0,0 +1,22 @@
package com.genersoft.iot.vmp.gb28181.utils;
import javax.sip.PeerUnavailableException;
import javax.sip.SipFactory;
import javax.sip.header.UserAgentHeader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
/**
 * 生成header的工具类
 * @author lin
 */
public class HeaderUtils {
    public static UserAgentHeader createUserAgentHeader(SipFactory sipFactory) throws PeerUnavailableException, ParseException {
        List<String> agentParam = new ArrayList<>();
        agentParam.add("WVP PRO");
        // TODO 添加版本信息以及日期
        return sipFactory.createHeaderFactory().createUserAgentHeader(agentParam);
    }
}
src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java
@@ -50,7 +50,7 @@
        if (mediaServerItem == null) {
            return null;
        }
        if (ObjectUtils.isEmpty(mediaServerItem.getRecordAssistPort())) {
        if (mediaServerItem.getRecordAssistPort() > 0) {
            logger.warn("未启用Assist服务");
            return null;
        }
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
@@ -7,12 +7,10 @@
import com.alibaba.fastjson.JSON;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.bean.GbStream;
import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
import com.genersoft.iot.vmp.media.zlm.dto.*;
import com.genersoft.iot.vmp.service.*;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
@@ -20,10 +18,11 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -48,6 +47,9 @@
    @Autowired
    private SIPCommander cmder;
    @Autowired
    private SIPCommanderFroPlatform commanderFroPlatform;
    @Autowired
    private IPlayService playService;
@@ -77,7 +79,7 @@
     private ZLMMediaListManager zlmMediaListManager;
    @Autowired
    private ZLMHttpHookSubscribe subscribe;
    private ZlmHttpHookSubscribe subscribe;
    @Autowired
    private UserSetting userSetting;
@@ -91,6 +93,10 @@
    @Autowired
    private AssistRESTfulUtils assistRESTfulUtils;
    @Qualifier("taskExecutor")
    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;
    /**
     * 服务器定时上报时间,上报间隔可配置,默认10s上报一次
     *
@@ -101,9 +107,9 @@
        logger.info("[ ZLM HOOK ] on_server_keepalive API调用,参数:" + json.toString());
        String mediaServerId = json.getString("mediaServerId");
        List<ZLMHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_keepalive);
        List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_keepalive);
        if (subscribes != null  && subscribes.size() > 0) {
            for (ZLMHttpHookSubscribe.Event subscribe : subscribes) {
            for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
                subscribe.response(null, json);
            }
        }
@@ -167,7 +173,7 @@
            logger.debug("[ ZLM HOOK ]on_play API调用,参数:" + JSON.toJSONString(param));
        }
        String mediaServerId = param.getMediaServerId();
        ZLMHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_play, json);
        ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_play, json);
        if (subscribe != null ) {
            MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
            if (mediaInfo != null) {
@@ -237,9 +243,11 @@
            // 鉴权通过
            redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo);
            // 通知assist新的callId
            if (mediaInfo != null && mediaInfo.getRecordAssistPort() > 0) {
                assistRESTfulUtils.addStreamCallInfo(mediaInfo, param.getApp(), param.getStream(), callId, null);
            }
            taskExecutor.execute(()->{
                if (mediaInfo != null && mediaInfo.getRecordAssistPort() > 0) {
                    assistRESTfulUtils.addStreamCallInfo(mediaInfo, param.getApp(), param.getStream(), callId, null);
                }
            });
        }else {
            zlmMediaListManager.sendStreamEvent(param.getApp(),param.getStream(), param.getMediaServerId());
        }
@@ -252,7 +260,7 @@
        }
        ZLMHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_publish, json);
        ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_publish, json);
        if (subscribe != null) {
            if (mediaInfo != null) {
                subscribe.response(mediaInfo, json);
@@ -376,7 +384,7 @@
            logger.debug("[ ZLM HOOK ]on_shell_login API调用,参数:" + json.toString());
        }
        String mediaServerId = json.getString("mediaServerId");
        ZLMHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_shell_login, json);
        ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_shell_login, json);
        if (subscribe != null ) {
            MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
            if (mediaInfo != null) {
@@ -402,7 +410,7 @@
        logger.info("[ ZLM HOOK ]on_stream_changed API调用,参数:" + JSONObject.toJSONString(item));
        String mediaServerId = item.getMediaServerId();
        JSONObject json = (JSONObject) JSON.toJSON(item);
        ZLMHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json);
        ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json);
        if (subscribe != null ) {
            MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
            if (mediaInfo != null) {
@@ -415,19 +423,24 @@
        String schema = item.getSchema();
        List<MediaItem.MediaTrack> tracks = item.getTracks();
        boolean regist = item.isRegist();
        if (regist) {
            StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(app, stream);
            if (streamAuthorityInfo == null) {
                streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(item);
        if (item.getOriginType() == OriginType.RTMP_PUSH.ordinal()
                || item.getOriginType() == OriginType.RTSP_PUSH.ordinal()
                || item.getOriginType() == OriginType.RTC_PUSH.ordinal()) {
            if (regist) {
                StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(app, stream);
                if (streamAuthorityInfo == null) {
                    streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(item);
                }else {
                    streamAuthorityInfo.setOriginType(item.getOriginType());
                    streamAuthorityInfo.setOriginTypeStr(item.getOriginTypeStr());
                }
                redisCatchStorage.updateStreamAuthorityInfo(app, stream, streamAuthorityInfo);
            }else {
                streamAuthorityInfo.setOriginType(item.getOriginType());
                streamAuthorityInfo.setOriginTypeStr(item.getOriginTypeStr());
                redisCatchStorage.removeStreamAuthorityInfo(app, stream);
            }
            redisCatchStorage.updateStreamAuthorityInfo(app, stream, streamAuthorityInfo);
        }else {
            redisCatchStorage.removeStreamAuthorityInfo(app, stream);
        }
        if ("rtmp".equals(schema)){
        if ("rtsp".equals(schema)){
            logger.info("on_stream_changed:注册->{}, app->{}, stream->{}", regist, app, stream);
            if (regist) {
                mediaServerService.addCount(mediaServerId);
@@ -523,17 +536,21 @@
        if ("rtp".equals(app)){
            ret.put("close", true);
            StreamInfo streamInfoForPlayCatch = redisCatchStorage.queryPlayByStreamId(streamId);
            SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, null, streamId);
            if (streamInfoForPlayCatch != null) {
                // 如果在给上级推流,也不停止。
                // 收到无人观看说明流也没有在往上级推送
                if (redisCatchStorage.isChannelSendingRTP(streamInfoForPlayCatch.getChannelId())) {
                    ret.put("close", false);
                } else {
                    cmder.streamByeCmd(streamInfoForPlayCatch.getDeviceID(), streamInfoForPlayCatch.getChannelId(),
                            streamInfoForPlayCatch.getStream(), null);
                    redisCatchStorage.stopPlay(streamInfoForPlayCatch);
                    storager.stopPlay(streamInfoForPlayCatch.getDeviceID(), streamInfoForPlayCatch.getChannelId());
                    List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByChnnelId(streamInfoForPlayCatch.getChannelId());
                    if (sendRtpItems.size() > 0) {
                        for (SendRtpItem sendRtpItem : sendRtpItems) {
                            ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
                            commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
                        }
                    }
                }
                cmder.streamByeCmd(streamInfoForPlayCatch.getDeviceID(), streamInfoForPlayCatch.getChannelId(),
                        streamInfoForPlayCatch.getStream(), null);
                redisCatchStorage.stopPlay(streamInfoForPlayCatch);
                storager.stopPlay(streamInfoForPlayCatch.getDeviceID(), streamInfoForPlayCatch.getChannelId());
            }else{
                StreamInfo streamInfoForPlayBackCatch = redisCatchStorage.queryPlayback(null, null, streamId, null);
                if (streamInfoForPlayBackCatch != null) {
@@ -615,9 +632,9 @@
        }
        String remoteAddr = request.getRemoteAddr();
        jsonObject.put("ip", remoteAddr);
        List<ZLMHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_started);
        List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_started);
        if (subscribes != null  && subscribes.size() > 0) {
            for (ZLMHttpHookSubscribe.Event subscribe : subscribes) {
            for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
                subscribe.response(null, jsonObject);
            }
        }
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java
@@ -1,30 +1,24 @@
package com.genersoft.iot.vmp.media.zlm;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.GbStream;
import com.genersoft.iot.vmp.media.zlm.dto.*;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.IStreamProxyService;
import com.genersoft.iot.vmp.service.IStreamPushService;
import com.genersoft.iot.vmp.service.bean.ThirdPartyGB;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.storager.dao.GbStreamMapper;
import com.genersoft.iot.vmp.storager.dao.PlatformGbStreamMapper;
import com.genersoft.iot.vmp.storager.dao.StreamPushMapper;
import com.genersoft.iot.vmp.utils.DateUtil;
import org.checkerframework.checker.units.qual.C;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * @author lin
@@ -59,7 +53,7 @@
    private StreamPushMapper streamPushMapper;
    @Autowired
    private ZLMHttpHookSubscribe subscribe;
    private ZlmHttpHookSubscribe subscribe;
    @Autowired
    private UserSetting userSetting;
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
@@ -92,6 +92,7 @@
        int result = -1;
        // 查询此rtp server 是否已经存在
        JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId);
        logger.info(JSONObject.toJSONString(rtpInfo));
        if(rtpInfo.getInteger("code") == 0){
            if (rtpInfo.getBoolean("exist")) {
                result = rtpInfo.getInteger("local_port");
@@ -113,7 +114,7 @@
        }
        param.put("ssrc", ssrc);
        JSONObject openRtpServerResultJson = zlmresTfulUtils.openRtpServer(mediaServerItem, param);
        logger.info(JSONObject.toJSONString(openRtpServerResultJson));
        if (openRtpServerResultJson != null) {
            if (openRtpServerResultJson.getInteger("code") == 0) {
                result= openRtpServerResultJson.getInteger("port");
@@ -277,7 +278,7 @@
     * 查询待转推的流是否就绪
     */
    public Boolean isRtpReady(MediaServerItem mediaServerItem, String streamId) {
        JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem,"rtp", "rtmp", streamId);
        JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem,"rtp", "rtsp", streamId);
        return (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online"));
    }
@@ -297,7 +298,7 @@
     * @return
     */
    public int totalReaderCount(MediaServerItem mediaServerItem, String app, String streamId) {
        JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem, app, "rtmp", streamId);
        JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem, app, "rtsp", streamId);
        if (mediaInfo == null) {
            return 0;
        }
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
@@ -8,7 +8,6 @@
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForServerStarted;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import org.slf4j.Logger;
@@ -19,9 +18,7 @@
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Component
@Order(value=1)
@@ -35,7 +32,7 @@
    private ZLMRESTfulUtils zlmresTfulUtils;
    @Autowired
    private ZLMHttpHookSubscribe hookSubscribe;
    private ZlmHttpHookSubscribe hookSubscribe;
    @Autowired
    private EventPublisher publisher;
@@ -62,8 +59,6 @@
        }
        mediaServerService.syncCatchFromDatabase();
        HookSubscribeForServerStarted hookSubscribeForServerStarted = HookSubscribeFactory.on_server_started();
//        Instant expiresInstant = Instant.now().plusSeconds(TimeUnit.SECONDS.toSeconds(60));
//        hookSubscribeForStreamChange.setExpires(expiresInstant);
        // 订阅 zlm启动事件, 新的zlm也会从这里进入系统
        hookSubscribe.addSubscribe(hookSubscribeForServerStarted,
                (MediaServerItem mediaServerItem, JSONObject response)->{
src/main/java/com/genersoft/iot/vmp/media/zlm/ZlmHttpHookSubscribe.java
File was renamed from src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookSubscribe.java
@@ -4,6 +4,9 @@
import com.genersoft.iot.vmp.media.zlm.dto.HookType;
import com.genersoft.iot.vmp.media.zlm.dto.IHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
@@ -13,21 +16,22 @@
import java.util.concurrent.TimeUnit;
/**
 * @description:针对 ZLMediaServer的hook事件订阅
 * @author: pan
 * @date:   2020年12月2日 21:17:32
 * ZLMediaServer的hook事件订阅
 * @author lin
 */
@Component
public class ZLMHttpHookSubscribe {
public class ZlmHttpHookSubscribe {
    private final static Logger logger = LoggerFactory.getLogger(ZlmHttpHookSubscribe.class);
    @FunctionalInterface
    public interface Event{
        void response(MediaServerItem mediaServerItem, JSONObject response);
    }
    private Map<HookType, Map<IHookSubscribe, ZLMHttpHookSubscribe.Event>> allSubscribes = new ConcurrentHashMap<>();
    private Map<HookType, Map<IHookSubscribe, ZlmHttpHookSubscribe.Event>> allSubscribes = new ConcurrentHashMap<>();
    public void addSubscribe(IHookSubscribe hookSubscribe, ZLMHttpHookSubscribe.Event event) {
    public void addSubscribe(IHookSubscribe hookSubscribe, ZlmHttpHookSubscribe.Event event) {
        if (hookSubscribe.getExpires() == null) {
            // 默认5分钟过期
            Instant expiresInstant = Instant.now().plusSeconds(TimeUnit.MINUTES.toSeconds(5));
@@ -36,8 +40,8 @@
        allSubscribes.computeIfAbsent(hookSubscribe.getHookType(), k -> new ConcurrentHashMap<>()).put(hookSubscribe, event);
    }
    public ZLMHttpHookSubscribe.Event sendNotify(HookType type, JSONObject hookResponse) {
        ZLMHttpHookSubscribe.Event event= null;
    public ZlmHttpHookSubscribe.Event sendNotify(HookType type, JSONObject hookResponse) {
        ZlmHttpHookSubscribe.Event event= null;
        Map<IHookSubscribe, Event> eventMap = allSubscribes.get(type);
        if (eventMap == null) {
            return null;
@@ -69,8 +73,8 @@
        Set<Map.Entry<IHookSubscribe, Event>> entries = eventMap.entrySet();
        if (entries.size() > 0) {
            List<Map.Entry<IHookSubscribe, ZLMHttpHookSubscribe.Event>> entriesToRemove = new ArrayList<>();
            for (Map.Entry<IHookSubscribe, ZLMHttpHookSubscribe.Event> entry : entries) {
            List<Map.Entry<IHookSubscribe, ZlmHttpHookSubscribe.Event>> entriesToRemove = new ArrayList<>();
            for (Map.Entry<IHookSubscribe, ZlmHttpHookSubscribe.Event> entry : entries) {
                JSONObject content = entry.getKey().getContent();
                if (content == null || content.size() == 0) {
                    entriesToRemove.add(entry);
@@ -87,13 +91,13 @@
                        result = result && content.getString(s).equals(hookSubscribe.getContent().getString(s));
                    }
                }
                if (null != result && result){
                if (result){
                    entriesToRemove.add(entry);
                }
            }
            if (!CollectionUtils.isEmpty(entriesToRemove)) {
                for (Map.Entry<IHookSubscribe, ZLMHttpHookSubscribe.Event> entry : entriesToRemove) {
                for (Map.Entry<IHookSubscribe, ZlmHttpHookSubscribe.Event> entry : entriesToRemove) {
                    entries.remove(entry);
                }
            }
@@ -106,12 +110,12 @@
     * @param type
     * @return
     */
    public List<ZLMHttpHookSubscribe.Event> getSubscribes(HookType type) {
    public List<ZlmHttpHookSubscribe.Event> getSubscribes(HookType type) {
        Map<IHookSubscribe, Event> eventMap = allSubscribes.get(type);
        if (eventMap == null) {
            return null;
        }
        List<ZLMHttpHookSubscribe.Event> result = new ArrayList<>();
        List<ZlmHttpHookSubscribe.Event> result = new ArrayList<>();
        for (IHookSubscribe key : eventMap.keySet()) {
            result.add(eventMap.get(key));
        }
@@ -127,5 +131,28 @@
        return result;
    }
    /**
     * 对订阅数据进行过期清理
     */
    @Scheduled(cron="0 0/5 * * * ?")   //每5分钟执行一次
    public void execute(){
        logger.info("[hook订阅] 清理");
        Instant instant = Instant.now().minusMillis(TimeUnit.MINUTES.toMillis(5));
        int total = 0;
        for (HookType hookType : allSubscribes.keySet()) {
            Map<IHookSubscribe, Event> hookSubscribeEventMap = allSubscribes.get(hookType);
            if (hookSubscribeEventMap.size() > 0) {
                for (IHookSubscribe hookSubscribe : hookSubscribeEventMap.keySet()) {
                    if (hookSubscribe.getExpires().isBefore(instant)) {
                        // 过期的
                        hookSubscribeEventMap.remove(hookSubscribe);
                        total ++;
                    }
                }
            }
        }
        logger.info("[hook订阅] 清理结束,共清理{}条过期数据", total);
    }
}
src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMKeepliveTimeoutListener.java
File was deleted
src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java
New file
@@ -0,0 +1,45 @@
package com.genersoft.iot.vmp.service;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.github.pagehelper.PageInfo;
/**
 * 国标平台的业务类
 * @author lin
 */
public interface IPlatformService {
    ParentPlatform queryPlatformByServerGBId(String platformGbId);
    /**
     * 分页获取上级平台
     * @param page
     * @param count
     * @return
     */
    PageInfo<ParentPlatform> queryParentPlatformList(int page, int count);
    /**
     * 添加级联平台
     * @param parentPlatform 级联平台
     */
    boolean add(ParentPlatform parentPlatform);
    /**
     * 平台上线
     * @param parentPlatform 平台信息
     */
    void online(ParentPlatform parentPlatform);
    /**
     * 平台离线
     * @param parentPlatform 平台信息
     */
    void offline(ParentPlatform parentPlatform);
    /**
     * 向上级平台发起注册
     * @param parentPlatform
     */
    void login(ParentPlatform parentPlatform);
}
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
@@ -6,7 +6,7 @@
import com.genersoft.iot.vmp.gb28181.bean.InviteStreamCallback;
import com.genersoft.iot.vmp.gb28181.bean.InviteStreamInfo;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback;
import com.genersoft.iot.vmp.service.bean.PlayBackCallback;
@@ -14,7 +14,6 @@
import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.async.DeferredResult;
/**
@@ -25,9 +24,9 @@
    void onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid);
    void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
              ZLMHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
              ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
              InviteTimeOutCallback timeoutCallback, String uuid);
    PlayResult play(MediaServerItem mediaServerItem, String deviceId, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent, Runnable timeoutCallback);
    PlayResult play(MediaServerItem mediaServerItem, String deviceId, String channelId, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent, Runnable timeoutCallback);
    MediaServerItem getNewMediaServerItem(Device device);
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
@@ -11,6 +11,7 @@
import com.genersoft.iot.vmp.media.zlm.ZLMRunner;
import com.genersoft.iot.vmp.service.IStreamProxyService;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import org.slf4j.Logger;
@@ -22,7 +23,6 @@
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
@@ -42,7 +42,6 @@
import com.genersoft.iot.vmp.storager.dao.MediaServerMapper;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import okhttp3.OkHttpClient;
import okhttp3.Request;
@@ -57,6 +56,8 @@
public class MediaServerServiceImpl implements IMediaServerService {
    private final static Logger logger = LoggerFactory.getLogger(MediaServerServiceImpl.class);
    private final String zlmKeepaliveKeyPrefix = "zlm-keepalive_";
    @Autowired
    private SipConfig sipConfig;
@@ -88,9 +89,11 @@
    @Autowired
    private ZLMRTPServerFactory zlmrtpServerFactory;
    @Autowired
    private EventPublisher publisher;
    @Autowired
    private DynamicTask dynamicTask;
    /**
     * 初始化
@@ -135,7 +138,7 @@
            logger.info("media server [ {} ] ssrcConfig is null", mediaServerItem.getId());
            return null;
        }else {
            String ssrc = null;
            String ssrc;
            if (presetSsrc != null) {
                ssrc = presetSsrc;
            }else {
@@ -404,15 +407,43 @@
        if (serverItem.isAutoConfig()) {
            setZLMConfig(serverItem, "0".equals(zlmServerConfig.getHookEnable()));
        }
        final String zlmKeepaliveKey = zlmKeepaliveKeyPrefix + serverItem.getId();
        dynamicTask.stop(zlmKeepaliveKey);
        dynamicTask.startDelay(zlmKeepaliveKey, new KeepAliveTimeoutRunnable(serverItem), (serverItem.getHookAliveInterval() + 5) * 1000);
        publisher.zlmOnlineEventPublish(serverItem.getId());
        logger.info("[ZLM] 连接成功 {} - {}:{} ",
                zlmServerConfig.getGeneralMediaServerId(), zlmServerConfig.getIp(), zlmServerConfig.getHttpPort());
    }
    class KeepAliveTimeoutRunnable implements Runnable{
        private MediaServerItem serverItem;
        public KeepAliveTimeoutRunnable(MediaServerItem serverItem) {
            this.serverItem = serverItem;
        }
        @Override
        public void run() {
            logger.info("[zlm心跳到期]:" + serverItem.getId());
            // 发起http请求验证zlm是否确实无法连接,如果确实无法连接则发送离线事件,否则不作处理
            JSONObject mediaServerConfig = zlmresTfulUtils.getMediaServerConfig(serverItem);
            if (mediaServerConfig != null && mediaServerConfig.getInteger("code") == 0) {
                logger.info("[zlm心跳到期]:{}验证后zlm仍在线,恢复心跳信息,请检查zlm是否可以正常向wvp发送心跳", serverItem.getId());
                // 添加zlm信息
                updateMediaServerKeepalive(serverItem.getId(), mediaServerConfig);
            }else {
                publisher.zlmOfflineEventPublish(serverItem.getId());
            }
        }
    }
    @Override
    public void zlmServerOffline(String mediaServerId) {
        delete(mediaServerId);
        final String zlmKeepaliveKey = zlmKeepaliveKeyPrefix + mediaServerId;
        dynamicTask.stop(zlmKeepaliveKey);
    }
    @Override
@@ -423,7 +454,7 @@
        if (RedisUtil.zScore(key, serverItem.getId()) == null) {  // 不存在则设置默认值 已存在则重置
            RedisUtil.zAdd(key, serverItem.getId(), 0L);
            // 查询服务流数量
            zlmresTfulUtils.getMediaList(serverItem, null, null, "rtmp",(mediaList ->{
            zlmresTfulUtils.getMediaList(serverItem, null, null, "rtsp",(mediaList ->{
                Integer code = mediaList.getInteger("code");
                if (code == 0) {
                    JSONArray data = mediaList.getJSONArray("data");
@@ -435,7 +466,6 @@
        }else {
            clearRTPServer(serverItem);
        }
    }
@@ -471,7 +501,7 @@
        }
        // 获取分数最低的,及并发最低的
        Set<Object> objects = RedisUtil.ZRange(key, 0, -1);
        Set<Object> objects = RedisUtil.zRange(key, 0, -1);
        ArrayList<Object> mediaServerObjectS = new ArrayList<>(objects);
        String mediaServerId = (String)mediaServerObjectS.get(0);
@@ -489,10 +519,7 @@
                mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
        String protocol = sslEnabled ? "https" : "http";
        String hookPrex = String.format("%s://%s:%s/index/hook", protocol, mediaServerItem.getHookIp(), serverPort);
        String recordHookPrex = null;
        if (mediaServerItem.getRecordAssistPort() != 0) {
            recordHookPrex = String.format("http://127.0.0.1:%s/api/record", mediaServerItem.getRecordAssistPort());
        }
        Map<String, Object> param = new HashMap<>();
        param.put("api.secret",mediaServerItem.getSecret()); // -profile:v Baseline
        param.put("ffmpeg.cmd","%s -fflags nobuffer -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264  -f flv %s");
@@ -501,7 +528,6 @@
        param.put("hook.on_play",String.format("%s/on_play", hookPrex));
        param.put("hook.on_http_access",String.format("%s/on_http_access", hookPrex));
        param.put("hook.on_publish", String.format("%s/on_publish", hookPrex));
        param.put("hook.on_record_mp4",recordHookPrex != null? String.format("%s/on_record_mp4", recordHookPrex): "");
        param.put("hook.on_record_ts",String.format("%s/on_record_ts", hookPrex));
        param.put("hook.on_rtsp_auth",String.format("%s/on_rtsp_auth", hookPrex));
        param.put("hook.on_rtsp_realm",String.format("%s/on_rtsp_realm", hookPrex));
@@ -511,6 +537,11 @@
        param.put("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrex));
        param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrex));
        param.put("hook.on_server_keepalive",String.format("%s/on_server_keepalive", hookPrex));
        if (mediaServerItem.getRecordAssistPort() > 0) {
            param.put("hook.on_record_mp4",String.format("http://127.0.0.1:%s/api/record/on_record_mp4", mediaServerItem.getRecordAssistPort()));
        }else {
            param.put("hook.on_record_mp4","");
        }
        param.put("hook.timeoutSec","20");
        param.put("general.streamNoneReaderDelayMS",mediaServerItem.getStreamNoneReaderDelayMS()==-1?"3600000":mediaServerItem.getStreamNoneReaderDelayMS() );
        // 推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。
@@ -631,9 +662,9 @@
                return;
            }
        }
        String key = VideoManagerConstants.MEDIA_SERVER_KEEPALIVE_PREFIX + userSetting.getServerId() + "_" + mediaServerId;
        int hookAliveInterval = mediaServerItem.getHookAliveInterval() + 2;
        RedisUtil.set(key, data, hookAliveInterval);
        final String zlmKeepaliveKey = zlmKeepaliveKeyPrefix + mediaServerItem.getId();
        dynamicTask.stop(zlmKeepaliveKey);
        dynamicTask.startDelay(zlmKeepaliveKey, new KeepAliveTimeoutRunnable(mediaServerItem), (mediaServerItem.getHookAliveInterval() + 5) * 1000);
    }
    private MediaServerItem getOneFromDatabase(String mediaServerId) {
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java
@@ -108,6 +108,7 @@
        streamInfoResult.setWs_fmp4(String.format("ws://%s:%s/%s/%s.live.mp4%s", addr, mediaInfo.getHttpPort(), app,  stream, callIdParam));
        streamInfoResult.setTs(String.format("http://%s:%s/%s/%s.live.ts%s", addr, mediaInfo.getHttpPort(), app,  stream, callIdParam));
        streamInfoResult.setWs_ts(String.format("ws://%s:%s/%s/%s.live.ts%s", addr, mediaInfo.getHttpPort(), app,  stream, callIdParam));
        streamInfoResult.setRtc(String.format("http://%s:%s/index/api/webrtc?app=%s&stream=%s&type=play%s", mediaInfo.getStreamIp(), mediaInfo.getHttpPort(), app,  stream, ObjectUtils.isEmpty(callId)?"":"&callId=" + callId));
        if (mediaInfo.getHttpSSlPort() != 0) {
            streamInfoResult.setHttps_flv(String.format("https://%s:%s/%s/%s.live.flv%s", addr, mediaInfo.getHttpSSlPort(), app,  stream, callIdParam));
            streamInfoResult.setWss_flv(String.format("wss://%s:%s/%s/%s.live.flv%s", addr, mediaInfo.getHttpSSlPort(), app,  stream, callIdParam));
@@ -118,7 +119,7 @@
            streamInfoResult.setHttps_ts(String.format("https://%s:%s/%s/%s.live.ts%s", addr, mediaInfo.getHttpSSlPort(), app,  stream, callIdParam));
            streamInfoResult.setWss_ts(String.format("wss://%s:%s/%s/%s.live.ts%s", addr, mediaInfo.getHttpSSlPort(), app,  stream, callIdParam));
            streamInfoResult.setWss_ts(String.format("wss://%s:%s/%s/%s.live.ts%s", addr, mediaInfo.getHttpSSlPort(), app,  stream, callIdParam));
            streamInfoResult.setRtc(String.format("https://%s:%s/index/api/webrtc?app=%s&stream=%s&type=%s%s", mediaInfo.getStreamIp(), mediaInfo.getHttpSSlPort(), app,  stream, isPlay?"play":"push", ObjectUtils.isEmpty(callId)?"":"&callId=" + callId));
            streamInfoResult.setRtcs(String.format("https://%s:%s/index/api/webrtc?app=%s&stream=%s&type=play%s", mediaInfo.getStreamIp(), mediaInfo.getHttpSSlPort(), app,  stream, ObjectUtils.isEmpty(callId)?"":"&callId=" + callId));
        }
        streamInfoResult.setTracks(tracks);
src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java
New file
@@ -0,0 +1,232 @@
package com.genersoft.iot.vmp.service.impl;
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatformCatch;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.IPlatformService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.ParentPlatformMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.sip.TimeoutEvent;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * @author lin
 */
@Service
public class PlatformServiceImpl implements IPlatformService {
    private final static String REGISTER_KEY_PREFIX = "platform_register_";
    private final static String KEEPALIVE_KEY_PREFIX = "platform_keepalive_";
    private final static Logger logger = LoggerFactory.getLogger(PlatformServiceImpl.class);
    @Autowired
    private ParentPlatformMapper platformMapper;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private IMediaServerService mediaServerService;
    @Autowired
    private SIPCommanderFroPlatform commanderForPlatform;
    @Autowired
    private DynamicTask dynamicTask;
    @Autowired
    private ZLMRTPServerFactory zlmrtpServerFactory;
    @Autowired
    private SubscribeHolder subscribeHolder;
    @Override
    public ParentPlatform queryPlatformByServerGBId(String platformGbId) {
        return platformMapper.getParentPlatByServerGBId(platformGbId);
    }
    @Override
    public PageInfo<ParentPlatform> queryParentPlatformList(int page, int count) {
        PageHelper.startPage(page, count);
        List<ParentPlatform> all = platformMapper.getParentPlatformList();
        return new PageInfo<>(all);
    }
    @Override
    public boolean add(ParentPlatform parentPlatform) {
        if (parentPlatform.getCatalogGroup() == 0) {
            // 每次发送目录的数量默认为1
            parentPlatform.setCatalogGroup(1);
        }
        if (parentPlatform.getAdministrativeDivision() == null) {
            // 行政区划默认去编号的前6位
            parentPlatform.setAdministrativeDivision(parentPlatform.getServerGBId().substring(0,6));
        }
        parentPlatform.setCatalogId(parentPlatform.getDeviceGBId());
        int result = platformMapper.addParentPlatform(parentPlatform);
        // 添加缓存
        ParentPlatformCatch parentPlatformCatch = new ParentPlatformCatch();
        parentPlatformCatch.setParentPlatform(parentPlatform);
        parentPlatformCatch.setId(parentPlatform.getServerGBId());
        parentPlatformCatch.setParentPlatform(parentPlatform);
        redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
        if (parentPlatform.isEnable()) {
            // 保存时启用就发送注册
            // 注册成功时由程序直接调用了online方法
            commanderForPlatform.register(parentPlatform, eventResult -> {
                logger.info("[国标级联] {},添加向上级注册失败,请确定上级平台可用时重新保存", parentPlatform.getServerGBId());
            }, null);
        }
        return result > 0;
    }
    @Override
    public void online(ParentPlatform parentPlatform) {
        logger.info("[国标级联]:{}, 平台上线/更新注册", parentPlatform.getServerGBId());
        platformMapper.updateParentPlatformStatus(parentPlatform.getServerGBId(), true);
        ParentPlatformCatch parentPlatformCatch = redisCatchStorage.queryPlatformCatchInfo(parentPlatform.getServerGBId());
        if (parentPlatformCatch != null) {
            parentPlatformCatch.getParentPlatform().setStatus(true);
            redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
        }else {
            parentPlatformCatch = new ParentPlatformCatch();
            parentPlatformCatch.setParentPlatform(parentPlatform);
            parentPlatformCatch.setId(parentPlatform.getServerGBId());
            parentPlatform.setStatus(true);
            parentPlatformCatch.setParentPlatform(parentPlatform);
            redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
        }
        final String registerTaskKey = REGISTER_KEY_PREFIX + parentPlatform.getServerGBId();
        if (dynamicTask.contains(registerTaskKey)) {
            dynamicTask.stop(registerTaskKey);
        }
        // 添加注册任务
        dynamicTask.startDelay(registerTaskKey,
                // 注册失败(注册成功时由程序直接调用了online方法)
                ()->commanderForPlatform.register(parentPlatform, eventResult -> offline(parentPlatform),null),
                parentPlatform.getExpires()*1000);
        final String keepaliveTaskKey = KEEPALIVE_KEY_PREFIX + parentPlatform.getServerGBId();
        if (!dynamicTask.contains(keepaliveTaskKey)) {
            // 添加心跳任务
            dynamicTask.startCron(keepaliveTaskKey,
                    ()-> commanderForPlatform.keepalive(parentPlatform, eventResult -> {
                        // 心跳失败
                        if (eventResult.type == SipSubscribe.EventResultType.timeout) {
                            // 心跳超时
                            ParentPlatformCatch platformCatch = redisCatchStorage.queryPlatformCatchInfo(parentPlatform.getServerGBId());
                            // 此时是第三次心跳超时, 平台离线
                            if (platformCatch.getKeepAliveReply()  == 2) {
                                // 设置平台离线,并重新注册
                                offline(parentPlatform);
                                logger.info("[国标级联] {},三次心跳超时后再次发起注册", parentPlatform.getServerGBId());
                                commanderForPlatform.register(parentPlatform, eventResult1 -> {
                                    logger.info("[国标级联] {},三次心跳超时后再次发起注册仍然失败,开始定时发起注册,间隔为1分钟", parentPlatform.getServerGBId());
                                    // 添加注册任务
                                    dynamicTask.startCron(registerTaskKey,
                                            // 注册失败(注册成功时由程序直接调用了online方法)
                                            ()->logger.info("[国标级联] {},平台离线后持续发起注册,失败", parentPlatform.getServerGBId()),
                                            60*1000);
                                }, null);
                            }
                        }else {
                            logger.warn("[国标级联]发送心跳收到错误,code: {}, msg: {}", eventResult.statusCode, eventResult.msg);
                        }
                    }, eventResult -> {
                        // 心跳成功
                        // 清空之前的心跳超时计数
                        ParentPlatformCatch platformCatch = redisCatchStorage.queryPlatformCatchInfo(parentPlatform.getServerGBId());
                        if (platformCatch.getKeepAliveReply() > 0) {
                            platformCatch.setKeepAliveReply(0);
                            redisCatchStorage.updatePlatformCatchInfo(platformCatch);
                        }
                    }),
                    parentPlatform.getExpires()*1000);
        }
    }
    @Override
    public void offline(ParentPlatform parentPlatform) {
        logger.info("[平台离线]:{}", parentPlatform.getServerGBId());
        ParentPlatformCatch parentPlatformCatch = redisCatchStorage.queryPlatformCatchInfo(parentPlatform.getServerGBId());
        parentPlatformCatch.setKeepAliveReply(0);
        parentPlatformCatch.setRegisterAliveReply(0);
        ParentPlatform parentPlatformInCatch = parentPlatformCatch.getParentPlatform();
        parentPlatformInCatch.setStatus(false);
        parentPlatformCatch.setParentPlatform(parentPlatformInCatch);
        redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
        platformMapper.updateParentPlatformStatus(parentPlatform.getServerGBId(), false);
        // 停止所有推流
        logger.info("[平台离线] {}, 停止所有推流", parentPlatform.getServerGBId());
        stopAllPush(parentPlatform.getServerGBId());
        // 清除注册定时
        logger.info("[平台离线] {}, 停止定时注册任务", parentPlatform.getServerGBId());
        final String registerTaskKey = REGISTER_KEY_PREFIX + parentPlatform.getServerGBId();
        if (dynamicTask.contains(registerTaskKey)) {
            dynamicTask.stop(registerTaskKey);
        }
        // 清除心跳定时
        logger.info("[平台离线] {}, 停止定时发送心跳任务", parentPlatform.getServerGBId());
        final String keepaliveTaskKey = KEEPALIVE_KEY_PREFIX + parentPlatform.getServerGBId();
        if (dynamicTask.contains(keepaliveTaskKey)) {
            // 添加心跳任务
            dynamicTask.stop(keepaliveTaskKey);
        }
        // 停止目录订阅回复
        logger.info("[平台离线] {}, 停止订阅回复", parentPlatform.getServerGBId());
        subscribeHolder.removeAllSubscribe(parentPlatform.getServerGBId());
    }
    private void stopAllPush(String platformId) {
        List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServer(platformId);
        if (sendRtpItems != null && sendRtpItems.size() > 0) {
            for (SendRtpItem sendRtpItem : sendRtpItems) {
                redisCatchStorage.deleteSendRTPServer(platformId, sendRtpItem.getChannelId(), null, null);
                MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
                Map<String, Object> param = new HashMap<>(3);
                param.put("vhost", "__defaultVhost__");
                param.put("app", sendRtpItem.getApp());
                param.put("stream", sendRtpItem.getStreamId());
                zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
            }
        }
    }
    @Override
    public void login(ParentPlatform parentPlatform) {
        final String registerTaskKey = REGISTER_KEY_PREFIX + parentPlatform.getServerGBId();
        commanderForPlatform.register(parentPlatform, eventResult1 -> {
            logger.info("[国标级联] {},开始定时发起注册,间隔为1分钟", parentPlatform.getServerGBId());
            // 添加注册任务
            dynamicTask.startCron(registerTaskKey,
                    // 注册失败(注册成功时由程序直接调用了online方法)
                    ()->logger.info("[国标级联] {},平台离线后持续发起注册,失败", parentPlatform.getServerGBId()),
                    60*1000);
        }, null);
    }
}
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
@@ -37,7 +37,7 @@
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
@@ -128,7 +128,7 @@
    private DynamicTask dynamicTask;
    @Autowired
    private ZLMHttpHookSubscribe subscribe;
    private ZlmHttpHookSubscribe subscribe;
    @Qualifier("taskExecutor")
@@ -139,7 +139,7 @@
    @Override
    public PlayResult play(MediaServerItem mediaServerItem, String deviceId, String channelId,
                           ZLMHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
                           ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
                           Runnable timeoutCallback) {
        if (mediaServerItem == null) {
            throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm");
@@ -222,6 +222,7 @@
                streamId = String.format("%s_%s", device.getDeviceId(), channelId);
            }
            SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck(), false);
            logger.info(JSONObject.toJSONString(ssrcInfo));
            play(mediaServerItem, ssrcInfo, device, channelId, (mediaServerItemInUse, response)->{
                if (hookEvent != null) {
                    hookEvent.response(mediaServerItem, response);
@@ -257,8 +258,8 @@
    @Override
    public void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
                           ZLMHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
                           InviteTimeOutCallback timeoutCallback, String uuid) {
                     ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
                     InviteTimeOutCallback timeoutCallback, String uuid) {
        String streamId = null;
        if (mediaServerItem.isRtpEnable()) {
@@ -333,7 +334,7 @@
                    // 单端口模式streamId也有变化,需要重新设置监听
                    if (!mediaServerItem.isRtpEnable()) {
                        // 添加订阅
                        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtmp", mediaServerItem.getId());
                        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
                        subscribe.removeSubscribe(hookSubscribe);
                        hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
                        subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response)->{
@@ -609,7 +610,7 @@
                logger.warn("查询录像信息时发现节点已离线");
                return null;
            }
            if (mediaServerItem.getRecordAssistPort() != 0) {
            if (mediaServerItem.getRecordAssistPort() > 0) {
                JSONObject jsonObject = assistRESTfulUtils.fileDuration(mediaServerItem, streamInfo.getApp(), streamInfo.getStream(), null);
                if (jsonObject != null && jsonObject.getInteger("code") == 0) {
                    long duration = jsonObject.getLong("data");
@@ -802,7 +803,7 @@
//                            for (SendRtpItem sendRtpItem : sendRtpItems) {
//                                if (sendRtpItem.getMediaServerId().equals(mediaServerId)) {
//                                    if (mediaListMap.get(sendRtpItem.getStreamId()) == null) {
//                                        ParentPlatform platform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
//                                        ParentPlatform platform = storager.queryPlatformByServerGBId(sendRtpItem.getPlatformId());
//                                        sipCommanderFroPlatform.streamByeCmd(platform, sendRtpItem.getCallId());
//                                    }
//                                }
src/main/java/com/genersoft/iot/vmp/service/impl/RedisAlarmMsgListener.java
@@ -4,6 +4,8 @@
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.service.IPlatformChannelService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.utils.DateUtil;
import org.slf4j.Logger;
@@ -12,6 +14,9 @@
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import java.util.List;
@Component
@@ -37,8 +42,6 @@
            return;
        }
        String gbId = alarmChannelMessage.getGbId();
        Device device = storage.queryVideoDevice(gbId);
        ParentPlatform platform = storage.queryParentPlatByServerGBId(gbId);
        DeviceAlarm deviceAlarm = new DeviceAlarm();
        deviceAlarm.setCreateTime(DateUtil.getNow());
@@ -46,18 +49,29 @@
        deviceAlarm.setAlarmDescription(alarmChannelMessage.getAlarmDescription());
        deviceAlarm.setAlarmMethod("" + alarmChannelMessage.getAlarmSn());
        deviceAlarm.setAlarmPriority("1");
        deviceAlarm.setAlarmTime(DateUtil.getNow());
        deviceAlarm.setAlarmTime(DateUtil.getNowForISO8601());
        deviceAlarm.setAlarmType("1");
        deviceAlarm.setLongitude(0);
        deviceAlarm.setLatitude(0);
        if (device != null && platform == null) {
            commander.sendAlarmMessage(device, deviceAlarm);
        }else if (device == null && platform != null){
            commanderForPlatform.sendAlarmMessage(platform, deviceAlarm);
        if (ObjectUtils.isEmpty(gbId)) {
            // 发送给所有的上级
            List<ParentPlatform> parentPlatforms = storage.queryEnableParentPlatformList(true);
            if (parentPlatforms.size() > 0) {
                for (ParentPlatform parentPlatform : parentPlatforms) {
                    commanderForPlatform.sendAlarmMessage(parentPlatform, deviceAlarm);
                }
            }
        }else {
           logger.warn("无法确定" + gbId + "是平台还是设备");
            Device device = storage.queryVideoDevice(gbId);
            ParentPlatform platform = storage.queryParentPlatByServerGBId(gbId);
            if (device != null && platform == null) {
                commander.sendAlarmMessage(device, deviceAlarm);
            }else if (device == null && platform != null){
                commanderForPlatform.sendAlarmMessage(platform, deviceAlarm);
            }else {
                logger.warn("无法确定" + gbId + "是平台还是设备");
            }
        }
    }
}
src/main/java/com/genersoft/iot/vmp/service/impl/RedisGbPlayMsgListener.java
@@ -5,12 +5,11 @@
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMMediaListManager;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
import com.genersoft.iot.vmp.media.zlm.dto.HookType;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.*;
@@ -24,9 +23,6 @@
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
import javax.sip.InvalidArgumentException;
import javax.sip.SipException;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@@ -86,7 +82,7 @@
    private ZLMMediaListManager mediaListManager;
    @Autowired
    private ZLMHttpHookSubscribe subscribe;
    private ZlmHttpHookSubscribe subscribe;
    public interface PlayMsgCallback{
@@ -271,7 +267,7 @@
            }, userSetting.getPlatformPlayTimeout());
            // 添加订阅
            HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed(content.getApp(), content.getStream(), true, "rtmp", mediaServerItem.getId());
            HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed(content.getApp(), content.getStream(), true, "rtsp", mediaServerItem.getId());
            subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json)->{
                        dynamicTask.stop(taskKey);
src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java
@@ -295,7 +295,6 @@
            if (jsonObject == null) {
                return false;
            }
            System.out.println(jsonObject);
            if (jsonObject.getInteger("code") == 0) {
                result = true;
                streamProxy.setEnable(true);
@@ -421,7 +420,7 @@
                        if(data != null && data.size() > 0) {
                            for (int i = 0; i < data.size(); i++) {
                                JSONObject streamJSONObj = data.getJSONObject(i);
                                if ("rtmp".equals(streamJSONObj.getString("schema"))) {
                                if ("rtsp".equals(streamJSONObj.getString("schema"))) {
                                    StreamInfo streamInfo = new StreamInfo();
                                    String app = streamJSONObj.getString("app");
                                    String stream = streamJSONObj.getString("stream");
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
@@ -8,6 +8,7 @@
import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.service.bean.ThirdPartyGB;
import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
import java.util.List;
import java.util.Map;
@@ -61,17 +62,13 @@
    void delPlatformCatchInfo(String platformGbId);
    void updatePlatformKeepalive(ParentPlatform parentPlatform);
    void delPlatformKeepalive(String platformGbId);
    void updatePlatformRegister(ParentPlatform parentPlatform);
    void delPlatformRegister(String platformGbId);
    void updatePlatformRegisterInfo(String callId, String platformGbId);
    void updatePlatformRegisterInfo(String callId, PlatformRegisterInfo platformRegisterInfo);
    String queryPlatformRegisterInfo(String callId);
    PlatformRegisterInfo queryPlatformRegisterInfo(String callId);
    void delPlatformRegisterInfo(String callId);
@@ -237,4 +234,6 @@
     * 发送redis消息 查询所有推流设备的状态
     */
    void sendStreamPushRequestedMsgForStatus();
    List<SendRtpItem> querySendRTPServerByChnnelId(String channelId);
}
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java
@@ -170,15 +170,6 @@
     */
    boolean deleteParentPlatform(ParentPlatform parentPlatform);
    /**
     * 分页获取上级平台
     * @param page
     * @param count
     * @return
     */
    PageInfo<ParentPlatform> queryParentPlatformList(int page, int count);
    /**
     * 获取所有已启用的平台
     * @return
src/main/java/com/genersoft/iot/vmp/storager/dao/dto/PlatformRegisterInfo.java
New file
@@ -0,0 +1,41 @@
package com.genersoft.iot.vmp.storager.dao.dto;
/**
 * 平台发送注册/注销消息时缓存此消息
 * @author lin
 */
public class PlatformRegisterInfo {
    /**
     * 平台Id
     */
    private String platformId;
    /**
     * 是否时注册,false为注销
     */
    private boolean register;
    public static PlatformRegisterInfo getInstance(String platformId, boolean register) {
        PlatformRegisterInfo platformRegisterInfo = new PlatformRegisterInfo();
        platformRegisterInfo.setPlatformId(platformId);
        platformRegisterInfo.setRegister(register);
        return platformRegisterInfo;
    }
    public String getPlatformId() {
        return platformId;
    }
    public void setPlatformId(String platformId) {
        this.platformId = platformId;
    }
    public boolean isRegister() {
        return register;
    }
    public void setRegister(boolean register) {
        this.register = register;
    }
}
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
@@ -16,11 +16,13 @@
import com.genersoft.iot.vmp.service.bean.ThirdPartyGB;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import java.util.*;
@@ -290,18 +292,6 @@
    }
    @Override
    public void updatePlatformKeepalive(ParentPlatform parentPlatform) {
        String key = VideoManagerConstants.PLATFORM_KEEPALIVE_PREFIX  + userSetting.getServerId() + "_" + parentPlatform.getServerGBId();
        RedisUtil.set(key, "", Integer.parseInt(parentPlatform.getKeepTimeout()));
    }
    @Override
    public void updatePlatformRegister(ParentPlatform parentPlatform) {
        String key = VideoManagerConstants.PLATFORM_REGISTER_PREFIX + userSetting.getServerId() + "_" + parentPlatform.getServerGBId();
        RedisUtil.set(key, "", Integer.parseInt(parentPlatform.getExpires()));
    }
    @Override
    public ParentPlatformCatch queryPlatformCatchInfo(String platformGbId) {
        return (ParentPlatformCatch)RedisUtil.get(VideoManagerConstants.PLATFORM_CATCH_PREFIX + userSetting.getServerId() + "_" + platformGbId);
    }
@@ -323,15 +313,15 @@
    @Override
    public void updatePlatformRegisterInfo(String callId, String platformGbId) {
    public void updatePlatformRegisterInfo(String callId, PlatformRegisterInfo platformRegisterInfo) {
        String key = VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + userSetting.getServerId() + "_" + callId;
        RedisUtil.set(key, platformGbId, 30);
        RedisUtil.set(key, platformRegisterInfo, 30);
    }
    @Override
    public String queryPlatformRegisterInfo(String callId) {
        return (String)RedisUtil.get(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + userSetting.getServerId() + "_" + callId);
    public PlatformRegisterInfo queryPlatformRegisterInfo(String callId) {
        return (PlatformRegisterInfo)RedisUtil.get(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + userSetting.getServerId() + "_" + callId);
    }
    @Override
@@ -380,6 +370,24 @@
    }
    @Override
    public List<SendRtpItem> querySendRTPServerByChnnelId(String channelId) {
        if (channelId == null) {
            return null;
        }
        String platformGbId = "*";
        String callId = "*";
        String streamId = "*";
        String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + userSetting.getServerId() + "_" + platformGbId
                + "_" + channelId + "_" + streamId + "_" + callId;
        List<Object> scan = RedisUtil.scan(key);
        List<SendRtpItem> result = new ArrayList<>();
        for (Object o : scan) {
            result.add((SendRtpItem) RedisUtil.get((String) o));
        }
        return result;
    }
    @Override
    public List<SendRtpItem> querySendRTPServer(String platformGbId) {
        if (platformGbId == null) {
            platformGbId = "*";
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
@@ -458,13 +458,6 @@
    }
    @Override
    public PageInfo<ParentPlatform> queryParentPlatformList(int page, int count) {
        PageHelper.startPage(page, count);
        List<ParentPlatform> all = platformMapper.getParentPlatformList();
        return new PageInfo<>(all);
    }
    @Override
    public ParentPlatform queryParentPlatByServerGBId(String platformGbId) {
        return platformMapper.getParentPlatByServerGBId(platformGbId);
    }
src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java
@@ -82,4 +82,9 @@
            return false;
        }
    }
    public static String getNowForISO8601() {
        LocalDateTime nowDateTime = LocalDateTime.now();
        return formatterISO8601.format(nowDateTime);
    }
}
src/main/java/com/genersoft/iot/vmp/utils/SpringBeanFactory.java
@@ -33,11 +33,11 @@
    /**
     * 获取对象 这里重写了bean方法,起主要作用
     */
    public static Object getBean(String beanId) throws BeansException {
    public static  <T> T getBean(String beanId) throws BeansException {
        if (applicationContext == null) {
            return null;
        }
        return applicationContext.getBean(beanId);
        return (T) applicationContext.getBean(beanId);
    }
    /**
src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java
@@ -5,15 +5,14 @@
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.utils.SpringBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import gov.nist.javax.sip.stack.UDPMessageChannel;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
/**    
 * @description:Redis工具类
 * @author: swwheihei
 * @date:   2020年5月6日 下午8:27:29
 * Redis工具类
 * @author swwheihei
 * @date 2020年5月6日 下午8:27:29
 */
@SuppressWarnings(value = {"rawtypes", "unchecked"})
public class RedisUtil {
@@ -21,9 +20,9 @@
    private static RedisTemplate redisTemplate;
    static {
        redisTemplate = (RedisTemplate)SpringBeanFactory.getBean("redisTemplate");
        redisTemplate = SpringBeanFactory.getBean("redisTemplate");
    }
    /**
     * 指定缓存失效时间
     * @param key 键
@@ -31,6 +30,9 @@
     * @return true / false
     */
    public static boolean expire(String key, long time) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
@@ -45,9 +47,11 @@
    /**
     * 根据 key 获取过期时间
     * @param key 键
     * @return
     */
    public static long getExpire(String key) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }
@@ -57,6 +61,9 @@
     * @return true / false
     */
    public static boolean hasKey(String key) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
@@ -71,6 +78,9 @@
     * @param key 键(一个或者多个)
     */
    public static boolean del(String... key) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            if (key != null && key.length > 0) {
                if (key.length == 1) {
@@ -95,6 +105,9 @@
     * @return 值
     */
    public static Object get(String key) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }
@@ -105,6 +118,9 @@
     * @return true / false
     */
    public static boolean set(String key, Object value) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
@@ -122,6 +138,9 @@
     * @return true / false
     */
    public static boolean set(String key, Object value, long time) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
@@ -142,6 +161,9 @@
     * @return
     */
    public static long incr(String key, long delta) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于 0");
        }
@@ -155,6 +177,9 @@
     * @return
     */
    public static long decr(String key, long delta) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于 0");
        }
@@ -170,6 +195,9 @@
     * @return 值
     */
    public static Object hget(String key, String item) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        return redisTemplate.opsForHash().get(key, item);
    }
@@ -179,6 +207,9 @@
     * @return 对应的多个键值
     */
    public static Map<Object, Object> hmget(String key) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        return redisTemplate.opsForHash().entries(key);
    }
@@ -189,6 +220,9 @@
     * @return true / false
     */
    public static boolean hmset(String key, Map<Object, Object> map) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
@@ -206,6 +240,9 @@
     * @return true / false
     */
    public static boolean hmset(String key, Map<Object, Object> map, long time) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
@@ -226,6 +263,9 @@
     * @return true / false
     */
    public static boolean hset(String key, String item, Object value) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
@@ -244,6 +284,9 @@
     * @return true / false
     */
    public static boolean hset(String key, String item, Object value, long time) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
@@ -262,6 +305,9 @@
     * @param item 项(可以多个,no null)
     */
    public static void hdel(String key, Object... item) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        redisTemplate.opsForHash().delete(key, item);
    }
@@ -272,6 +318,9 @@
     * @return true / false
     */
    public static boolean hHasKey(String key, String item) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        return redisTemplate.opsForHash().hasKey(key, item);
    }
@@ -283,6 +332,9 @@
     * @return
     */
    public static Double hincr(String key, String item, Double by) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        return redisTemplate.opsForHash().increment(key, item, by);
    }
@@ -294,6 +346,9 @@
     * @return
     */
    public static Double hdecr(String key, String item, Double by) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        return redisTemplate.opsForHash().increment(key, item, -by);
    }
@@ -305,6 +360,9 @@
     * @return 值
     */
    public static Set<Object> sGet(String key) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
@@ -320,6 +378,9 @@
     * @return true / false
     */
    public static boolean sHasKey(String key, Object value) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
@@ -335,6 +396,9 @@
     * @return 成功个数
     */
    public static long sSet(String key, Object... values) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
@@ -351,6 +415,9 @@
     * @return 成功放入个数
     */
    public static long sSet(String key, long time, Object... values) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0) {
@@ -369,6 +436,9 @@
     * @return 长度
     */
    public static long sGetSetSize(String key) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
@@ -384,6 +454,9 @@
     * @return 成功移除个数
     */
    public static long setRemove(String key, Object... values) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            return redisTemplate.opsForSet().remove(key, values);
        } catch (Exception e) {
@@ -401,6 +474,9 @@
     * @param score
     */
    public static void zAdd(Object key, Object value, double score) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        redisTemplate.opsForZSet().add(key, value, score);
    }
@@ -411,6 +487,9 @@
     * @param value
     */
    public static void zRemove(Object key, Object value) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        redisTemplate.opsForZSet().remove(key, value);
    }
@@ -422,6 +501,9 @@
     * @param delta -1 表示减 1 表示加1
     */
    public static Double zIncrScore(Object key, Object value, double delta) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        return redisTemplate.opsForZSet().incrementScore(key, value, delta);
    }
@@ -433,6 +515,9 @@
     * @return
     */
    public static Double zScore(Object key, Object value) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        return redisTemplate.opsForZSet().score(key, value);
    }
@@ -444,6 +529,9 @@
     * @return
     */
    public static Long zRank(Object key, Object value) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        return redisTemplate.opsForZSet().rank(key, value);
    }
@@ -454,6 +542,9 @@
     * @return
     */
    public static Long zSize(Object key) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        return redisTemplate.opsForZSet().zCard(key);
    }
@@ -467,7 +558,10 @@
     * @param end
     * @return
     */
    public static Set<Object> ZRange(Object key, int start, int end) {
    public static Set<Object> zRange(Object key, int start, int end) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        return redisTemplate.opsForZSet().range(key, start, end);
    }
    /**
@@ -479,6 +573,9 @@
     * @return
     */
    public static Set<ZSetOperations.TypedTuple<String>> zRangeWithScore(Object key, int start, int end) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
    }
    /**
@@ -492,6 +589,9 @@
     * @return
     */
    public static Set<String> zRevRange(Object key, int start, int end) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        return redisTemplate.opsForZSet().reverseRange(key, start, end);
    }
    /**
@@ -503,6 +603,9 @@
     * @return
     */
    public static Set<String> zSortRange(Object key, int min, int max) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        return redisTemplate.opsForZSet().rangeByScore(key, min, max);
    }
@@ -517,6 +620,9 @@
     * @return
     */
    public static List<Object> lGet(String key, long start, long end) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
@@ -531,6 +637,9 @@
     * @return 长度
     */
    public static long lGetListSize(String key) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
@@ -548,6 +657,9 @@
     * @return 值
     */
    public static Object lGetIndex(String key, long index) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
@@ -563,6 +675,9 @@
     * @return true / false
     */
    public static boolean lSet(String key, Object value) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
@@ -580,6 +695,9 @@
     * @return true / false
     */
    public static boolean lSet(String key, Object value, long time) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
@@ -599,6 +717,9 @@
     * @return true / false
     */
    public static boolean lSetList(String key, List<Object> values) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            redisTemplate.opsForList().rightPushAll(key, values);
            return true;
@@ -616,6 +737,9 @@
     * @return true / false
     */
    public static boolean lSetList(String key, List<Object> values, long time) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            redisTemplate.opsForList().rightPushAll(key, values);
            if (time > 0) {
@@ -636,6 +760,9 @@
     * @return true / false
     */
    public static boolean lUpdateIndex(String key, long index, Object value) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
@@ -655,6 +782,9 @@
     * @return
     */
    public static long lRemove(String key, long count, Object value) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            return redisTemplate.opsForList().remove(key, count, value);
        } catch (Exception e) {
@@ -669,6 +799,9 @@
     * @return
     */
    public static Object lLeftPop(String key) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        return redisTemplate.opsForList().leftPop(key);
    }
@@ -678,6 +811,9 @@
     * @return
     */
    public static Object lrightPop(String key) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        return redisTemplate.opsForList().rightPop(key);
    }
@@ -687,6 +823,9 @@
     * @return true / false
     */
    public static List<Object> keys(String key) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        try {
            Set<String> set = redisTemplate.keys(key);
            return new ArrayList<>(set);
@@ -727,6 +866,9 @@
     * @return
     */
    public static List<Object> scan(String query) {
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        Set<String> resultKeys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
            ScanOptions scanOptions = ScanOptions.scanOptions().match("*" + query + "*").count(1000).build();
            Cursor<byte[]> scan = connection.scan(scanOptions);
@@ -743,9 +885,10 @@
    //    ============================== 消息发送与订阅 ==============================
    public static void convertAndSend(String channel, JSONObject msg) {
//        redisTemplate.convertAndSend(channel, msg);
        if (redisTemplate == null) {
            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
        }
        redisTemplate.convertAndSend(channel, msg);
    }
}
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/platform/PlatformController.java
@@ -9,14 +9,13 @@
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.bean.PlatformCatalog;
import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
import com.genersoft.iot.vmp.gb28181.bean.TreeType;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.service.IPlatformChannelService;
import com.genersoft.iot.vmp.service.IPlatformService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.UpdateChannelParam;
import com.github.pagehelper.PageInfo;
@@ -26,10 +25,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import com.genersoft.iot.vmp.conf.SipConfig;
@@ -70,6 +66,9 @@
    @Autowired
    private DynamicTask dynamicTask;
    @Autowired
    private IPlatformService platformService;
    /**
     * 获取国标服务的配置
     *
@@ -95,8 +94,7 @@
    @Parameter(name = "id", description = "平台国标编号", required = true)
    @GetMapping("/info/{id}")
    public ParentPlatform getPlatform(@PathVariable String id) {
        ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(id);
        WVPResult<ParentPlatform> wvpResult = new WVPResult<>();
        ParentPlatform parentPlatform = platformService.queryPlatformByServerGBId(id);
        if (parentPlatform != null) {
            return  parentPlatform;
        } else {
@@ -117,7 +115,7 @@
    @Parameter(name = "count", description = "每页条数", required = true)
    public PageInfo<ParentPlatform> platforms(@PathVariable int page, @PathVariable int count) {
        PageInfo<ParentPlatform> parentPlatformPageInfo = storager.queryParentPlatformList(page, count);
        PageInfo<ParentPlatform> parentPlatformPageInfo = platformService.queryParentPlatformList(page, count);
        if (parentPlatformPageInfo.getList().size() > 0) {
            for (ParentPlatform platform : parentPlatformPageInfo.getList()) {
                platform.setMobilePositionSubscribe(subscribeHolder.getMobilePositionSubscribe(platform.getServerGBId()) != null);
@@ -136,7 +134,7 @@
    @Operation(summary = "添加上级平台信息")
    @PostMapping("/add")
    @ResponseBody
    public String addPlatform(@RequestBody ParentPlatform parentPlatform) {
    public void addPlatform(@RequestBody ParentPlatform parentPlatform) {
        if (logger.isDebugEnabled()) {
            logger.debug("保存上级平台信息API调用");
@@ -158,32 +156,16 @@
            throw new ControllerException(ErrorCode.ERROR400.getCode(), "error severPort");
        }
        ParentPlatform parentPlatformOld = storager.queryParentPlatByServerGBId(parentPlatform.getServerGBId());
        if (parentPlatformOld != null) {
            throw new ControllerException(ErrorCode.ERROR100.getCode(), "平台 " + parentPlatform.getServerGBId() + " 已存在");
        }
        parentPlatform.setCreateTime(DateUtil.getNow());
        parentPlatform.setUpdateTime(DateUtil.getNow());
        boolean updateResult = storager.updateParentPlatform(parentPlatform);
        boolean updateResult = platformService.add(parentPlatform);
        if (updateResult) {
            // 保存时启用就发送注册
            if (parentPlatform.isEnable()) {
                if (parentPlatformOld != null && parentPlatformOld.isStatus()) {
                    commanderForPlatform.unregister(parentPlatformOld, null, eventResult -> {
                        //  只要保存就发送注册
                        commanderForPlatform.register(parentPlatform, null, null);
                    });
                } else {
                    //  只要保存就发送注册
                    commanderForPlatform.register(parentPlatform, null, null);
                }
            } else if (parentPlatformOld != null && parentPlatformOld.isEnable() && !parentPlatform.isEnable()) { // 关闭启用时注销
                commanderForPlatform.unregister(parentPlatform, null, null);
            }
            return null;
        } else {
        if (!updateResult) {
            throw new ControllerException(ErrorCode.ERROR100.getCode(),"写入数据库失败");
        }
    }
@@ -197,7 +179,7 @@
    @Operation(summary = "保存上级平台信息")
    @PostMapping("/save")
    @ResponseBody
    public String savePlatform(@RequestBody ParentPlatform parentPlatform) {
    public void savePlatform(@RequestBody ParentPlatform parentPlatform) {
        if (logger.isDebugEnabled()) {
            logger.debug("保存上级平台信息API调用");
@@ -247,7 +229,6 @@
                // 停止订阅相关的定时任务
                subscribeHolder.removeAllSubscribe(parentPlatform.getServerGBId());
            }
            return null;
        } else {
            throw new ControllerException(ErrorCode.ERROR100.getCode(),"写入数据库失败");
        }
src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java
@@ -8,24 +8,21 @@
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.VersionInfo;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.IHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.utils.SpringBeanFactory;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import gov.nist.javax.sip.SipStackImpl;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.ehcache.xml.model.ThreadPoolsType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.sip.ListeningPoint;
@@ -42,7 +39,7 @@
public class ServerController {
    @Autowired
    private ZLMHttpHookSubscribe zlmHttpHookSubscribe;
    private ZlmHttpHookSubscribe zlmHttpHookSubscribe;
    @Autowired
    private IMediaServerService mediaServerService;
src/main/java/com/genersoft/iot/vmp/vmanager/streamPush/StreamPushController.java
@@ -5,6 +5,7 @@
import com.alibaba.excel.read.metadata.ReadSheet;
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.security.SecurityUtils;
import com.genersoft.iot.vmp.conf.security.dto.LoginUser;
import com.genersoft.iot.vmp.gb28181.bean.GbStream;
@@ -17,6 +18,7 @@
import com.genersoft.iot.vmp.service.IStreamPushService;
import com.genersoft.iot.vmp.service.impl.StreamPushUploadFileHandler;
import com.genersoft.iot.vmp.vmanager.bean.BatchGBStreamParam;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.StreamPushExcelDto;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.github.pagehelper.PageInfo;
@@ -95,11 +97,9 @@
    @PostMapping(value = "/save_to_gb")
    @ResponseBody
    @Operation(summary = "将推流添加到国标")
    public Object saveToGB(@RequestBody GbStream stream){
        if (streamPushService.saveToGB(stream)){
            return "success";
        }else {
            return "fail";
    public void saveToGB(@RequestBody GbStream stream){
        if (!streamPushService.saveToGB(stream)){
           throw new ControllerException(ErrorCode.ERROR100);
        }
    }
@@ -107,11 +107,9 @@
    @DeleteMapping(value = "/remove_form_gb")
    @ResponseBody
    @Operation(summary = "将推流移出到国标")
    public Object removeFormGB(@RequestBody GbStream stream){
        if (streamPushService.removeFromGB(stream)){
            return "success";
        }else {
            return "fail";
    public void removeFormGB(@RequestBody GbStream stream){
        if (!streamPushService.removeFromGB(stream)){
            throw new ControllerException(ErrorCode.ERROR100);
        }
    }
@@ -121,25 +119,21 @@
    @Operation(summary = "中止一个推流")
    @Parameter(name = "app", description = "应用名", required = true)
    @Parameter(name = "stream", description = "流id", required = true)
    public Object stop(String app, String streamId){
        if (streamPushService.stop(app, streamId)){
            return "success";
        }else {
            return "fail";
    public void stop(String app, String streamId){
        if (!streamPushService.stop(app, streamId)){
            throw new ControllerException(ErrorCode.ERROR100);
        }
    }
    @DeleteMapping(value = "/batchStop")
    @ResponseBody
    @Operation(summary = "中止多个推流")
    public Object batchStop(@RequestBody BatchGBStreamParam batchGBStreamParam){
    public void batchStop(@RequestBody BatchGBStreamParam batchGBStreamParam){
        if (batchGBStreamParam.getGbStreams().size() == 0) {
            return "fail";
            throw new ControllerException(ErrorCode.ERROR100);
        }
        if (streamPushService.batchStop(batchGBStreamParam.getGbStreams())){
            return "success";
        }else {
            return "fail";
        if (!streamPushService.batchStop(batchGBStreamParam.getGbStreams())){
            throw new ControllerException(ErrorCode.ERROR100);
        }
    }
@@ -249,7 +243,7 @@
    @Parameter(name = "app", description = "应用名", required = true)
    @Parameter(name = "stream", description = "流id", required = true)
    @Parameter(name = "mediaServerId", description = "媒体服务器id")
    public WVPResult<StreamInfo> getPlayUrl(@RequestParam String app,@RequestParam String stream,
    public StreamInfo getPlayUrl(@RequestParam String app,@RequestParam String stream,
                                            @RequestParam(required = false) String mediaServerId){
        boolean authority = false;
        // 是否登陆用户, 登陆用户返回完整信息
@@ -257,52 +251,38 @@
        if (userInfo!= null) {
            authority = true;
        }
        WVPResult<StreamInfo> result = new WVPResult<>();
        StreamPushItem push = streamPushService.getPush(app, stream);
        if (push != null && !push.isSelf()) {
            result.setCode(-1);
            result.setMsg("来自其他平台的推流信息");
            return result;
            throw new ControllerException(ErrorCode.ERROR100.getCode(), "来自其他平台的推流信息");
        }
        StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId, authority);
        if (streamInfo != null){
            result.setCode(0);
            result.setMsg("success");
            result.setData(streamInfo);
        }else {
            result.setCode(-1);
            result.setMsg("获取播放地址失败");
        if (streamInfo == null){
            throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取播放地址失败");
        }
        return result;
        return streamInfo;
    }
    /**
     * 获取推流播放地址
     * 添加推流信息
     * @param stream 推流信息
     * @return
     */
    @PostMapping(value = "/add")
    @ResponseBody
    @Operation(summary = "停止视频回放")
    public WVPResult<StreamInfo> add(@RequestBody StreamPushItem stream){
    @Operation(summary = "添加推流信息")
    public void add(@RequestBody StreamPushItem stream){
        if (ObjectUtils.isEmpty(stream.getGbId())) {
            return new WVPResult<>(400, "国标ID不可为空", null);
            throw new ControllerException(ErrorCode.ERROR400.getCode(), "国标ID不可为空");
        }
        if (ObjectUtils.isEmpty(stream.getApp()) && ObjectUtils.isEmpty(stream.getStream())) {
            return new WVPResult<>(400, "app或stream不可为空", null);
            throw new ControllerException(ErrorCode.ERROR400.getCode(), "app或stream不可为空");
        }
        stream.setStatus(false);
        stream.setPushIng(false);
        stream.setAliveSecond(0L);
        stream.setTotalReaderCount("0");
        boolean result = streamPushService.add(stream);
        if (result) {
            return new WVPResult<>(0, "success", null);
        }else {
            return new WVPResult<>(-1, "fail", null);
        if (!streamPushService.add(stream)) {
            throw new ControllerException(ErrorCode.ERROR100);
        }
    }
}
src/main/resources/all-application.yml
@@ -179,8 +179,6 @@
    platform-play-timeout: 60000
    # 是否开启接口鉴权
    interface-authentication: true
    # 自动配置redis 可以过期事件
    redis-config: true
    # 接口鉴权例外的接口, 即不进行接口鉴权的接口,尽量详细书写,尽量不用/**,至少两级目录
    interface-authentication-excludes:
        - /api/v1/**
src/main/resources/logback-spring-local.xml
@@ -77,25 +77,35 @@
        </encoder>
    </appender>
    <!-- 生成 SIP日志追加 -->
    <appender name="sipRollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--历史日志文件输出的文件名 -->
            <FileNamePattern>${LOG_HOME}/sip-%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <!--日志文件保留天数 -->
            <MaxHistory>30</MaxHistory>
            <maxFileSize>50MB</maxFileSize>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>
        </encoder>
    </appender>
    <!-- 日志输出级别 -->
    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="RollingFile" />
        <appender-ref ref="RollingFileError" />
    </root>
<!--    <logger name="com.genersoft.iot.vmp.storager.dao" level="INFO">-->
<!--        <appender-ref ref="STDOUT"/>-->
<!--    </logger>-->
<!--    <logger name="com.genersoft.iot.vmp.gb28181" level="INFO">-->
<!--        <appender-ref ref="STDOUT"/>-->
<!--    </logger>-->
    <logger name="GB28181_SIP" level="debug" additivity="true">
        <appender-ref ref="RollingFileError"/>
        <appender-ref ref="sipRollingFile"/>
    </logger>
    <!--记录druid-sql的记录-->
    <logger name="druid.sql.Statement" level="debug" additivity="true">
    <logger name="com.genersoft.iot.vmp.storager.dao" level="info" additivity="true">
        <!--AppenderRef ref="Console"/-->
        <!--        <appender-ref ref="RollingFile"/>-->
        <appender-ref ref="RollingFileError"/>
        <appender-ref ref="druidSqlRollingFile"/>
    </logger>
web_src/src/components/CloudRecord.vue
@@ -1,7 +1,11 @@
<template>
    <div id="app" style="width: 100%">
    <div class="page-header">
      <div class="page-title">云端录像</div>
      <div class="page-title">
        <el-page-header v-if="recordDetail" @back="backToList" content="云端录像"></el-page-header>
        <div v-if="!recordDetail">云端录像</div>
      </div>
      <div class="page-header-btn">
        节点选择:
        <el-select size="mini" @change="chooseMediaChange" style="width: 16rem; margin-right: 1rem;" v-model="mediaServerId" placeholder="请选择" :disabled="recordDetail">
@@ -183,7 +187,7 @@
        }).catch(function (error) {
          console.log(error);
        });
      }
      },
        }
web_src/src/components/CloudRecordDetail.vue
@@ -1,7 +1,9 @@
<template>
    <div id="recordDetail">
        <el-container>
      <el-aside width="300px">
        <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 class="record-list-box" :style="recordListStyle">
@@ -423,6 +425,9 @@
        }).catch(function (error) {
          console.log(error);
        });
      },
      goBack(){
        this.$router.push('/cloudRecord');
      }
        }
    };
web_src/src/components/Login.vue
@@ -66,10 +66,6 @@
      //登录请求
      toLogin(){
          //一般要跟后端了解密码的加密规则
          //这里例子用的哈希算法来自./js/sha1.min.js
          //需要想后端发送的登录参数
          let loginParam = {
              username: this.username,
@@ -78,12 +74,17 @@
      var that = this;
      //设置在登录状态
      this.isLoging = true;
      let timeoutTask = setTimeout(()=>{
        that.$message.error("登录超时");
        that.isLoging = false;
      }, 1000)
      this.$axios({
          method: 'get',
        url:"/api/user/login",
        params: loginParam
      }).then(function (res) {
        window.clearTimeout(timeoutTask)
        console.log(JSON.stringify(res));
          if (res.data.code === 0 ) {
            that.$cookies.set("session", {"username": that.username,"roleId":res.data.data.role.id}) ;
@@ -99,6 +100,8 @@
              });
          }
      }).catch(function (error) {
        console.log(error)
        window.clearTimeout(timeoutTask)
        that.$message.error(error.response.data.msg);
        that.isLoging = false;
      });
web_src/src/components/dialog/MediaServerEdit.vue
@@ -357,7 +357,7 @@
      var result = false;
      var that = this;
      await that.$axios({
        method: 'post',
        method: 'get',
        url:`/api/platform/exit/${deviceGbId}`
      }).then(function (res) {
        result = res.data;
web_src/src/components/dialog/StreamProxyEdit.vue
@@ -263,7 +263,7 @@
      var result = false;
      var that = this;
      await that.$axios({
        method: 'post',
        method: 'get',
        url:`/api/platform/exit/${deviceGbId}`
      }).then(function (res) {
        result = res.data;
web_src/src/components/dialog/devicePlayer.vue
@@ -386,7 +386,7 @@
            if (tab.name === "codec") {
                this.$axios({
                    method: 'get',
                    url: '/zlm/' +this.mediaServerId+ '/index/api/getMediaInfo?vhost=__defaultVhost__&schema=rtmp&app='+ this.app +'&stream='+ this.streamId
                    url: '/zlm/' +this.mediaServerId+ '/index/api/getMediaInfo?vhost=__defaultVhost__&schema=rtsp&app='+ this.app +'&stream='+ this.streamId
                }).then(function (res) {
                    that.tracksLoading = false;
                    if (res.data.code == 0 && res.data.tracks) {
web_src/src/components/dialog/platformEdit.vue
@@ -268,30 +268,29 @@
      }
    },
    saveForm: function (){
      var that = this;
      that.$axios({
      this.$axios({
        method: 'post',
        url: this.saveUrl,
        data: that.platform
      }).then(function (res) {
        data: this.platform
      }).then((res) =>{
        if (res.data.code === 0) {
          that.$message({
          this.$message({
            showClose: true,
            message: "保存成功",
            type: "success",
          });
          that.showDialog = false;
          if (that.listChangeCallback != null) {
            that.listChangeCallback();
          this.showDialog = false;
          if (this.listChangeCallback != null) {
            this.listChangeCallback();
          }
        }else {
          that.$message({
          this.$message({
            showClose: true,
            message: res.data.msg,
            type: "error",
          });
        }
      }).catch(function (error) {
      }).catch((error)=> {
        console.log(error);
      });
    },
@@ -328,7 +327,7 @@
      var result = false;
      var that = this;
      await that.$axios({
                method: 'post',
                method: 'get',
                url:`/api/platform/exit/${deviceGbId}`})
        .then(function (res) {
            if (res.data.code === 0) {
web_src/src/components/dialog/pushStreamEdit.vue
@@ -158,7 +158,7 @@
      var result = false;
      var that = this;
      await that.$axios({
        method:"post",
        method:"get",
        url:`/api/platform/exit/${deviceGbId}`
      }).then(function (res) {
        result = res.data;