hotcoffie
2022-05-17 1d753b48c023090430c2931fd8e3a45a382eddf8
Merge branch '648540858:wvp-28181-2.0' into wvp-28181-2.0
67个文件已修改
5个文件已添加
8个文件已删除
2400 ■■■■ 已修改文件
README.md 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/ThreadPoolTaskConfig.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/runner/SipDeviceRunner.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/CatalogData.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java 37 ●●●●● 补丁 | 查看 | 原始文档 | 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 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/device/RequestTimeoutEvent.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/device/RequestTimeoutEventImpl.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/KeepaliveTimeoutListenerForPlatform.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/KeepliveTimeoutListener.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/OfflineEvent.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/OfflineEventListener.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEvent.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEventListener.java 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/session/RecordDataCatch.java 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/task/SipDeviceRunner.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/CheckForAllRecordsThread.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java 43 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/RecordInfoQueryMessageHandler.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceInfoResponseMessageHandler.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceStatusResponseMessageHandler.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/PresetQueryResponseMessageHandler.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java 101 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/utils/DateUtil.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java 76 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMStatusEventListener.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IDeviceService.java 69 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java 183 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/RedisAlarmMsgListener.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java 65 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/MobilePosition/MobilePositionController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/alarm/AlarmController.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/log/LogController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/user/RoleController.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/all-application.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-docker.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/genersoft/iot/vmp/service/impl/DeviceAlarmServiceImplTest.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/genersoft/iot/vmp/service/impl/RoleServiceImplTest.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/genersoft/iot/vmp/service/impl/UserServiceImplTest.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/channelList.vue 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/control.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/devicePlayer.vue 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/recordDownload.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md
@@ -106,7 +106,6 @@
- [X] 添加RTMP视频
- [X] 云端录像(需要部署单独服务配合使用)
- [X] 多流媒体节点,自动选择负载最低的节点使用。
- [X] 支持使用mysql作为数据库,默认sqlite3,开箱即用。
- [X] WEB端支持播放H264与H265,音频支持G.711A/G.711U/AAC,覆盖国标常用编码格式。
[//]: # (# docker快速体验)
pom.xml
@@ -101,13 +101,6 @@
            <version>8.0.22</version>
        </dependency>
        <!-- 添加sqlite-jdbc数据库驱动 -->
        <dependency>
            <groupId>org.xerial</groupId>
            <artifactId>sqlite-jdbc</artifactId>
            <version>3.32.3.2</version>
        </dependency>
        <!--Mybatis分页插件 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
@@ -47,8 +47,6 @@
    public static final String PLATFORM_SEND_RTP_INFO_PREFIX = "VMP_PLATFORM_SEND_RTP_INFO_";
    public static final String EVENT_ONLINE_REGISTER = "1";
    public static final String EVENT_ONLINE_KEEPLIVE = "2";
    public static final String EVENT_ONLINE_MESSAGE = "3";
src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java
@@ -4,6 +4,7 @@
import com.genersoft.iot.vmp.conf.security.SecurityUtils;
import com.genersoft.iot.vmp.service.ILogService;
import com.genersoft.iot.vmp.storager.dao.dto.LogDto;
import com.genersoft.iot.vmp.utils.DateUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -16,7 +17,6 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.SimpleDateFormat;
/**
 * @author lin
@@ -26,7 +26,6 @@
    private final static Logger logger = LoggerFactory.getLogger(ApiAccessFilter.class);
    private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @Autowired
    private UserSetting userSetting;
@@ -58,7 +57,7 @@
            logDto.setTiming(System.currentTimeMillis() - start);
            logDto.setType(servletRequest.getMethod());
            logDto.setUri(servletRequest.getRequestURI());
            logDto.setCreateTime(format.format(System.currentTimeMillis()));
            logDto.setCreateTime(DateUtil.getNow());
            logService.add(logDto);
//            logger.warn("[Api Access]  [{}] [{}] [{}] [{}] [{}] {}ms",
//                    uriName, servletRequest.getMethod(), servletRequest.getRequestURI(), servletRequest.getRemoteAddr(), HttpStatus.valueOf(servletResponse.getStatus()),
src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java
@@ -1,7 +1,6 @@
package com.genersoft.iot.vmp.conf;
import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -9,25 +8,27 @@
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.time.Instant;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
 * 动态定时任务
 * @author lin
 */
@Component
public class DynamicTask {
    private Logger logger = LoggerFactory.getLogger(DynamicTask.class);
    private final Logger logger = LoggerFactory.getLogger(DynamicTask.class);
    @Autowired
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;
    private Map<String, ScheduledFuture<?>> futureMap = new ConcurrentHashMap<>();
    private Map<String, Runnable> runnableMap = new ConcurrentHashMap<>();
    private final Map<String, ScheduledFuture<?>> futureMap = new ConcurrentHashMap<>();
    private final Map<String, Runnable> runnableMap = new ConcurrentHashMap<>();
    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
@@ -43,27 +44,27 @@
     * 循环执行的任务
     * @param key 任务ID
     * @param task 任务
     * @param cycleForCatalog 间隔
     * @param cycleForCatalog 间隔 毫秒
     * @return
     */
    public void startCron(String key, Runnable task, int cycleForCatalog) {
        ScheduledFuture future = futureMap.get(key);
        ScheduledFuture<?> future = futureMap.get(key);
        if (future != null) {
            if (future.isCancelled()) {
                logger.info("任务【{}】已存在但是关闭状态!!!", key);
                logger.debug("任务【{}】已存在但是关闭状态!!!", key);
            } else {
                logger.info("任务【{}】已存在且已启动!!!", key);
                logger.debug("任务【{}】已存在且已启动!!!", key);
                return;
            }
        }
        // scheduleWithFixedDelay 必须等待上一个任务结束才开始计时period, cycleForCatalog表示执行的间隔
        future = threadPoolTaskScheduler.scheduleAtFixedRate(task, cycleForCatalog * 1000L);
        future = threadPoolTaskScheduler.scheduleAtFixedRate(task, cycleForCatalog);
        if (future != null){
            futureMap.put(key, future);
            runnableMap.put(key, task);
            logger.info("任务【{}】启动成功!!!", key);
            logger.debug("任务【{}】启动成功!!!", key);
        }else {
            logger.info("任务【{}】启动失败!!!", key);
            logger.debug("任务【{}】启动失败!!!", key);
        }
    }
@@ -76,25 +77,27 @@
     */
    public void startDelay(String key, Runnable task, int delay) {
        stop(key);
        Date starTime = new Date(System.currentTimeMillis() + delay);
        // 获取执行的时刻
        Instant startInstant = Instant.now().plusMillis(TimeUnit.MILLISECONDS.toMillis(delay));
        ScheduledFuture future = futureMap.get(key);
        if (future != null) {
            if (future.isCancelled()) {
                logger.info("任务【{}】已存在但是关闭状态!!!", key);
                logger.debug("任务【{}】已存在但是关闭状态!!!", key);
            } else {
                logger.info("任务【{}】已存在且已启动!!!", key);
                logger.debug("任务【{}】已存在且已启动!!!", key);
                return;
            }
        }
        // scheduleWithFixedDelay 必须等待上一个任务结束才开始计时period, cycleForCatalog表示执行的间隔
        future = threadPoolTaskScheduler.schedule(task, starTime);
        future = threadPoolTaskScheduler.schedule(task, startInstant);
        if (future != null){
            futureMap.put(key, future);
            runnableMap.put(key, task);
            logger.info("任务【{}】启动成功!!!", key);
            logger.debug("任务【{}】启动成功!!!", key);
        }else {
            logger.info("任务【{}】启动失败!!!", key);
            logger.debug("任务【{}】启动失败!!!", key);
        }
    }
src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java
@@ -1,12 +1,11 @@
package com.genersoft.iot.vmp.conf;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.utils.DateUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
@Configuration("mediaConfig")
public class MediaConfig{
@@ -206,9 +205,8 @@
        mediaServerItem.setRecordAssistPort(recordAssistPort);
        mediaServerItem.setHookAliveInterval(120);
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        mediaServerItem.setCreateTime(format.format(System.currentTimeMillis()));
        mediaServerItem.setUpdateTime(format.format(System.currentTimeMillis()));
        mediaServerItem.setCreateTime(DateUtil.getNow());
        mediaServerItem.setUpdateTime(DateUtil.getNow());
        return mediaServerItem;
    }
src/main/java/com/genersoft/iot/vmp/conf/ThreadPoolTaskConfig.java
@@ -35,10 +35,11 @@
     * 允许线程空闲时间(单位:默认为秒)
     */
    private static final int keepAliveTime = 30;
    /**
     * 缓冲队列大小
     */
    private static final int queueCapacity = 500;
    private static final int queueCapacity = 10000;
    /**
     * 线程池名前缀
     */
src/main/java/com/genersoft/iot/vmp/conf/runner/SipDeviceRunner.java
File was deleted
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
@@ -48,8 +48,15 @@
        Properties properties = new Properties();
        properties.setProperty("javax.sip.STACK_NAME", "GB28181_SIP");
        properties.setProperty("javax.sip.IP_ADDRESS", sipConfig.getMonitorIp());
        /**
         * 完整配置参考 gov.nist.javax.sip.SipStackImpl,需要下载源码
         * gov/nist/javax/sip/SipStackImpl.class
         */
        properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "true");
        properties.setProperty("gov.nist.javax.sip.DELIVER_UNSOLICITED_NOTIFY", "true"); // 接收所有notify请求,即使没有订阅
        properties.setProperty("gov.nist.javax.sip.DELIVER_TERMINATED_EVENT_FOR_NULL_DIALOG", "true"); // 为_NULL _对话框传递_终止的_事件
        properties.setProperty("gov.nist.javax.sip.RELEASE_REFERENCES_STRATEGY", "Normal"); // 会话清理策略
        properties.setProperty("gov.nist.javax.sip.RELIABLE_CONNECTION_KEEP_ALIVE_TIMEOUT", "10");
        /**
         * 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
@@ -74,11 +81,11 @@
            tcpSipProvider.setDialogErrorsAutomaticallyHandled();
            tcpSipProvider.addSipListener(sipProcessorObserver);
//            tcpSipProvider.setAutomaticDialogSupportEnabled(false);
            logger.info("Sip Server TCP 启动成功 port {" + sipConfig.getMonitorIp() + ":" + sipConfig.getPort() + "}");
            logger.info("[Sip Server] TCP 启动成功 {}:{}", sipConfig.getMonitorIp(), sipConfig.getPort());
        } catch (TransportNotSupportedException e) {
            e.printStackTrace();
        } catch (InvalidArgumentException e) {
            logger.error("无法使用 [ {}:{} ]作为SIP[ TCP ]服务,可排查: 1. sip.monitor-ip 是否为本机网卡IP; 2. sip.port 是否已被占用"
            logger.error("[Sip Server]  无法使用 [ {}:{} ]作为SIP[ TCP ]服务,可排查: 1. sip.monitor-ip 是否为本机网卡IP; 2. sip.port 是否已被占用"
                    , sipConfig.getMonitorIp(), sipConfig.getPort());
        } catch (TooManyListenersException e) {
            e.printStackTrace();
@@ -101,14 +108,14 @@
        } catch (TransportNotSupportedException e) {
            e.printStackTrace();
        } catch (InvalidArgumentException e) {
            logger.error("无法使用 [ {}:{} ]作为SIP[ UDP ]服务,可排查: 1. sip.monitor-ip 是否为本机网卡IP; 2. sip.port 是否已被占用"
            logger.error("[Sip Server]  无法使用 [ {}:{} ]作为SIP[ UDP ]服务,可排查: 1. sip.monitor-ip 是否为本机网卡IP; 2. sip.port 是否已被占用"
                    , sipConfig.getMonitorIp(), sipConfig.getPort());
        } catch (TooManyListenersException e) {
            e.printStackTrace();
        } catch (ObjectInUseException e) {
            e.printStackTrace();
        }
        logger.info("Sip Server UDP 启动成功 port [" + sipConfig.getMonitorIp() + ":" + sipConfig.getPort() + "]");
        logger.info("[Sip Server] UDP 启动成功 {}:{}", sipConfig.getMonitorIp(), sipConfig.getPort());
        return udpSipProvider;
    }
src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java
@@ -27,8 +27,7 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat;
import java.util.Date;
import java.time.Instant;
import java.util.Random;
import javax.sip.address.URI;
@@ -90,17 +89,12 @@
     * @return a generated nonce.
     */
    private String generateNonce() {
        // Get the time of day and run MD5 over it.
        Date date = new Date();
        long time = date.getTime();
        long time = Instant.now().toEpochMilli();
        Random rand = new Random();
        long pad = rand.nextLong();
        // String nonceString = (new Long(time)).toString()
        //         + (new Long(pad)).toString();
        String nonceString = Long.valueOf(time).toString()
                + Long.valueOf(pad).toString();
        byte mdbytes[] = messageDigest.digest(nonceString.getBytes());
        // Convert the mdbytes array into a hex string.
        return toHexString(mdbytes);
    }
src/main/java/com/genersoft/iot/vmp/gb28181/bean/CatalogData.java
@@ -1,13 +1,13 @@
package com.genersoft.iot.vmp.gb28181.bean;
import java.util.Date;
import java.time.Instant;
import java.util.List;
public class CatalogData {
    private int sn; // 命令序列号
    private int total;
    private List<DeviceChannel> channelList;
    private Date lastTime;
    private Instant lastTime;
    private Device device;
    private String errorMsg;
@@ -41,11 +41,11 @@
        this.channelList = channelList;
    }
    public Date getLastTime() {
    public Instant getLastTime() {
        return lastTime;
    }
    public void setLastTime(Date lastTime) {
    public void setLastTime(Instant lastTime) {
        this.lastTime = lastTime;
    }
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
@@ -100,11 +100,6 @@
    private String mediaServerId;
    /**
     * 首次注册
     */
    private boolean firsRegister;
    /**
     * 字符集, 支持 UTF-8 与 GB2312
     */
    private String charset ;
@@ -277,14 +272,6 @@
    public void setMediaServerId(String mediaServerId) {
        this.mediaServerId = mediaServerId;
    }
    public boolean isFirsRegister() {
        return firsRegister;
    }
    public void setFirsRegister(boolean firsRegister) {
        this.firsRegister = firsRegister;
    }
    public String getCharset() {
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java
@@ -1,8 +1,6 @@
package com.genersoft.iot.vmp.gb28181.bean;
//import gov.nist.javax.sip.header.SIPDate;
import java.time.Instant;
import java.util.List;
/**    
@@ -21,6 +19,8 @@
    private String name;
    
    private int sumNum;
    private Instant lastTime;
    
    private List<RecordItem> recordList;
@@ -71,4 +71,12 @@
    public void setSn(String sn) {
        this.sn = sn;
    }
    public Instant getLastTime() {
        return lastTime;
    }
    public void setLastTime(Instant lastTime) {
        this.lastTime = lastTime;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java
@@ -1,11 +1,12 @@
package com.genersoft.iot.vmp.gb28181.bean;
import com.genersoft.iot.vmp.utils.DateUtil;
import org.jetbrains.annotations.NotNull;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.time.Instant;
import java.time.temporal.TemporalAccessor;
/**
 * @description:设备录像bean 
@@ -116,18 +117,17 @@
    @Override
    public int compareTo(@NotNull RecordItem recordItem) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            Date startTime_now = sdf.parse(startTime);
            Date startTime_param = sdf.parse(recordItem.getStartTime());
            if (startTime_param.compareTo(startTime_now) > 0) {
        TemporalAccessor startTimeNow = DateUtil.formatter.parse(startTime);
        TemporalAccessor startTimeParam = DateUtil.formatter.parse(recordItem.getStartTime());
        Instant startTimeParamInstant = Instant.from(startTimeParam);
        Instant startTimeNowInstant = Instant.from(startTimeNow);
        if (startTimeNowInstant.equals(startTimeParamInstant)) {
            return 0;
        }else if (Instant.from(startTimeParam).isAfter(Instant.from(startTimeNow)) ) {
                return -1;
            }else {
                return 1;
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return 0;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java
@@ -77,6 +77,21 @@
    private String CallId;
    /**
     * 发送时,rtp的pt(uint8_t),不传时默认为96
     */
    private int pt = 96;
    /**
     * 发送时,rtp的负载类型。为true时,负载为ps;为false时,为es;
     */
    private boolean usePs = true;
    /**
     * 当usePs 为false时,有效。为1时,发送音频;为0时,发送视频;不传时默认为0
     */
    private boolean onlyAudio = false;
    /**
     * 播放类型
     */
    private InviteStreamType playType;
@@ -221,5 +236,27 @@
        this.dialog = dialog;
    }
    public int getPt() {
        return pt;
    }
    public void setPt(int pt) {
        this.pt = pt;
    }
    public boolean isUsePs() {
        return usePs;
    }
    public void setUsePs(boolean usePs) {
        this.usePs = usePs;
    }
    public boolean isOnlyAudio() {
        return onlyAudio;
    }
    public void setOnlyAudio(boolean onlyAudio) {
        this.onlyAudio = onlyAudio;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java
@@ -61,12 +61,11 @@
        // 添加任务处理GPS定时推送
        dynamicTask.startCron(key, new MobilePositionSubscribeHandlerTask(redisCatchStorage, sipCommanderForPlatform,
                storager,  platformId, subscribeInfo.getSn(), key, this, dynamicTask),
                subscribeInfo.getGpsInterval());
                subscribeInfo.getGpsInterval() * 1000);
        String taskOverdueKey = taskOverduePrefix +  "MobilePosition_" + platformId;
        dynamicTask.stop(taskOverdueKey);
        // 添加任务处理订阅过期
        dynamicTask.startDelay(taskOverdueKey, () -> {
                    System.out.println("订阅过期");
                    removeMobilePositionSubscribe(subscribeInfo.getId());
                },
                subscribeInfo.getExpires() * 1000);
src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java
@@ -1,7 +1,7 @@
package com.genersoft.iot.vmp.gb28181.event;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.event.offline.OfflineEvent;
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;
@@ -11,12 +11,11 @@
import com.genersoft.iot.vmp.media.zlm.event.ZLMOnlineEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.gb28181.event.alarm.AlarmEvent;
import com.genersoft.iot.vmp.gb28181.event.online.OnlineEvent;
import javax.sip.TimeoutEvent;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -32,28 +31,6 @@
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;
    public void onlineEventPublish(Device device, String from, int expires) {
        OnlineEvent onEvent = new OnlineEvent(this);
        onEvent.setDevice(device);
        onEvent.setFrom(from);
        onEvent.setExpires(expires);
        applicationEventPublisher.publishEvent(onEvent);
    }
    public void onlineEventPublish(Device device, String from) {
        OnlineEvent onEvent = new OnlineEvent(this);
        onEvent.setDevice(device);
        onEvent.setFrom(from);
        applicationEventPublisher.publishEvent(onEvent);
    }
    public void outlineEventPublish(String deviceId, String from){
        OfflineEvent outEvent = new OfflineEvent(this);
        outEvent.setDeviceId(deviceId);
        outEvent.setFrom(from);
        applicationEventPublisher.publishEvent(outEvent);
    }
    /**
     * 平台心跳到期事件
@@ -115,6 +92,13 @@
    }
    public void requestTimeOut(TimeoutEvent timeoutEvent) {
        RequestTimeoutEvent requestTimeoutEvent = new RequestTimeoutEvent(this);
        requestTimeoutEvent.setTimeoutEvent(timeoutEvent);
        applicationEventPublisher.publishEvent(requestTimeoutEvent);
    }
    /**
     *
     * @param platformId
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java
@@ -9,11 +9,14 @@
import javax.sip.*;
import javax.sip.header.CallIdHeader;
import javax.sip.message.Response;
import java.util.Calendar;
import java.util.Date;
import java.time.Instant;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
 * @author lin
 */
@Component
public class SipSubscribe {
@@ -23,28 +26,25 @@
    private Map<String, SipSubscribe.Event> okSubscribes = new ConcurrentHashMap<>();
    private Map<String, Date> okTimeSubscribes = new ConcurrentHashMap<>();
    private Map<String, Date> errorTimeSubscribes = new ConcurrentHashMap<>();
    private Map<String, Instant> okTimeSubscribes = new ConcurrentHashMap<>();
    private Map<String, Instant> errorTimeSubscribes = new ConcurrentHashMap<>();
    //    @Scheduled(cron="*/5 * * * * ?")   //每五秒执行一次
//    @Scheduled(fixedRate= 100 * 60 * 60 )
    @Scheduled(cron="0 0/5 * * * ?")   //每5分钟执行一次
    public void execute(){
        logger.info("[定时任务] 清理过期的SIP订阅信息");
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.set(Calendar.MINUTE, calendar.get(Calendar.MINUTE) - 5);
        Instant instant = Instant.now().minusMillis(TimeUnit.MINUTES.toMillis(5));
        for (String key : okTimeSubscribes.keySet()) {
            if (okTimeSubscribes.get(key).before(calendar.getTime())){
//                logger.info("[定时任务] 清理过期的订阅信息: {}", key);
            if (okTimeSubscribes.get(key).isBefore(instant)){
                okSubscribes.remove(key);
                okTimeSubscribes.remove(key);
            }
        }
        for (String key : errorTimeSubscribes.keySet()) {
            if (errorTimeSubscribes.get(key).before(calendar.getTime())){
//                logger.info("[定时任务] 清理过期的订阅信息: {}", key);
            if (errorTimeSubscribes.get(key).isBefore(instant)){
                errorSubscribes.remove(key);
                errorTimeSubscribes.remove(key);
            }
@@ -117,12 +117,12 @@
    public void addErrorSubscribe(String key, SipSubscribe.Event event) {
        errorSubscribes.put(key, event);
        errorTimeSubscribes.put(key, new Date());
        errorTimeSubscribes.put(key, Instant.now());
    }
    public void addOkSubscribe(String key, SipSubscribe.Event event) {
        okSubscribes.put(key, event);
        okTimeSubscribes.put(key, new Date());
        okTimeSubscribes.put(key, Instant.now());
    }
    public SipSubscribe.Event getErrorSubscribe(String key) {
src/main/java/com/genersoft/iot/vmp/gb28181/event/device/RequestTimeoutEvent.java
New file
@@ -0,0 +1,25 @@
package com.genersoft.iot.vmp.gb28181.event.device;
import org.springframework.context.ApplicationEvent;
import javax.sip.TimeoutEvent;
/**
 * @author lin
 */
public class RequestTimeoutEvent extends ApplicationEvent {
    public RequestTimeoutEvent(Object source) {
        super(source);
    }
    private TimeoutEvent timeoutEvent;
    public TimeoutEvent getTimeoutEvent() {
        return timeoutEvent;
    }
    public void setTimeoutEvent(TimeoutEvent timeoutEvent) {
        this.timeoutEvent = timeoutEvent;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/event/device/RequestTimeoutEventImpl.java
New file
@@ -0,0 +1,41 @@
package com.genersoft.iot.vmp.gb28181.event.device;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.service.IDeviceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import javax.sip.ClientTransaction;
import javax.sip.address.SipURI;
import javax.sip.header.CallIdHeader;
import javax.sip.header.ToHeader;
import javax.sip.message.Request;
/**
 * @author lin
 */
@Component
public class RequestTimeoutEventImpl implements ApplicationListener<RequestTimeoutEvent> {
    @Autowired
    private IDeviceService deviceService;
    @Override
    public void onApplicationEvent(RequestTimeoutEvent event) {
        ClientTransaction clientTransaction = event.getTimeoutEvent().getClientTransaction();
        if (clientTransaction != null) {
            Request request = clientTransaction.getRequest();
            if (request != null) {
                String host = ((SipURI) request.getRequestURI()).getHost();
                int port = ((SipURI) request.getRequestURI()).getPort();
                Device device = deviceService.getDeviceByHostAndPort(host, port);
                if (device == null) {
                    return;
                }
                deviceService.offline(device.getDeviceId());
            }
        }
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/KeepaliveTimeoutListenerForPlatform.java
@@ -17,9 +17,8 @@
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
/**    
 * @description:设备心跳超时监听,借助redis过期特性,进行监听,监听到说明设备心跳超时,发送离线事件
 * @author: swwheihei
 * @date:   2020年5月6日 上午11:35:46
 * 设备心跳超时监听,借助redis过期特性,进行监听,监听到说明设备心跳超时,发送离线事件
 * @author swwheihei
 */
@Component
public class KeepaliveTimeoutListenerForPlatform extends RedisKeyExpirationEventMessageListener {
@@ -55,25 +54,18 @@
        // 平台心跳到期,需要重发, 判断是否已经多次未收到心跳回复, 多次未收到,则重新发起注册, 注册尝试多次未得到回复,则认为平台离线
        String PLATFORM_KEEPLIVEKEY_PREFIX = VideoManagerConstants.PLATFORM_KEEPALIVE_PREFIX + userSetting.getServerId() + "_";
        String PLATFORM_REGISTER_PREFIX = VideoManagerConstants.PLATFORM_REGISTER_PREFIX + userSetting.getServerId() + "_";
        String KEEPLIVEKEY_PREFIX = VideoManagerConstants.KEEPLIVEKEY_PREFIX + userSetting.getServerId() + "_";
        String REGISTER_INFO_PREFIX = VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + userSetting.getServerId() + "_";
        if (expiredKey.startsWith(PLATFORM_KEEPLIVEKEY_PREFIX)) {
            String platformGBId = expiredKey.substring(PLATFORM_KEEPLIVEKEY_PREFIX.length(),expiredKey.length());
            ParentPlatform platform = storager.queryParentPlatByServerGBId(platformGBId);
            String platformGbId = expiredKey.substring(PLATFORM_KEEPLIVEKEY_PREFIX.length());
            ParentPlatform platform = storager.queryParentPlatByServerGBId(platformGbId);
            if (platform != null) {
                publisher.platformKeepaliveExpireEventPublish(platformGBId);
                publisher.platformKeepaliveExpireEventPublish(platformGbId);
            }
        }else if (expiredKey.startsWith(PLATFORM_REGISTER_PREFIX)) {
            String platformGBId = expiredKey.substring(PLATFORM_REGISTER_PREFIX.length(),expiredKey.length());
            ParentPlatform platform = storager.queryParentPlatByServerGBId(platformGBId);
            String platformGbId = expiredKey.substring(PLATFORM_REGISTER_PREFIX.length(),expiredKey.length());
            ParentPlatform platform = storager.queryParentPlatByServerGBId(platformGbId);
            if (platform != null) {
                publisher.platformRegisterCycleEventPublish(platformGBId);
            }
        }else if (expiredKey.startsWith(KEEPLIVEKEY_PREFIX)){
            String deviceId = expiredKey.substring(KEEPLIVEKEY_PREFIX.length(),expiredKey.length());
            Device device = storager.queryVideoDevice(deviceId);
            if (device != null) {
                publisher.outlineEventPublish(deviceId, KEEPLIVEKEY_PREFIX);
                publisher.platformRegisterCycleEventPublish(platformGbId);
            }
        }else if (expiredKey.startsWith(REGISTER_INFO_PREFIX)) {
            String callId = expiredKey.substring(REGISTER_INFO_PREFIX.length());
@@ -85,6 +77,5 @@
                sipSubscribe.getErrorSubscribe(callId).response(eventResult);
            }
        }
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/KeepliveTimeoutListener.java
File was deleted
src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/OfflineEvent.java
File was deleted
src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/OfflineEventListener.java
File was deleted
src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEvent.java
File was deleted
src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEventListener.java
File was deleted
src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java
@@ -4,24 +4,20 @@
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.bean.SyncStatus;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@Component
public class CatalogDataCatch {
    public static Map<String, CatalogData> data = new ConcurrentHashMap<>();
    @Autowired
    private DeferredResultHolder deferredResultHolder;
    @Autowired
    private IVideoManagerStorage storager;
@@ -30,11 +26,11 @@
        CatalogData catalogData = data.get(device.getDeviceId());
        if (catalogData == null || catalogData.getStatus().equals(CatalogData.CatalogDataStatus.end)) {
            catalogData = new CatalogData();
            catalogData.setChannelList(new ArrayList<>());
            catalogData.setChannelList(Collections.synchronizedList(new ArrayList<>()));
            catalogData.setDevice(device);
            catalogData.setSn(sn);
            catalogData.setStatus(CatalogData.CatalogDataStatus.ready);
            catalogData.setLastTime(new Date(System.currentTimeMillis()));
            catalogData.setLastTime(Instant.now());
            data.put(device.getDeviceId(), catalogData);
        }
    }
@@ -46,9 +42,9 @@
            catalogData.setSn(sn);
            catalogData.setTotal(total);
            catalogData.setDevice(device);
            catalogData.setChannelList(new ArrayList<>());
            catalogData.setChannelList(Collections.synchronizedList(new ArrayList<>()));
            catalogData.setStatus(CatalogData.CatalogDataStatus.runIng);
            catalogData.setLastTime(new Date(System.currentTimeMillis()));
            catalogData.setLastTime(Instant.now());
            data.put(deviceId, catalogData);
        }else {
            // 同一个设备的通道同步请求只考虑一个,其他的直接忽略
@@ -59,7 +55,7 @@
            catalogData.setDevice(device);
            catalogData.setStatus(CatalogData.CatalogDataStatus.runIng);
            catalogData.getChannelList().addAll(deviceChannelList);
            catalogData.setLastTime(new Date(System.currentTimeMillis()));
            catalogData.setLastTime(Instant.now());
        }
    }
@@ -102,16 +98,13 @@
    @Scheduled(fixedRate = 5 * 1000)   //每5秒执行一次, 发现数据5秒未更新则移除数据并认为数据接收超时
    private void timerTask(){
        Set<String> keys = data.keySet();
        Calendar calendarBefore5S = Calendar.getInstance();
        calendarBefore5S.setTime(new Date());
        calendarBefore5S.set(Calendar.SECOND, calendarBefore5S.get(Calendar.SECOND) - 5);
        Calendar calendarBefore30S = Calendar.getInstance();
        calendarBefore30S.setTime(new Date());
        calendarBefore30S.set(Calendar.SECOND, calendarBefore30S.get(Calendar.SECOND) - 30);
        Instant instantBefore5S = Instant.now().minusMillis(TimeUnit.SECONDS.toMillis(5));
        Instant instantBefore30S = Instant.now().minusMillis(TimeUnit.SECONDS.toMillis(30));
        for (String deviceId : keys) {
            CatalogData catalogData = data.get(deviceId);
            if ( catalogData.getLastTime().before(calendarBefore5S.getTime())) { // 超过五秒收不到消息任务超时, 只更新这一部分数据
            if ( catalogData.getLastTime().isBefore(instantBefore5S)) { // 超过五秒收不到消息任务超时, 只更新这一部分数据
                if (catalogData.getStatus().equals(CatalogData.CatalogDataStatus.runIng)) {
                    storager.resetChannels(catalogData.getDevice().getDeviceId(), catalogData.getChannelList());
                    if (catalogData.getTotal() != catalogData.getChannelList().size()) {
@@ -124,7 +117,7 @@
                }
                catalogData.setStatus(CatalogData.CatalogDataStatus.end);
            }
            if (catalogData.getStatus().equals(CatalogData.CatalogDataStatus.end) && catalogData.getLastTime().before(calendarBefore30S.getTime())) { // 超过三十秒,如果标记为end则删除
            if (catalogData.getStatus().equals(CatalogData.CatalogDataStatus.end) && catalogData.getLastTime().isBefore(instantBefore30S)) { // 超过三十秒,如果标记为end则删除
                data.remove(deviceId);
            }
        }
src/main/java/com/genersoft/iot/vmp/gb28181/session/RecordDataCatch.java
New file
@@ -0,0 +1,91 @@
package com.genersoft.iot.vmp.gb28181.session;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
 * @author lin
 */
@Component
public class RecordDataCatch {
    public static Map<String, RecordInfo> data = new ConcurrentHashMap<>();
    @Autowired
    private DeferredResultHolder deferredResultHolder;
    public int put(String deviceId, String sn, int sumNum, List<RecordItem> recordItems) {
        String key = deviceId + sn;
        RecordInfo recordInfo = data.get(key);
        if (recordInfo == null) {
            recordInfo = new RecordInfo();
            recordInfo.setDeviceId(deviceId);
            recordInfo.setSn(sn.trim());
            recordInfo.setSumNum(sumNum);
            recordInfo.setRecordList(Collections.synchronizedList(new ArrayList<>()));
            recordInfo.setLastTime(Instant.now());
            recordInfo.getRecordList().addAll(recordItems);
            data.put(key, recordInfo);
        }else {
            // 同一个设备的通道同步请求只考虑一个,其他的直接忽略
            if (!Objects.equals(sn.trim(), recordInfo.getSn())) {
                return 0;
            }
            recordInfo.getRecordList().addAll(recordItems);
            recordInfo.setLastTime(Instant.now());
        }
        return recordInfo.getRecordList().size();
    }
    @Scheduled(fixedRate = 5 * 1000)   //每5秒执行一次, 发现数据5秒未更新则移除数据并认为数据接收超时
    private void timerTask(){
        Set<String> keys = data.keySet();
        // 获取五秒前的时刻
        Instant instantBefore5S = Instant.now().minusMillis(TimeUnit.SECONDS.toMillis(5));
        for (String key : keys) {
            RecordInfo recordInfo = data.get(key);
            // 超过五秒收不到消息任务超时, 只更新这一部分数据
            if ( recordInfo.getLastTime().isBefore(instantBefore5S)) {
                // 处理录像数据, 返回给前端
                String msgKey = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + recordInfo.getDeviceId() + recordInfo.getSn();
                WVPResult<RecordInfo> wvpResult = new WVPResult<>();
                wvpResult.setCode(0);
                wvpResult.setMsg("success");
                // 对数据进行排序
                Collections.sort(recordInfo.getRecordList());
                wvpResult.setData(recordInfo);
                RequestMessage msg = new RequestMessage();
                msg.setKey(msgKey);
                msg.setData(wvpResult);
                deferredResultHolder.invokeAllResult(msg);
                data.remove(key);
            }
        }
    }
    public boolean isComplete(String deviceId, String sn) {
        RecordInfo recordInfo = data.get(deviceId + sn);
        return recordInfo != null && recordInfo.getRecordList().size() == recordInfo.getSumNum();
    }
    public RecordInfo getRecordInfo(String deviceId, String sn) {
        return data.get(deviceId + sn);
    }
    public void remove(String deviceId, String sn) {
        data.remove(deviceId + sn);
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java
@@ -76,8 +76,8 @@
    }
    
    public ClientTransaction getTransactionByStream(String deviceId, String channelId, String stream){
        SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream);
    public ClientTransaction getTransaction(String deviceId, String channelId, String stream, String callId){
        SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, callId, stream);
        if (ssrcTransaction == null) {
            return null;
        }
src/main/java/com/genersoft/iot/vmp/gb28181/task/SipDeviceRunner.java
New file
@@ -0,0 +1,51 @@
package com.genersoft.iot.vmp.gb28181.task;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.service.IDeviceService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
 * 系统启动时控制设备
 * @author lin
 */
@Component
@Order(value=4)
public class SipDeviceRunner implements CommandLineRunner {
    @Autowired
    private IVideoManagerStorage storager;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private UserSetting userSetting;
    @Autowired
    private IDeviceService deviceService;
    @Override
    public void run(String... args) throws Exception {
        List<Device> deviceList = deviceService.getAllOnlineDevice();
        for (Device device : deviceList) {
            if (deviceService.expire(device)){
                deviceService.offline(device.getDeviceId());
            }else {
                deviceService.online(device);
            }
        }
        // 重置cseq计数
        redisCatchStorage.resetAllCSEQ();
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java
@@ -1,7 +1,10 @@
package com.genersoft.iot.vmp.gb28181.transmit;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.RegisterRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd.KeepaliveNotifyMessageHandler;
import com.genersoft.iot.vmp.gb28181.transmit.event.response.ISIPResponseProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.event.timeout.ITimeoutProcessor;
import org.slf4j.Logger;
@@ -11,10 +14,13 @@
import org.springframework.stereotype.Component;
import javax.sip.*;
import javax.sip.header.CSeqHeader;
import javax.sip.header.CallIdHeader;
import javax.sip.address.SipURI;
import javax.sip.address.URI;
import javax.sip.header.*;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -34,10 +40,11 @@
    @Autowired
    private SipSubscribe sipSubscribe;
    @Autowired
    private EventPublisher eventPublisher;
//    @Autowired
//    @Qualifier(value = "taskExecutor")
//    private ThreadPoolTaskExecutor poolTaskExecutor;
    /**
     * 添加 request订阅
@@ -141,10 +148,33 @@
     */
    @Override
    public void processTimeout(TimeoutEvent timeoutEvent) {
        if(timeoutProcessor != null) {
            timeoutProcessor.process(timeoutEvent);
        logger.info("[消息发送超时]");
        ClientTransaction clientTransaction = timeoutEvent.getClientTransaction();
        eventPublisher.requestTimeOut(timeoutEvent);
        if (clientTransaction != null) {
            Request request = clientTransaction.getRequest();
            if (request != null) {
                CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
                if (callIdHeader != null) {
                    SipSubscribe.Event subscribe = sipSubscribe.getErrorSubscribe(callIdHeader.getCallId());
                    SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(timeoutEvent);
                    subscribe.response(eventResult);
                    sipSubscribe.removeErrorSubscribe(callIdHeader.getCallId());
        }
    }
        }
//        Timeout timeout = timeoutEvent.getTimeout();
//        ServerTransaction serverTransaction = timeoutEvent.getServerTransaction();
//        if (serverTransaction != null) {
//            Request request = serverTransaction.getRequest();
//            URI requestURI = request.getRequestURI();
//            Header header = request.getHeader(FromHeader.NAME);
//        }
//        if(timeoutProcessor != null) {
//            timeoutProcessor.process(timeoutEvent);
//        }
    }
    @Override
    public void processIOException(IOExceptionEvent exceptionEvent) {
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/CheckForAllRecordsThread.java
File was deleted
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -10,7 +10,7 @@
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.DateUtil;
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.dto.MediaServerItem;
@@ -27,7 +27,6 @@
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringBootVersion;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@@ -371,7 +370,7 @@
            //
            StringBuffer content = new StringBuffer(200);
            content.append("v=0\r\n");
            content.append("o="+ sipConfig.getId()+" 0 0 IN IP4 "+ mediaServerItem.getSdpIp() +"\r\n");
            content.append("o="+ channelId+" 0 0 IN IP4 "+ mediaServerItem.getSdpIp() +"\r\n");
            content.append("s=Play\r\n");
            content.append("c=IN IP4 "+ mediaServerItem.getSdpIp() +"\r\n");
            content.append("t=0 0\r\n");
@@ -390,8 +389,7 @@
                content.append("a=rtpmap:126 H264/90000\r\n");
                content.append("a=rtpmap:125 H264S/90000\r\n");
                content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
                content.append("a=rtpmap:99 MP4V-ES/90000\r\n");
                content.append("a=fmtp:99 profile-level-id=3\r\n");
                content.append("a=rtpmap:99 H265/90000\r\n");
                content.append("a=rtpmap:98 H264/90000\r\n");
                content.append("a=rtpmap:97 MPEG4/90000\r\n");
                if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
@@ -403,16 +401,17 @@
                }
            }else {
                if("TCP-PASSIVE".equals(streamMode)) {
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 97 98 99\r\n");
                }else if ("TCP-ACTIVE".equals(streamMode)) {
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 97 98 99\r\n");
                }else if("UDP".equals(streamMode)) {
                    content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 97 98 99\r\n");
                }
                content.append("a=recvonly\r\n");
                content.append("a=rtpmap:96 PS/90000\r\n");
                content.append("a=rtpmap:98 H264/90000\r\n");
                content.append("a=rtpmap:97 MPEG4/90000\r\n");
                content.append("a=rtpmap:99 H265/90000\r\n");
                if ("TCP-PASSIVE".equals(streamMode)) { // tcp被动模式
                    content.append("a=setup:passive\r\n");
                    content.append("a=connection:new\r\n");
@@ -468,7 +467,7 @@
            StringBuffer content = new StringBuffer(200);
            content.append("v=0\r\n");
            content.append("o="+sipConfig.getId()+" 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
            content.append("o="+channelId+" 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
            content.append("s=Playback\r\n");
            content.append("u="+channelId+":0\r\n");
            content.append("c=IN IP4 "+mediaServerItem.getSdpIp()+"\r\n");
@@ -491,8 +490,7 @@
                content.append("a=rtpmap:126 H264/90000\r\n");
                content.append("a=rtpmap:125 H264S/90000\r\n");
                content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
                content.append("a=rtpmap:99 MP4V-ES/90000\r\n");
                content.append("a=fmtp:99 profile-level-id=3\r\n");
                content.append("a=rtpmap:99 H265/90000\r\n");
                content.append("a=rtpmap:98 H264/90000\r\n");
                content.append("a=rtpmap:97 MPEG4/90000\r\n");
                if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
@@ -504,16 +502,17 @@
                }
            }else {
                if("TCP-PASSIVE".equals(streamMode)) {
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 97 98 99\r\n");
                }else if ("TCP-ACTIVE".equals(streamMode)) {
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 97 98 99\r\n");
                }else if("UDP".equals(streamMode)) {
                    content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 97 98 99\r\n");
                }
                content.append("a=recvonly\r\n");
                content.append("a=rtpmap:96 PS/90000\r\n");
                content.append("a=rtpmap:98 H264/90000\r\n");
                content.append("a=rtpmap:97 MPEG4/90000\r\n");
                content.append("a=rtpmap:98 H264/90000\r\n");
                content.append("a=rtpmap:99 H265/90000\r\n");
                if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
                    content.append("a=setup:passive\r\n");
                    content.append("a=connection:new\r\n");
@@ -578,7 +577,7 @@
            StringBuffer content = new StringBuffer(200);
            content.append("v=0\r\n");
            content.append("o="+sipConfig.getId()+" 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
            content.append("o="+channelId+" 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
            content.append("s=Download\r\n");
            content.append("u="+channelId+":0\r\n");
            content.append("c=IN IP4 "+mediaServerItem.getSdpIp()+"\r\n");
@@ -614,16 +613,17 @@
                }
            }else {
                if("TCP-PASSIVE".equals(streamMode)) {
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 97 98 99\r\n");
                }else if ("TCP-ACTIVE".equals(streamMode)) {
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 97 98 99\r\n");
                }else if("UDP".equals(streamMode)) {
                    content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 97 98 99\r\n");
                }
                content.append("a=recvonly\r\n");
                content.append("a=rtpmap:96 PS/90000\r\n");
                content.append("a=rtpmap:98 H264/90000\r\n");
                content.append("a=rtpmap:97 MPEG4/90000\r\n");
                content.append("a=rtpmap:98 H264/90000\r\n");
                content.append("a=rtpmap:99 H265/90000\r\n");
                if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
                    content.append("a=setup:passive\r\n");
                    content.append("a=connection:new\r\n");
@@ -652,6 +652,17 @@
                    (MediaServerItem mediaServerItemInUse, JSONObject json)->{
                        hookEvent.call(new InviteStreamInfo(mediaServerItem, json, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
                        subscribe.removeSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey);
                        subscribeKey.put("regist", false);
                        subscribeKey.put("schema", "rtmp");
                        // 添加流注销的订阅,注销了后向设备发送bye
                        subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
                                (MediaServerItem mediaServerItemForEnd, JSONObject jsonForEnd)->{
                                    ClientTransaction transaction = streamSession.getTransaction(device.getDeviceId(), channelId, ssrcInfo.getStream(), callIdHeader.getCallId());
                                    if (transaction != null) {
                                        logger.info("[录像]下载结束, 发送BYE");
                                        streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), callIdHeader.getCallId());
                                    }
                                });
                    });
            Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null, callIdHeader, ssrcInfo.getSsrc());
@@ -684,8 +695,8 @@
    @Override
    public void streamByeCmd(String deviceId, String channelId, String stream, String callId, SipSubscribe.Event okEvent) {
        try {
            SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(deviceId, channelId, null, stream);
            ClientTransaction transaction = streamSession.getTransactionByStream(deviceId, channelId, stream);
            SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(deviceId, channelId, callId, stream);
            ClientTransaction transaction = streamSession.getTransaction(deviceId, channelId, stream, callId);
            if (transaction == null) {
                logger.warn("[ {} -> {}]停止视频流的时候发现事务已丢失", deviceId, channelId);
@@ -1664,6 +1675,7 @@
            sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> {
                errorEvent.response(eventResult);
                sipSubscribe.removeErrorSubscribe(eventResult.callId);
                sipSubscribe.removeOkSubscribe(eventResult.callId);
            }));
        }
        // 添加订阅
@@ -1671,6 +1683,7 @@
            sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult ->{
                okEvent.response(eventResult);
                sipSubscribe.removeOkSubscribe(eventResult.callId);
                sipSubscribe.removeErrorSubscribe(eventResult.callId);
            });
        }
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
@@ -4,7 +4,7 @@
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.gb28181.utils.DateUtil;
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;
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
@@ -4,8 +4,12 @@
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.gb28181.bean.InviteStreamType;
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;
@@ -13,6 +17,8 @@
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import org.ehcache.shadow.org.terracotta.offheapstore.storage.IntegerStorageEngine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
@@ -51,6 +57,9 @@
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private IVideoManagerStorage storager;
    @Autowired
    private ZLMRTPServerFactory zlmrtpServerFactory;
    @Autowired
@@ -61,6 +70,12 @@
    @Autowired
    private DynamicTask dynamicTask;
    @Autowired
    private ISIPCommander cmder;
    @Autowired
    private ISIPCommanderForPlatform commanderForPlatform;
    /**   
@@ -78,6 +93,7 @@
        if (dialog.getState()== DialogState.CONFIRMED) {
            String platformGbId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser();
            logger.info("ACK请求: platformGbId->{}", platformGbId);
            ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(platformGbId);
            // 取消设置的超时任务
            dynamicTask.stop(callIdHeader.getCallId());
            String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
@@ -94,8 +110,23 @@
            param.put("dst_port", sendRtpItem.getPort());
            param.put("is_udp", is_Udp);
            param.put("src_port", sendRtpItem.getLocalPort());
            zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
            param.put("pt", sendRtpItem.getPt());
            param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
            param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
            JSONObject jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
            if (jsonObject == null) {
                logger.error("RTP推流失败: 请检查ZLM服务");
            } else if (jsonObject.getInteger("code") == 0) {
                logger.info("RTP推流成功[ {}/{} ],{}->{}:{}, " ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"), param.get("dst_url"), param.get("dst_port"));
            } else {
                logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"),JSONObject.toJSON(param));
                if (sendRtpItem.isOnlyAudio()) {
                    // TODO 可能是语音对讲
                }else {
                    // 向上级平台
                    commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId());
                }
            }
//            if (streamInfo == null) { // 流还没上来,对方就回复ack
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
@@ -23,6 +23,7 @@
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
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.utils.SerializeUtils;
import gov.nist.javax.sdp.TimeDescriptionImpl;
import gov.nist.javax.sdp.fields.TimeField;
@@ -39,8 +40,7 @@
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.time.Instant;
import java.util.Vector;
/**
@@ -180,16 +180,16 @@
                Long startTime = null;
                Long stopTime = null;
                Date start = null;
                Date end = null;
                Instant start = null;
                Instant end = null;
                if (sdp.getTimeDescriptions(false) != null && sdp.getTimeDescriptions(false).size() > 0) {
                    TimeDescriptionImpl timeDescription = (TimeDescriptionImpl)(sdp.getTimeDescriptions(false).get(0));
                    TimeField startTimeFiled = (TimeField)timeDescription.getTime();
                    startTime = startTimeFiled.getStartTime();
                    stopTime = startTimeFiled.getStopTime();
                    start = new Date(startTime*1000);
                    end = new Date(stopTime*1000);
                    start = Instant.ofEpochMilli(startTime*1000);
                    end = Instant.ofEpochMilli(stopTime*1000);
                }
                //  获取支持的格式
                Vector mediaDescriptions = sdp.getMediaDescriptions(true);
@@ -331,13 +331,12 @@
                    sendRtpItem.setApp("rtp");
                    if ("Playback".equals(sessionName)) {
                        sendRtpItem.setPlayType(InviteStreamType.PLAYBACK);
                        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, true);
                        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, true, true);
                        sendRtpItem.setStreamId(ssrcInfo.getStream());
                        // 写入redis, 超时时回复
                        redisCatchStorage.updateSendRTPSever(sendRtpItem);
                        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                        playService.playBack(mediaServerItem, ssrcInfo, device.getDeviceId(), channelId, format.format(start),
                                format.format(end), null, result -> {
                        playService.playBack(mediaServerItem, ssrcInfo, device.getDeviceId(), channelId, DateUtil.formatter.format(start),
                                DateUtil.formatter.format(end), null, result -> {
                                if (result.getCode() != 0){
                                    logger.warn("录像回放失败");
                                    if (result.getEvent() != null) {
@@ -373,7 +372,7 @@
                            if (mediaServerItem.isRtpEnable()) {
                                streamId = String.format("%s_%s", device.getDeviceId(), channelId);
                            }
                            SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, true);
                            SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, true, false);
                            sendRtpItem.setStreamId(ssrcInfo.getStream());
                            // 写入redis, 超时时回复
                            redisCatchStorage.updateSendRTPSever(sendRtpItem);
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java
@@ -252,14 +252,12 @@
            FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME);
            String deviceId = SipUtils.getUserIdFromFromHeader(fromHeader);
            Element rootElement = getRootElement(evt);
            Device device = redisCatchStorage.getDevice(deviceId);
            if (device == null) {
            if (device == null || device.getOnline() == 0) {
                logger.warn("[收到 目录订阅]:{}, 但是设备已经离线", (device != null ? device.getDeviceId():"" ));
                return;
            }
            if (device != null ) {
                rootElement = getRootElement(evt, device.getCharset());
            }
            Element rootElement = getRootElement(evt, device.getCharset());
            Element deviceListElement = rootElement.element("DeviceList");
            if (deviceListElement == null) {
                return;
@@ -279,39 +277,46 @@
                    channel.setDeviceId(device.getDeviceId());
                    logger.info("[收到 目录订阅]:{}/{}", device.getDeviceId(), channel.getChannelId());
                    switch (eventElement.getText().toUpperCase()) {
                        case CatalogEvent.ON: // 上线
                        case CatalogEvent.ON:
                            // 上线
                            logger.info("收到来自设备【{}】的通道【{}】上线通知", device.getDeviceId(), channel.getChannelId());
                            storager.deviceChannelOnline(deviceId, channel.getChannelId());
                            // 回复200 OK
                            responseAck(evt, Response.OK);
                            break;
                        case CatalogEvent.OFF : // 离线
                        case CatalogEvent.OFF :
                            // 离线
                            logger.info("收到来自设备【{}】的通道【{}】离线通知", device.getDeviceId(), channel.getChannelId());
                            storager.deviceChannelOffline(deviceId, channel.getChannelId());
                            // 回复200 OK
                            responseAck(evt, Response.OK);
                            break;
                        case CatalogEvent.VLOST: // 视频丢失
                        case CatalogEvent.VLOST:
                            // 视频丢失
                            logger.info("收到来自设备【{}】的通道【{}】视频丢失通知", device.getDeviceId(), channel.getChannelId());
                            storager.deviceChannelOffline(deviceId, channel.getChannelId());
                            // 回复200 OK
                            responseAck(evt, Response.OK);
                            break;
                        case CatalogEvent.DEFECT: // 故障
                        case CatalogEvent.DEFECT:
                            // 故障
                            // 回复200 OK
                            responseAck(evt, Response.OK);
                            break;
                        case CatalogEvent.ADD: // 增加
                        case CatalogEvent.ADD:
                            // 增加
                            logger.info("收到来自设备【{}】的增加通道【{}】通知", device.getDeviceId(), channel.getChannelId());
                            storager.updateChannel(deviceId, channel);
                            responseAck(evt, Response.OK);
                            break;
                        case CatalogEvent.DEL: // 删除
                        case CatalogEvent.DEL:
                            // 删除
                            logger.info("收到来自设备【{}】的删除通道【{}】通知", device.getDeviceId(), channel.getChannelId());
                            storager.delChannel(deviceId, channel.getChannelId());
                            responseAck(evt, Response.OK);
                            break;
                        case CatalogEvent.UPDATE: // 更新
                        case CatalogEvent.UPDATE:
                            // 更新
                            logger.info("收到来自设备【{}】的更新通道【{}】通知", device.getDeviceId(), channel.getChannelId());
                            storager.updateChannel(deviceId, channel);
                            responseAck(evt, Response.OK);
@@ -323,10 +328,6 @@
                    // 转发变化信息
                    eventPublisher.catalogEventPublish(null, channel, eventElement.getText().toUpperCase());
                }
                if (!redisCatchStorage.deviceIsOnline(deviceId)) {
                    publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_MESSAGE);
                }
            }
        } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java
@@ -9,8 +9,10 @@
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
import com.genersoft.iot.vmp.service.IDeviceService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.utils.DateUtil;
import gov.nist.javax.sip.RequestEventExt;
import gov.nist.javax.sip.address.AddressImpl;
import gov.nist.javax.sip.address.SipUri;
@@ -60,6 +62,9 @@
    @Autowired
    private SIPProcessorObserver sipProcessorObserver;
    @Autowired
    private IDeviceService deviceService;
    @Override
    public void afterPropertiesSet() throws Exception {
        // 添加消息处理的订阅
@@ -76,13 +81,13 @@
        try {
            RequestEventExt evtExt = (RequestEventExt) evt;
            String requestAddress = evtExt.getRemoteIpAddress() + ":" + evtExt.getRemotePort();
            logger.info("[{}] 收到注册请求,开始处理", requestAddress);
            logger.info("[注册请求] 开始处理: {}", requestAddress);
            Request request = evt.getRequest();
            ExpiresHeader expiresHeader = (ExpiresHeader) request.getHeader(Expires.NAME);
            Response response = null;
            boolean passwordCorrect = false;
            // 注册标志  0:未携带授权头或者密码错误  1:注册成功   2:注销成功
            int registerFlag = 0;
            boolean registerFlag = false;
            FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME);
            AddressImpl address = (AddressImpl) fromHeader.getAddress();
            SipUri uri = (SipUri) address.getURI();
@@ -90,7 +95,7 @@
            AuthorizationHeader authHead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
            if (authHead == null) {
                logger.info("[{}] 未携带授权头 回复401", requestAddress);
                logger.info("[注册请求] 未携带授权头 回复401: {}", requestAddress);
                response = getMessageFactory().createResponse(Response.UNAUTHORIZED, request);
                new DigestServerAuthenticationHelper().generateChallenge(getHeaderFactory(), response, sipConfig.getDomain());
                sendResponse(evt, response);
@@ -106,17 +111,13 @@
                // 注册失败
                response = getMessageFactory().createResponse(Response.FORBIDDEN, request);
                response.setReasonPhrase("wrong password");
                logger.info("[{}] 密码/SIP服务器ID错误, 回复403", requestAddress);
                logger.info("[注册请求] 密码/SIP服务器ID错误, 回复403: {}", requestAddress);
                sendResponse(evt, response);
                return;
            }
            Device deviceInRedis = redisCatchStorage.getDevice(deviceId);
            Device device = storager.queryVideoDevice(deviceId);
            if (deviceInRedis != null && device == null) {
                // redis 存在脏数据
                redisCatchStorage.clearCatchByDeviceId(deviceId);
            }
            Device device = deviceService.queryDevice(deviceId);
            // 携带授权头并且密码正确
            response = getMessageFactory().createResponse(Response.OK, request);
            // 添加date头
@@ -154,20 +155,17 @@
                device.setStreamMode("UDP");
                device.setCharset("GB2312");
                device.setDeviceId(deviceId);
                device.setFirsRegister(true);
            } else {
                device.setFirsRegister(device.getOnline() == 0);
            }
            device.setIp(received);
            device.setPort(rPort);
            device.setHostAddress(received.concat(":").concat(String.valueOf(rPort)));
            if (expiresHeader.getExpires() == 0) {
                // 注销成功
                registerFlag = 2;
                registerFlag = false;
            } else {
                // 注册成功
                device.setExpires(expiresHeader.getExpires());
                registerFlag = 1;
                registerFlag = true;
                // 判断TCP还是UDP
                ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
                String transport = reqViaHeader.getTransport();
@@ -177,12 +175,13 @@
            sendResponse(evt, response);
            // 注册成功
            // 保存到redis
            if (registerFlag == 1) {
                logger.info("[{}] 注册成功! deviceId:" + deviceId, requestAddress);
                publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_REGISTER, expiresHeader.getExpires());
            } else if (registerFlag == 2) {
                logger.info("[{}] 注销成功! deviceId:" + deviceId, requestAddress);
                publisher.outlineEventPublish(deviceId, VideoManagerConstants.EVENT_OUTLINE_UNREGISTER);
            if (registerFlag) {
                logger.info("[注册成功] deviceId: {}->{}",  deviceId, requestAddress);
                device.setRegisterTime(DateUtil.getNow());
                deviceService.online(device);
            } else {
                logger.info("[注销成功] deviceId: {}->{}" ,deviceId, requestAddress);
                deviceService.offline(deviceId);
            }
        } catch (SipException | InvalidArgumentException | NoSuchAlgorithmException | ParseException e) {
            e.printStackTrace();
@@ -193,7 +192,7 @@
    private void sendResponse(RequestEvent evt, Response response) throws InvalidArgumentException, SipException {
        ServerTransaction serverTransaction = getServerTransaction(evt);
        if (serverTransaction == null) {
            logger.warn("回复失败:{}", response);
            logger.warn("[回复失败]:{}", response);
            return;
        }
        serverTransaction.sendResponse(response);
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java
@@ -1,14 +1,15 @@
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler;
import com.genersoft.iot.vmp.service.IDeviceService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.utils.DateUtil;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -28,19 +29,13 @@
public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
    private Logger logger = LoggerFactory.getLogger(KeepaliveNotifyMessageHandler.class);
    private final String cmdType = "Keepalive";
    private final static String cmdType = "Keepalive";
    @Autowired
    private NotifyMessageHandler notifyMessageHandler;
    @Autowired
    private EventPublisher publisher;
    @Autowired
    private IVideoManagerStorage videoManagerStorager;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    private IDeviceService deviceService;
    @Override
    public void afterPropertiesSet() throws Exception {
@@ -49,11 +44,18 @@
    @Override
    public void handForDevice(RequestEvent evt, Device device, Element element) {
        // 检查设备是否存在并在线, 不在线则设置为在线
        if (device == null) {
            // 未注册的设备不做处理
            return;
        }
        try {
            if (device != null ) {
            if (device.getOnline() == 1) {
                // 回复200 OK
                responseAck(evt, Response.OK);
            }else {
                // 对于已经离线的设备判断他的注册是否已经过期
                if (!deviceService.expire(device)){
                    device.setKeepaliveTime(DateUtil.getNow());
                // 判断RPort是否改变,改变则说明路由nat信息变化,修改设备信息
                // 获取到通信地址等信息
                ViaHeader viaHeader = (ViaHeader) evt.getRequest().getHeader(ViaHeader.NAME);
@@ -67,11 +69,11 @@
                if (device.getPort() != rPort) {
                    device.setPort(rPort);
                    device.setHostAddress(received.concat(":").concat(String.valueOf(rPort)));
                    videoManagerStorager.updateDevice(device);
                    redisCatchStorage.updateDevice(device);
                }
                if (!redisCatchStorage.deviceIsOnline(device.getDeviceId())) {
                    publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
                    device.setKeepaliveTime(DateUtil.getNow());
                    deviceService.online(device);
                    // 回复200 OK
                    responseAck(evt, Response.OK);
                }
            }
        } catch (SipException e) {
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java
@@ -60,10 +60,9 @@
        CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
        String NotifyType =getText(rootElement, "NotifyType");
        if (NotifyType.equals("121")){
            logger.info("媒体播放完毕,通知关流");
            logger.info("[录像流]推送完毕,收到关流通知");
            String channelId =getText(rootElement, "DeviceID");
//            redisCatchStorage.stopPlayback(device.getDeviceId(), channelId, null, callIdHeader.getCallId());
//            redisCatchStorage.stopDownload(device.getDeviceId(), channelId, null, callIdHeader.getCallId());
            // 查询是设备
            StreamInfo streamInfo = redisCatchStorage.queryDownload(device.getDeviceId(), channelId, null, callIdHeader.getCallId());
            // 设置进度100%
            streamInfo.setProgress(1);
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/RecordInfoQueryMessageHandler.java
@@ -9,7 +9,7 @@
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.QueryMessageHandler;
import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.storager.dao.dto.ChannelSourceInfo;
import org.dom4j.Element;
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java
@@ -28,7 +28,6 @@
import javax.sip.SipException;
import javax.sip.message.Response;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -38,8 +37,6 @@
    private Logger logger = LoggerFactory.getLogger(CatalogResponseMessageHandler.class);
    private final String cmdType = "Catalog";
    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @Autowired
    private ResponseMessageHandler responseMessageHandler;
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceInfoResponseMessageHandler.java
@@ -10,6 +10,7 @@
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
import com.genersoft.iot.vmp.service.IDeviceService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import org.dom4j.DocumentException;
@@ -29,6 +30,9 @@
import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
/**
 * @author lin
 */
@Component
public class DeviceInfoResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
@@ -53,6 +57,9 @@
    @Autowired
    private EventPublisher publisher;
    @Autowired
    private IDeviceService deviceService;
    @Override
    public void afterPropertiesSet() throws Exception {
        responseMessageHandler.addHandler(cmdType, this);
@@ -61,6 +68,11 @@
    @Override
    public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
        logger.debug("接收到DeviceInfo应答消息");
        // 检查设备是否存在, 不存在则不回复
        if (device == null || device.getOnline() == 0) {
            logger.warn("[接收到DeviceInfo应答消息,但是设备已经离线]:" + (device != null ? device.getDeviceId():"" ));
            return;
        }
        try {
            rootElement = getRootElement(evt, device.getCharset());
            Element deviceIdElement = rootElement.element("DeviceID");
@@ -74,7 +86,8 @@
            if (StringUtils.isEmpty(device.getStreamMode())) {
                device.setStreamMode("UDP");
            }
            storager.updateDevice(device);
            deviceService.updateDevice(device);
//            storager.updateDevice(device);
            RequestMessage msg = new RequestMessage();
            msg.setKey(key);
@@ -82,9 +95,6 @@
            deferredResultHolder.invokeAllResult(msg);
            // 回复200 OK
            responseAck(evt, Response.OK);
            if (redisCatchStorage.deviceIsOnline(device.getDeviceId())) {
                publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_MESSAGE);
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (InvalidArgumentException e) {
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceStatusResponseMessageHandler.java
@@ -11,6 +11,7 @@
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import com.genersoft.iot.vmp.service.IDeviceService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import org.dom4j.Element;
import org.slf4j.Logger;
@@ -24,6 +25,7 @@
import javax.sip.SipException;
import javax.sip.message.Response;
import java.text.ParseException;
import java.util.Objects;
@Component
public class DeviceStatusResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
@@ -34,12 +36,11 @@
    @Autowired
    private ResponseMessageHandler responseMessageHandler;
    @Autowired
    private DeferredResultHolder deferredResultHolder;
    @Autowired
    private EventPublisher publisher;
    private IDeviceService deviceService;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
@@ -53,6 +54,9 @@
    public void handForDevice(RequestEvent evt, Device device, Element element) {
        logger.info("接收到DeviceStatus应答消息");
        // 检查设备是否存在, 不存在则不回复
        if (device == null) {
            return;
        }
        // 回复200 OK
        try {
            responseAck(evt, Response.OK);
@@ -64,20 +68,23 @@
            e.printStackTrace();
        }
        Element deviceIdElement = element.element("DeviceID");
        Element onlineElement = element.element("Online");
        String channelId = deviceIdElement.getText();
        JSONObject json = new JSONObject();
        XmlUtil.node2Json(element, json);
        if (logger.isDebugEnabled()) {
            logger.debug(json.toJSONString());
        }
        String text = onlineElement.getText();
        if (Objects.equals(text.trim().toUpperCase(), "ONLINE")) {
            deviceService.online(device);
        }else {
            deviceService.offline(device.getDeviceId());
        }
        RequestMessage msg = new RequestMessage();
        msg.setKey(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + device.getDeviceId() + channelId);
        msg.setData(json);
        deferredResultHolder.invokeAllResult(msg);
        if (redisCatchStorage.deviceIsOnline(device.getDeviceId())) {
            publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_MESSAGE);
        }
    }
    @Override
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/PresetQueryResponseMessageHandler.java
@@ -1,21 +1,12 @@
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.domain.req.PresetQuerySipReq;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.session.CatalogDataCatch;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
import com.genersoft.iot.vmp.gb28181.utils.Coordtransform;
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.slf4j.Logger;
@@ -23,18 +14,15 @@
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.sip.InvalidArgumentException;
import javax.sip.RequestEvent;
import javax.sip.SipException;
import javax.sip.message.Response;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
@@ -43,8 +31,6 @@
    private Logger logger = LoggerFactory.getLogger(PresetQueryResponseMessageHandler.class);
    private final String cmdType = "PresetQuery";
    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @Autowired
    private ResponseMessageHandler responseMessageHandler;
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java
@@ -5,14 +5,14 @@
import com.genersoft.iot.vmp.gb28181.bean.RecordInfo;
import com.genersoft.iot.vmp.gb28181.bean.RecordItem;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.transmit.callback.CheckForAllRecordsThread;
import com.genersoft.iot.vmp.gb28181.session.RecordDataCatch;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.slf4j.Logger;
@@ -20,19 +20,20 @@
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.sip.InvalidArgumentException;
import javax.sip.RequestEvent;
import javax.sip.SipException;
import javax.sip.message.Response;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.*;
import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
/**
 * @author lin
 */
@Component
public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
@@ -45,10 +46,12 @@
    private ResponseMessageHandler responseMessageHandler;
    @Autowired
    private RedisUtil redis;
    private RecordDataCatch recordDataCatch;
    @Autowired
    private DeferredResultHolder deferredResultHolder;
    @Autowired
    private EventPublisher eventPublisher;
@@ -66,32 +69,22 @@
            responseAck(evt, Response.OK);
            rootElement = getRootElement(evt, device.getCharset());
            String uuid = UUID.randomUUID().toString().replace("-", "");
            RecordInfo recordInfo = new RecordInfo();
            String sn = getText(rootElement, "SN");
            String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + device.getDeviceId() + sn;
            recordInfo.setDeviceId(device.getDeviceId());
            recordInfo.setSn(sn);
            recordInfo.setName(getText(rootElement, "Name"));
            if (getText(rootElement, "SumNum") == null || getText(rootElement, "SumNum") == "") {
                recordInfo.setSumNum(0);
            } else {
                recordInfo.setSumNum(Integer.parseInt(getText(rootElement, "SumNum")));
            String sumNumStr = getText(rootElement, "SumNum");
            int sumNum = 0;
            if (!StringUtils.isEmpty(sumNumStr)) {
                sumNum = Integer.parseInt(sumNumStr);
            }
            Element recordListElement = rootElement.element("RecordList");
            if (recordListElement == null || recordInfo.getSumNum() == 0) {
            if (recordListElement == null || sumNum == 0) {
                logger.info("无录像数据");
                eventPublisher.recordEndEventPush(recordInfo);
                RequestMessage msg = new RequestMessage();
                msg.setKey(key);
                msg.setData(recordInfo);
                deferredResultHolder.invokeAllResult(msg);
                recordDataCatch.put(device.getDeviceId(), sn, sumNum, new ArrayList<>());
                releaseRequest(device.getDeviceId(), sn);
            } else {
                Iterator<Element> recordListIterator = recordListElement.elementIterator();
                List<RecordItem> recordList = new ArrayList<RecordItem>();
                if (recordListIterator != null) {
                    RecordItem record = new RecordItem();
                    logger.info("处理录像列表数据...");
                    List<RecordItem> recordList = new ArrayList<>();
                    // 遍历DeviceList
                    while (recordListIterator.hasNext()) {
                        Element itemRecord = recordListIterator.next();
@@ -100,43 +93,31 @@
                            logger.info("记录为空,下一个...");
                            continue;
                        }
                        record = new RecordItem();
                        RecordItem record = new RecordItem();
                        record.setDeviceId(getText(itemRecord, "DeviceID"));
                        record.setName(getText(itemRecord, "Name"));
                        record.setFilePath(getText(itemRecord, "FilePath"));
                        record.setFileSize(getText(itemRecord, "FileSize"));
                        record.setAddress(getText(itemRecord, "Address"));
                        record.setStartTime(
                                DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(getText(itemRecord, "StartTime")));
                        record.setEndTime(
                                DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(getText(itemRecord, "EndTime")));
                        String startTimeStr = getText(itemRecord, "StartTime");
                        record.setStartTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTimeStr));
                        String endTimeStr = getText(itemRecord, "EndTime");
                        record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTimeStr));
                        record.setSecrecy(itemRecord.element("Secrecy") == null ? 0
                                : Integer.parseInt(getText(itemRecord, "Secrecy")));
                        record.setType(getText(itemRecord, "Type"));
                        record.setRecorderId(getText(itemRecord, "RecorderID"));
                        recordList.add(record);
                    }
                    recordInfo.setRecordList(recordList);
                    int count = recordDataCatch.put(device.getDeviceId(), sn, sumNum, recordList);
                    logger.info("[国标录像], {}->{}: {}/{}", device.getDeviceId(), sn, count, sumNum);
                }
                eventPublisher.recordEndEventPush(recordInfo);
                // 改用单独线程统计已获取录像文件数量,避免多包并行分别统计不完整的问题
                String cacheKey = CACHE_RECORDINFO_KEY + device.getDeviceId() + sn;
                redis.set(cacheKey + "_" + uuid, recordList, 90);
                if (!threadNameList.contains(cacheKey)) {
                    threadNameList.add(cacheKey);
                    CheckForAllRecordsThread chk = new CheckForAllRecordsThread(cacheKey, recordInfo);
                    chk.setName(cacheKey);
                    chk.setDeferredResultHolder(deferredResultHolder);
                    chk.setRedis(redis);
                    chk.setLogger(logger);
                    chk.start();
                    if (logger.isDebugEnabled()) {
                        logger.debug("Start Thread " + cacheKey + ".");
                    }
                } else {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Thread " + cacheKey + " already started.");
                    }
                if (recordDataCatch.isComplete(device.getDeviceId(), sn)){
                    releaseRequest(device.getDeviceId(), sn);
                }
            }
        } catch (SipException e) {
@@ -154,4 +135,20 @@
    public void handForPlatform(RequestEvent evt, ParentPlatform parentPlatform, Element element) {
    }
    public void releaseRequest(String deviceId, String sn){
        String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + sn;
        WVPResult<RecordInfo> wvpResult = new WVPResult<>();
        wvpResult.setCode(0);
        wvpResult.setMsg("success");
        // 对数据进行排序
        Collections.sort(recordDataCatch.getRecordInfo(deviceId, sn).getRecordList());
        wvpResult.setData(recordDataCatch.getRecordInfo(deviceId, sn));
        RequestMessage msg = new RequestMessage();
        msg.setKey(key);
        msg.setData(wvpResult);
        deferredResultHolder.invokeAllResult(msg);
        recordDataCatch.remove(deviceId, sn);
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/utils/DateUtil.java
File was deleted
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
@@ -246,17 +246,7 @@
     * 调用zlm RESTFUL API —— startSendRtp
     */
    public JSONObject startSendRtpStream(MediaServerItem mediaServerItem, Map<String, Object>param) {
        Boolean result = false;
        JSONObject jsonObject = zlmresTfulUtils.startSendRtp(mediaServerItem, param);
        if (jsonObject == null) {
            logger.error("RTP推流失败: 请检查ZLM服务");
        } else if (jsonObject.getInteger("code") == 0) {
            result= true;
            logger.info("RTP推流成功[ {}/{} ],{}->{}:{}, " ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"), param.get("dst_url"), param.get("dst_port"));
        } else {
            logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"),JSONObject.toJSON(param));
        }
        return jsonObject;
        return zlmresTfulUtils.startSendRtp(mediaServerItem, param);
    }
    /**
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
@@ -55,9 +55,6 @@
    @Autowired
    private DynamicTask dynamicTask;
    @Qualifier("taskExecutor")
    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;
    @Override
    public void run(String... strings) throws Exception {
@@ -92,7 +89,7 @@
                });
        // 获取zlm信息
        logger.info("[zlm接入]等待默认zlm中...");
        logger.info("[zlm] 等待默认zlm中...");
        // 获取所有的zlm, 并开启主动连接
        List<MediaServerItem> all = mediaServerService.getAllFromDatabase();
@@ -105,9 +102,7 @@
                startGetMedia = new HashMap<>();
            }
            startGetMedia.put(mediaServerItem.getId(), true);
            taskExecutor.execute(()->{
                connectZlmServer(mediaServerItem);
            });
        }
        String taskKey = "zlm-connect-timeout";
        dynamicTask.startDelay(taskKey, ()->{
@@ -119,21 +114,37 @@
                startGetMedia = null;
            }
        //  TODO 清理数据库中与redis不匹配的zlm
        }, 6 * 1000 );
        }, 60 * 1000 );
    }
    @Async
    public void connectZlmServer(MediaServerItem mediaServerItem){
        ZLMServerConfig zlmServerConfig = getMediaServerConfig(mediaServerItem, 1);
        String connectZlmServerTaskKey = "connect-zlm-" + mediaServerItem.getId();
        ZLMServerConfig zlmServerConfigFirst = getMediaServerConfig(mediaServerItem);
        if (zlmServerConfigFirst != null) {
            zlmServerConfigFirst.setIp(mediaServerItem.getIp());
            zlmServerConfigFirst.setHttpPort(mediaServerItem.getHttpPort());
            startGetMedia.remove(mediaServerItem.getId());
            mediaServerService.zlmServerOnline(zlmServerConfigFirst);
        }else {
            logger.info("[ {} ]-[ {}:{} ]主动连接失败, 清理相关资源, 开始尝试重试连接",
                    mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
            publisher.zlmOfflineEventPublish(mediaServerItem.getId());
        }
        dynamicTask.startCron(connectZlmServerTaskKey, ()->{
            ZLMServerConfig zlmServerConfig = getMediaServerConfig(mediaServerItem);
        if (zlmServerConfig != null) {
                dynamicTask.stop(connectZlmServerTaskKey);
            zlmServerConfig.setIp(mediaServerItem.getIp());
            zlmServerConfig.setHttpPort(mediaServerItem.getHttpPort());
            startGetMedia.remove(mediaServerItem.getId());
            mediaServerService.zlmServerOnline(zlmServerConfig);
        }
        }, 2000);
    }
    public ZLMServerConfig getMediaServerConfig(MediaServerItem mediaServerItem, int index) {
    public ZLMServerConfig getMediaServerConfig(MediaServerItem mediaServerItem) {
        if (startGetMedia == null) { return null;}
        if (!mediaServerItem.isDefaultServer() && mediaServerService.getOne(mediaServerItem.getId()) == null) {
            return null;
@@ -149,53 +160,10 @@
                zlmServerConfig = JSON.parseObject(JSON.toJSONString(data.get(0)), ZLMServerConfig.class);
            }
        } else {
            logger.error("[ {} ]-[ {}:{} ]第{}次主动连接失败, 2s后重试",
                    mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort(), index);
            if (index == 1 && !StringUtils.isEmpty(mediaServerItem.getId())) {
                logger.info("[ {} ]-[ {}:{} ]第{}次主动连接失败, 开始清理相关资源",
                        mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort(), index);
                publisher.zlmOfflineEventPublish(mediaServerItem.getId());
            }
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            zlmServerConfig = getMediaServerConfig(mediaServerItem, index += 1);
            logger.error("[ {} ]-[ {}:{} ]主动连接失败, 2s后重试",
                    mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
        }
        return zlmServerConfig;
    }
    /**
     * zlm 连接成功或者zlm重启后
     */
//    private void zLmRunning(ZLMServerConfig zlmServerConfig){
//        logger.info( "[ id: " + zlmServerConfig.getGeneralMediaServerId() + "] zlm接入成功...");
//        // 关闭循环获取zlm配置
//        startGetMedia = false;
//        MediaServerItem mediaServerItem = new MediaServerItem(zlmServerConfig, sipIp);
//        storager.updateMediaServer(mediaServerItem);
//
//        if (mediaServerItem.isAutoConfig()) setZLMConfig(mediaServerItem);
//        zlmServerManger.updateServerCatchFromHook(zlmServerConfig);
//
//        // 清空所有session
////        zlmMediaListManager.clearAllSessions();
//
//        // 更新流列表
//        zlmMediaListManager.updateMediaList(mediaServerItem);
//        // 恢复流代理, 只查找这个这个流媒体
//        List<StreamProxyItem> streamProxyListForEnable = storager.getStreamProxyListForEnableInMediaServer(
//                mediaServerItem.getId(), true);
//        for (StreamProxyItem streamProxyDto : streamProxyListForEnable) {
//            logger.info("恢复流代理," + streamProxyDto.getApp() + "/" + streamProxyDto.getStream());
//            JSONObject jsonObject = streamProxyService.addStreamProxyToZlm(streamProxyDto);
//            if (jsonObject == null) {
//                // 设置为未启用
//                logger.info("恢复流代理失败,请检查流地址后重新启用" + streamProxyDto.getApp() + "/" + streamProxyDto.getStream());
//                streamProxyService.stop(streamProxyDto.getApp(), streamProxyDto.getStream());
//            }
//        }
//    }
}
src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMStatusEventListener.java
@@ -7,12 +7,10 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
/**
 * @description: 在线事件监听器,监听到离线后,修改设备离在线状态。 设备在线有两个来源:
@@ -38,13 +36,10 @@
    @Autowired
    private IPlayService playService;
    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @Async
    @EventListener
    public void onApplicationEvent(ZLMOnlineEvent event) {
        logger.info("ZLM上线事件触发,ID:" + event.getMediaServerId());
        logger.info("[ZLM] 上线 ID:" + event.getMediaServerId());
        streamPushService.zlmServerOnline(event.getMediaServerId());
        streamProxyService.zlmServerOnline(event.getMediaServerId());
@@ -54,7 +49,7 @@
    @EventListener
    public void onApplicationEvent(ZLMOfflineEvent event) {
        logger.info("ZLM离线事件触发,ID:" + event.getMediaServerId());
        logger.info("[ZLM] 离线,ID:" + event.getMediaServerId());
        // 处理ZLM离线
        mediaServerService.zlmServerOffline(event.getMediaServerId());
        streamProxyService.zlmServerOffline(event.getMediaServerId());
src/main/java/com/genersoft/iot/vmp/service/IDeviceService.java
@@ -3,56 +3,111 @@
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.SyncStatus;
import java.util.List;
/**
 * 设备相关业务处理
 * @author lin
 */
public interface IDeviceService {
    /**
     * 设备上线
     * @param device 设备信息
     */
    void online(Device device);
    /**
     * 设备下线
     * @param deviceId 设备编号
     */
    void offline(String deviceId);
    /**
     * 添加目录订阅
     * @param device 设备信息
     * @return
     * @return 布尔
     */
    boolean addCatalogSubscribe(Device device);
    /**
     * 移除目录订阅
     * @param device 设备信息
     * @return
     * @return 布尔
     */
    boolean removeCatalogSubscribe(Device device);
    /**
     * 添加移动位置订阅
     * @param device 设备信息
     * @return
     * @return 布尔
     */
    boolean addMobilePositionSubscribe(Device device);
    /**
     * 移除移动位置订阅
     * @param device 设备信息
     * @return
     * @return 布尔
     */
    boolean removeMobilePositionSubscribe(Device device);
    /**
     * 移除移动位置订阅
     * @param deviceId 设备ID
     * @return
     * @return 同步状态
     */
    SyncStatus getChannelSyncStatus(String deviceId);
    /**
     * 查看是否仍在同步
     * @param deviceId 设备ID
     * @return
     * @return 布尔
     */
    Boolean isSyncRunning(String deviceId);
    /**
     * 通道同步
     * @param device
     * @param device 设备信息
     */
    void sync(Device device);
    /**
     * 查询设备信息
     * @param deviceId 设备编号
     * @return 设备信息
     */
    Device queryDevice(String deviceId);
    /**
     * 获取所有在线设备
     * @return 设备列表
     */
    List<Device> getAllOnlineDevice();
    /**
     * 判断是否注册已经失效
     * @param device 设备信息
     * @return 布尔
     */
    boolean expire(Device device);
    /**
     * 检查设备状态
     * @param device 设备信息
     */
    void checkDeviceStatus(Device device);
    /**
     * 根据IP和端口获取设备信息
     * @param host IP
     * @param port 端口
     * @return 设备信息
     */
    Device getDeviceByHostAndPort(String host, int port);
    /**
     * 更新设备
     * @param device 设备信息
     */
    void updateDevice(Device device);
}
src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java
@@ -44,7 +44,7 @@
    void updateVmServer(List<MediaServerItem>  mediaServerItemList);
    SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, boolean ssrcCheck);
    SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, boolean ssrcCheck, boolean isPlayback);
    SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback);
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
@@ -2,21 +2,27 @@
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler;
import com.genersoft.iot.vmp.service.IDeviceService;
import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask;
import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask;
import com.genersoft.iot.vmp.gb28181.bean.SyncStatus;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.DeviceMapper;
import com.genersoft.iot.vmp.utils.DateUtil;
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.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.sip.DialogState;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
 * 设备业务(目录订阅)
@@ -25,6 +31,8 @@
public class DeviceServiceImpl implements IDeviceService {
    private final static Logger logger = LoggerFactory.getLogger(DeviceServiceImpl.class);
    private final String  registerExpireTaskKeyPrefix = "device-register-expire-";
    @Autowired
    private DynamicTask dynamicTask;
@@ -38,6 +46,83 @@
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private DeviceMapper deviceMapper;
    @Autowired
    private ISIPCommander commander;
    @Autowired
    private VideoStreamSessionManager streamSession;
    @Autowired
    private IMediaServerService mediaServerService;
    @Override
    public void online(Device device) {
        logger.info("[设备上线] deviceId:{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort());
        Device deviceInRedis = redisCatchStorage.getDevice(device.getDeviceId());
        Device deviceInDb = deviceMapper.getDeviceByDeviceId(device.getDeviceId());
        String now = DateUtil.getNow();
        if (deviceInRedis != null && deviceInDb == null) {
            // redis 存在脏数据
            redisCatchStorage.clearCatchByDeviceId(device.getDeviceId());
        }
        device.setUpdateTime(now);
        device.setOnline(1);
        // 第一次上线
        if (device.getCreateTime() == null) {
            device.setCreateTime(now);
            logger.info("[设备上线,首次注册]: {},查询设备信息以及通道信息", device.getDeviceId());
            commander.deviceInfoQuery(device);
            sync(device);
            deviceMapper.add(device);
        }else {
            deviceMapper.update(device);
        }
        redisCatchStorage.updateDevice(device);
        // 上线添加订阅
        if (device.getSubscribeCycleForCatalog() > 0) {
            // 查询在线设备那些开启了订阅,为设备开启定时的目录订阅
            addCatalogSubscribe(device);
        }
        if (device.getSubscribeCycleForMobilePosition() > 0) {
            addMobilePositionSubscribe(device);
        }
        // 刷新过期任务
        String registerExpireTaskKey = registerExpireTaskKeyPrefix + device.getDeviceId();
        dynamicTask.stop(registerExpireTaskKey);
        dynamicTask.startDelay(registerExpireTaskKey, ()-> offline(device.getDeviceId()), device.getExpires() * 1000);
    }
    @Override
    public void offline(String deviceId) {
        Device device = deviceMapper.getDeviceByDeviceId(deviceId);
        if (device == null) {
            return;
        }
        String registerExpireTaskKey = registerExpireTaskKeyPrefix + deviceId;
        dynamicTask.stop(registerExpireTaskKey);
        device.setOnline(0);
        redisCatchStorage.updateDevice(device);
        deviceMapper.update(device);
        // 离线释放所有ssrc
        List<SsrcTransaction> ssrcTransactions = streamSession.getSsrcTransactionForAll(deviceId, null, null, null);
        if (ssrcTransactions != null && ssrcTransactions.size() > 0) {
            for (SsrcTransaction ssrcTransaction : ssrcTransactions) {
                mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
                mediaServerService.closeRTPServer(deviceId, ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
                streamSession.remove(deviceId, ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
            }
        }
        // 移除订阅
        removeCatalogSubscribe(device);
        removeMobilePositionSubscribe(device);
    }
    @Override
    public boolean addCatalogSubscribe(Device device) {
        if (device == null || device.getSubscribeCycleForCatalog() < 0) {
@@ -49,7 +134,7 @@
        // 提前开始刷新订阅
        int subscribeCycleForCatalog = Math.max(device.getSubscribeCycleForCatalog(),30);
        // 设置最小值为30
        dynamicTask.startCron(device.getDeviceId() + "catalog", catalogSubscribeTask, subscribeCycleForCatalog -1);
        dynamicTask.startCron(device.getDeviceId() + "catalog", catalogSubscribeTask, (subscribeCycleForCatalog -1) * 1000);
        return true;
    }
@@ -74,7 +159,7 @@
        // 设置最小值为30
        int subscribeCycleForCatalog = Math.max(device.getSubscribeCycleForMobilePosition(),30);
        // 提前开始刷新订阅
        dynamicTask.startCron(device.getDeviceId() + "mobile_position" , mobilePositionSubscribeTask, subscribeCycleForCatalog -1 );
        dynamicTask.startCron(device.getDeviceId() + "mobile_position" , mobilePositionSubscribeTask, (subscribeCycleForCatalog -1 ) * 1000);
        return true;
    }
@@ -111,4 +196,92 @@
            catalogResponseMessageHandler.setChannelSyncEnd(device.getDeviceId(), errorMsg);
        });
    }
    @Override
    public Device queryDevice(String deviceId) {
        return deviceMapper.getDeviceByDeviceId(deviceId);
    }
    @Override
    public List<Device> getAllOnlineDevice() {
        return deviceMapper.getOnlineDevices();
    }
    @Override
    public boolean expire(Device device) {
        Instant registerTimeDate = Instant.from(DateUtil.formatter.parse(device.getRegisterTime()));
        Instant expireInstant = registerTimeDate.plusMillis(TimeUnit.SECONDS.toMillis(device.getExpires()));
        return expireInstant.isBefore(Instant.now());
    }
    @Override
    public void checkDeviceStatus(Device device) {
        if (device == null || device.getOnline() == 0) {
            return;
        }
        sipCommander.deviceStatusQuery(device, null);
    }
    @Override
    public Device getDeviceByHostAndPort(String host, int port) {
        return deviceMapper.getDeviceByHostAndPort(host, port);
    }
    @Override
    public void updateDevice(Device device) {
        Device deviceInStore = deviceMapper.getDeviceByDeviceId(device.getDeviceId());
        if (deviceInStore == null) {
            logger.warn("更新设备时未找到设备信息");
            return;
        }
        if (!StringUtils.isEmpty(device.getName())) {
            deviceInStore.setName(device.getName());
        }
        if (!StringUtils.isEmpty(device.getCharset())) {
            deviceInStore.setCharset(device.getCharset());
        }
        if (!StringUtils.isEmpty(device.getMediaServerId())) {
            deviceInStore.setMediaServerId(device.getMediaServerId());
        }
        //  目录订阅相关的信息
        if (device.getSubscribeCycleForCatalog() > 0) {
            if (deviceInStore.getSubscribeCycleForCatalog() == 0 || deviceInStore.getSubscribeCycleForCatalog() != device.getSubscribeCycleForCatalog()) {
                deviceInStore.setSubscribeCycleForCatalog(device.getSubscribeCycleForCatalog());
                // 开启订阅
                addCatalogSubscribe(deviceInStore);
            }
        }else if (device.getSubscribeCycleForCatalog() == 0) {
            if (deviceInStore.getSubscribeCycleForCatalog() != 0) {
                deviceInStore.setSubscribeCycleForCatalog(device.getSubscribeCycleForCatalog());
                // 取消订阅
                removeCatalogSubscribe(deviceInStore);
            }
        }
        // 移动位置订阅相关的信息
        if (device.getSubscribeCycleForMobilePosition() > 0) {
            if (deviceInStore.getSubscribeCycleForMobilePosition() == 0 || deviceInStore.getSubscribeCycleForMobilePosition() != device.getSubscribeCycleForMobilePosition()) {
                deviceInStore.setMobilePositionSubmissionInterval(device.getMobilePositionSubmissionInterval());
                deviceInStore.setSubscribeCycleForMobilePosition(device.getSubscribeCycleForMobilePosition());
                // 开启订阅
                addMobilePositionSubscribe(deviceInStore);
            }
        }else if (device.getSubscribeCycleForMobilePosition() == 0) {
            if (deviceInStore.getSubscribeCycleForMobilePosition() != 0) {
                // 取消订阅
                removeMobilePositionSubscribe(deviceInStore);
            }
        }
        String now = DateUtil.getNow();
        device.setUpdateTime(now);
        device.setCharset(device.getCharset().toUpperCase());
        device.setUpdateTime(DateUtil.getNow());
        if (deviceMapper.update(device) > 0) {
            redisCatchStorage.updateDevice(device);
        }
    }
}
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
@@ -18,6 +18,7 @@
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.storager.dao.MediaServerMapper;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.utils.redis.JedisUtil;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
@@ -89,14 +90,12 @@
    @Autowired
    JedisUtil jedisUtil;
    private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /**
     * 初始化
     */
    @Override
    public void updateVmServer(List<MediaServerItem>  mediaServerItemList) {
        logger.info("[缓存初始化] Media Server ");
        logger.info("[zlm] 缓存初始化 ");
        for (MediaServerItem mediaServerItem : mediaServerItemList) {
            if (StringUtils.isEmpty(mediaServerItem.getId())) {
                continue;
@@ -117,8 +116,8 @@
    }
    @Override
    public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, boolean ssrcCheck) {
        return openRTPServer(mediaServerItem, streamId, null, ssrcCheck,false);
    public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, boolean ssrcCheck, boolean isPlayback) {
        return openRTPServer(mediaServerItem, streamId, null, ssrcCheck,isPlayback);
    }
    @Override
@@ -231,7 +230,7 @@
        result.sort((serverItem1, serverItem2)->{
            int sortResult = 0;
            try {
                sortResult = format.parse(serverItem1.getCreateTime()).compareTo(format.parse(serverItem2.getCreateTime()));
                sortResult = DateUtil.format.parse(serverItem1.getCreateTime()).compareTo(DateUtil.format.parse(serverItem2.getCreateTime()));
            } catch (ParseException e) {
                e.printStackTrace();
            }
@@ -291,8 +290,8 @@
    @Override
    public WVPResult<String> add(MediaServerItem mediaServerItem) {
        WVPResult<String> result = new WVPResult<>();
        mediaServerItem.setCreateTime(this.format.format(System.currentTimeMillis()));
        mediaServerItem.setUpdateTime(this.format.format(System.currentTimeMillis()));
        mediaServerItem.setCreateTime(DateUtil.getNow());
        mediaServerItem.setUpdateTime(DateUtil.getNow());
        mediaServerItem.setHookAliveInterval(120);
        JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig(mediaServerItem);
        if (responseJSON != null) {
@@ -353,7 +352,7 @@
     */
    @Override
    public void zlmServerOnline(ZLMServerConfig zlmServerConfig) {
        logger.info("[ ZLM:{} ]-[ {}:{} ]正在连接",
        logger.info("[ZLM] 正在连接 : {} -> {}:{}",
                zlmServerConfig.getGeneralMediaServerId(), zlmServerConfig.getIp(), zlmServerConfig.getHttpPort());
        MediaServerItem serverItem = mediaServerMapper.queryOne(zlmServerConfig.getGeneralMediaServerId());
@@ -406,7 +405,7 @@
            setZLMConfig(serverItem, "0".equals(zlmServerConfig.getHookEnable()));
        }
        publisher.zlmOnlineEventPublish(serverItem.getId());
        logger.info("[ ZLM:{} ]-[ {}:{} ]连接成功",
        logger.info("[ZLM] 连接成功 {} - {}:{} ",
                zlmServerConfig.getGeneralMediaServerId(), zlmServerConfig.getIp(), zlmServerConfig.getHttpPort());
    }
@@ -484,7 +483,7 @@
     */
    @Override
    public void setZLMConfig(MediaServerItem mediaServerItem, boolean restart) {
        logger.info("[ ZLM:{} ]-[ {}:{} ]正在设置zlm",
        logger.info("[ZLM] 正在设置 :{} -> {}:{}",
                mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
        String protocol = sslEnabled ? "https" : "http";
        String hookPrex = String.format("%s://%s:%s/index/hook", protocol, mediaServerItem.getHookIp(), serverPort);
@@ -528,17 +527,17 @@
        if (responseJSON != null && responseJSON.getInteger("code") == 0) {
            if (restart) {
                logger.info("[ ZLM:{} ]-[ {}:{} ]设置zlm成功, 开始重启以保证配置生效",
                logger.info("[ZLM] 设置成功,开始重启以保证配置生效 {} -> {}:{}",
                        mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
                zlmresTfulUtils.restartServer(mediaServerItem);
            }else {
                logger.info("[ ZLM:{} ]-[ {}:{} ]设置zlm成功",
                logger.info("[ZLM] 设置成功 {} -> {}:{}",
                        mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
            }
        }else {
            logger.info("[ ZLM:{} ]-[ {}:{} ]设置zlm失败",
            logger.info("[ZLM] 设置zlm失败 {} -> {}:{}",
                    mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
        }
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
@@ -13,7 +13,7 @@
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
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.ZLMRESTfulUtils;
@@ -190,7 +190,7 @@
            if (mediaServerItem.isRtpEnable()) {
                streamId = String.format("%s_%s", device.getDeviceId(), channelId);
            }
            SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck());
            SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck(), false);
            play(mediaServerItem, ssrcInfo, device, channelId, (mediaServerItemInUse, response)->{
                if (hookEvent != null) {
                    hookEvent.response(mediaServerItem, response);
@@ -234,7 +234,7 @@
            streamId = String.format("%s_%s", device.getDeviceId(), channelId);
        }
        if (ssrcInfo == null) {
            ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck());
            ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck(), false);
        }
        // 超时处理
@@ -357,7 +357,7 @@
            return null;
        }
        MediaServerItem newMediaServerItem = getNewMediaServerItem(device);
        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, true);
        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, true, true);
        return playBack(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, inviteStreamCallback, callback);
    }
@@ -444,7 +444,7 @@
            return null;
        }
        MediaServerItem newMediaServerItem = getNewMediaServerItem(device);
        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, true);
        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, true, true);
        return download(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, downloadSpeed,infoCallBack, hookCallBack);
    }
src/main/java/com/genersoft/iot/vmp/service/impl/RedisAlarmMsgListener.java
@@ -4,20 +4,15 @@
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.bean.GPSMsgInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.genersoft.iot.vmp.utils.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
@Component
public class RedisAlarmMsgListener implements MessageListener {
@@ -32,8 +27,6 @@
    @Autowired
    private IVideoManagerStorage storage;
    private final SimpleDateFormat formatForGB = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    @Override
    public void onMessage(Message message, byte[] bytes) {
@@ -52,7 +45,7 @@
        deviceAlarm.setAlarmDescription(alarmChannelMessage.getAlarmDescription());
        deviceAlarm.setAlarmMethod("" + alarmChannelMessage.getAlarmSn());
        deviceAlarm.setAlarmPriority("1");
        deviceAlarm.setAlarmTime(formatForGB.format(System.currentTimeMillis()));
        deviceAlarm.setAlarmTime(DateUtil.getNow());
        deviceAlarm.setAlarmType("1");
        deviceAlarm.setLongitude(0);
        deviceAlarm.setLatitude(0);
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
@@ -112,23 +112,6 @@
    void clearCatchByDeviceId(String deviceId);
    /**
     * 获取mediaServer节点
     * @param mediaServerId
     * @return
     */
//    MediaServerItem getMediaInfo(String mediaServerId);
    /**
     * 设置所有设备离线
     */
    void outlineForAll();
    /**
     * 获取所有在线的
     */
    List<String> getOnlineForAll();
    /**
     * 在redis添加wvp的信息
     */
    void updateWVPInfo(JSONObject jsonObject, int time);
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java
@@ -25,22 +25,6 @@
     * @return true:存在  false:不存在
     */
    public boolean exists(String deviceId);
    /**
     * 视频设备创建
     *
     * @param device 设备对象
     * @return true:创建成功  false:创建失败
     */
    public boolean create(Device device);
    /**
     * 视频设备更新
     *
     * @param device 设备对象
     * @return true:创建成功  false:创建失败
     */
    public boolean updateDevice(Device device);
    /**
     * 添加设备通道
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java
@@ -257,4 +257,8 @@
    @Update(value = {"UPDATE device_channel SET latitude=${latitude}, longitude=${longitude} WHERE deviceId=#{deviceId} AND channelId=#{channelId}"})
    void updatePotion(String deviceId, String channelId, double longitude, double latitude);
    @Select("SELECT * FROM device_channel WHERE length(trim(streamId)) > 0")
    List<DeviceChannel> getAllChannelInPlay();
}
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java
@@ -99,4 +99,9 @@
    @Update("UPDATE device SET online=0")
    int outlineForAll();
    @Select("SELECT * FROM device WHERE online = 1")
    List<Device> getOnlineDevices();
    @Select("SELECT * FROM device WHERE ip = #{host} AND port=${port}")
    Device getDeviceByHostAndPort(String host, int port);
}
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
@@ -14,13 +14,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.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.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.*;
@SuppressWarnings("rawtypes")
@@ -37,8 +37,6 @@
    @Autowired
    private UserSetting userSetting;
    private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @Override
    public Long getCSEQ(String method) {
@@ -470,26 +468,6 @@
    }
    @Override
    public void outlineForAll() {
        List<Object> onlineDevices = redis.scan(VideoManagerConstants.KEEPLIVEKEY_PREFIX + userSetting.getServerId() + "_" + "*" );
        for (int i = 0; i < onlineDevices.size(); i++) {
            String key = (String) onlineDevices.get(i);
            redis.del(key);
        }
    }
    @Override
    public List<String> getOnlineForAll() {
        List<String> result = new ArrayList<>();
        List<Object> onlineDevices = redis.scan(VideoManagerConstants.KEEPLIVEKEY_PREFIX + userSetting.getServerId() + "_"  + "*" );
        for (int i = 0; i < onlineDevices.size(); i++) {
            String key = (String) onlineDevices.get(i);
            result.add((String) redis.get(key));
        }
        return result;
    }
    @Override
    public void updateWVPInfo(JSONObject jsonObject, int time) {
        String key = VideoManagerConstants.WVP_SERVER_PREFIX + userSetting.getServerId();
        redis.set(key, jsonObject, time);
@@ -638,7 +616,7 @@
    public void addCpuInfo(double cpuInfo) {
        String key = VideoManagerConstants.SYSTEM_INFO_CPU_PREFIX + userSetting.getServerId();
        SystemInfoDto<Double> systemInfoDto = new SystemInfoDto<>();
        systemInfoDto.setTime(format.format(System.currentTimeMillis()));
        systemInfoDto.setTime(DateUtil.getNow());
        systemInfoDto.setData(cpuInfo);
        redis.lSet(key, systemInfoDto);
        // 每秒一个,最多只存30个
@@ -653,7 +631,7 @@
    public void addMemInfo(double memInfo) {
        String key = VideoManagerConstants.SYSTEM_INFO_MEM_PREFIX + userSetting.getServerId();
        SystemInfoDto<Double> systemInfoDto = new SystemInfoDto<>();
        systemInfoDto.setTime(format.format(System.currentTimeMillis()));
        systemInfoDto.setTime(DateUtil.getNow());
        systemInfoDto.setData(memInfo);
        redis.lSet(key, systemInfoDto);
        // 每秒一个,最多只存30个
@@ -668,7 +646,7 @@
    public void addNetInfo(Map<String, String> networkInterfaces) {
        String key = VideoManagerConstants.SYSTEM_INFO_NET_PREFIX + userSetting.getServerId();
        SystemInfoDto<Map<String, String>> systemInfoDto = new SystemInfoDto<>();
        systemInfoDto.setTime(format.format(System.currentTimeMillis()));
        systemInfoDto.setTime(DateUtil.getNow());
        systemInfoDto.setData(networkInterfaces);
        redis.lSet(key, systemInfoDto);
        // 每秒一个,最多只存30个
@@ -702,7 +680,6 @@
    @Override
    public boolean deviceIsOnline(String deviceId) {
        String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + userSetting.getServerId() + "_" + deviceId;
        return redis.hasKey(key);
        return getDevice(deviceId).getOnline() == 1;
    }
}
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
@@ -13,6 +13,7 @@
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.storager.dao.*;
import com.genersoft.iot.vmp.storager.dao.dto.ChannelSourceInfo;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
@@ -26,8 +27,8 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**    
 * 视频设备数据存储-jdbc实现
@@ -91,9 +92,6 @@
    @Autowired
    private ParentPlatformMapper parentPlatformMapper;
    private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /**
     * 根据设备ID判断设备是否存在
     *
@@ -105,45 +103,6 @@
        return deviceMapper.getDeviceByDeviceId(deviceId) != null;
    }
    /**
     * 视频设备创建
     *
     * @param device 设备对象
     * @return true:创建成功  false:创建失败
     */
    @Override
    public synchronized boolean create(Device device) {
        redisCatchStorage.updateDevice(device);
        return deviceMapper.add(device) > 0;
    }
    /**
     * 视频设备更新
     *
     * @param device 设备对象
     * @return true:更新成功  false:更新失败
     */
    @Override
    public synchronized boolean updateDevice(Device device) {
        String now = this.format.format(System.currentTimeMillis());
        device.setUpdateTime(now);
        Device deviceByDeviceId = deviceMapper.getDeviceByDeviceId(device.getDeviceId());
        device.setCharset(device.getCharset().toUpperCase());
        if (deviceByDeviceId == null) {
            device.setCreateTime(now);
            redisCatchStorage.updateDevice(device);
            return deviceMapper.add(device) > 0;
        }else {
            redisCatchStorage.updateDevice(device);
            return deviceMapper.update(device) > 0;
        }
    }
    @Override
    public synchronized void updateChannel(String deviceId, DeviceChannel channel) {
        String channelId = channel.getChannelId();
@@ -152,7 +111,7 @@
        if (streamInfo != null) {
            channel.setStreamId(streamInfo.getStream());
        }
        String now = this.format.format(System.currentTimeMillis());
        String now = DateUtil.getNow();
        channel.setUpdateTime(now);
        DeviceChannel deviceChannel = deviceChannelMapper.queryChannel(deviceId, channelId);
        if (deviceChannel == null) {
@@ -178,7 +137,7 @@
                    if (streamInfo != null) {
                        channel.setStreamId(streamInfo.getStream());
                    }
                    String now = this.format.format(System.currentTimeMillis());
                    String now = DateUtil.getNow();
                    channel.setUpdateTime(now);
                    channel.setCreateTime(now);
                    addChannels.add(channel);
@@ -193,7 +152,7 @@
                    if (streamInfo != null) {
                        channel.setStreamId(streamInfo.getStream());
                    }
                    String now = this.format.format(System.currentTimeMillis());
                    String now = DateUtil.getNow();
                    channel.setUpdateTime(now);
                    if (channelsInStore.get(channel.getChannelId()) != null) {
                        updateChannels.add(channel);
@@ -239,17 +198,27 @@
        if (deviceChannelList == null) {
            return false;
        }
        List<DeviceChannel> allChannelInPlay = deviceChannelMapper.getAllChannelInPlay();
        Map<String,DeviceChannel> allChannelMapInPlay = new ConcurrentHashMap<>();
        if (allChannelInPlay.size() > 0) {
            for (DeviceChannel deviceChannel : allChannelInPlay) {
                allChannelMapInPlay.put(deviceChannel.getChannelId(), deviceChannel);
            }
        }
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        // 数据去重
        List<DeviceChannel> channels = new ArrayList<>();
        StringBuilder stringBuilder = new StringBuilder();
        Map<String, Integer> subContMap = new HashMap<>();
        if (deviceChannelList != null && deviceChannelList.size() > 1) {
        if (deviceChannelList.size() > 1) {
            // 数据去重
            Set<String> gbIdSet = new HashSet<>();
            for (DeviceChannel deviceChannel : deviceChannelList) {
                if (!gbIdSet.contains(deviceChannel.getChannelId())) {
                    gbIdSet.add(deviceChannel.getChannelId());
                    if (allChannelMapInPlay.containsKey(deviceChannel.getChannelId())) {
                        deviceChannel.setStreamId(allChannelMapInPlay.get(deviceChannel.getChannelId()).getStreamId());
                    }
                    channels.add(deviceChannel);
                    if (!StringUtils.isEmpty(deviceChannel.getParentId())) {
                        if (subContMap.get(deviceChannel.getParentId()) == null) {
@@ -732,7 +701,7 @@
        boolean result = false;
        streamProxyItem.setStreamType("proxy");
        streamProxyItem.setStatus(true);
        String now = this.format.format(System.currentTimeMillis());
        String now = DateUtil.getNow();
        streamProxyItem.setCreateTime(now);
        streamProxyItem.setCreateStamp(System.currentTimeMillis());
        try {
src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java
New file
@@ -0,0 +1,58 @@
package com.genersoft.iot.vmp.utils;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAccessor;
import java.util.Locale;
/**
 * 全局时间工具类
 * @author lin
 */
public class DateUtil {
    private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss";
    public static final String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss";
    public static final SimpleDateFormat formatISO8601 = new SimpleDateFormat(yyyy_MM_dd_T_HH_mm_ss_SSSXXX, Locale.getDefault());
    public static final SimpleDateFormat format = new SimpleDateFormat(yyyy_MM_dd_HH_mm_ss, Locale.getDefault());
    public static final DateTimeFormatter formatterISO8601 = DateTimeFormatter.ofPattern(yyyy_MM_dd_T_HH_mm_ss_SSSXXX, Locale.getDefault()).withZone(ZoneId.systemDefault());
    public static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(yyyy_MM_dd_HH_mm_ss, Locale.getDefault()).withZone(ZoneId.systemDefault());
    public static String yyyy_MM_dd_HH_mm_ssToISO8601(String formatTime) {
        return formatterISO8601.format(formatter.parse(formatTime));
    }
    public static String ISO8601Toyyyy_MM_dd_HH_mm_ss(String formatTime) {
        return formatter.format(formatterISO8601.parse(formatTime));
    }
    public static long yyyy_MM_dd_HH_mm_ssToTimestamp(String formatTime) {
        TemporalAccessor temporalAccessor = formatter.parse(formatTime);
        Instant instant = Instant.from(temporalAccessor);
        return instant.getEpochSecond();
    }
    public static String getNow() {
        LocalDateTime nowDateTime = LocalDateTime.now();
        return formatter.format(nowDateTime);
    }
    public static boolean verification(String timeStr, DateTimeFormatter dateTimeFormatter) {
        try {
            LocalDate.parse(timeStr, dateTimeFormatter);
            return true;
        }catch (DateTimeParseException exception) {
            return false;
        }
    }
}
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/MobilePosition/MobilePositionController.java
@@ -169,7 +169,7 @@
        Device device = storager.queryVideoDevice(deviceId);
        device.setSubscribeCycleForMobilePosition(Integer.parseInt(expires));
        device.setMobilePositionSubmissionInterval(Integer.parseInt(interval));
        storager.updateDevice(device);
        deviceService.updateDevice(device);
        String result = msg;
        if (deviceService.removeMobilePositionSubscribe(device)) {
            result += ",成功";
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/alarm/AlarmController.java
@@ -9,6 +9,7 @@
import com.genersoft.iot.vmp.service.IDeviceAlarmService;
import com.genersoft.iot.vmp.service.IGbStreamService;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.github.pagehelper.PageInfo;
import io.swagger.annotations.Api;
@@ -23,9 +24,7 @@
import org.springframework.web.bind.annotation.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@Api(tags = "报警信息管理")
@@ -45,9 +44,6 @@
    @Autowired
    private IVideoManagerStorage storage;
    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private SimpleDateFormat formatForGB = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    /**
     *  分页查询报警
@@ -104,10 +100,10 @@
        try {
            if (startTime != null) {
                format.parse(startTime);
                DateUtil.format.parse(startTime);
            }
            if (endTime != null) {
                format.parse(endTime);
                DateUtil.format.parse(endTime);
            }
        } catch (ParseException e) {
            return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
@@ -150,7 +146,7 @@
        }
        try {
            if (time != null) {
                format.parse(time);
                DateUtil.format.parse(time);
            }
        } catch (ParseException e) {
            return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
@@ -193,7 +189,7 @@
        deviceAlarm.setAlarmDescription("test");
        deviceAlarm.setAlarmMethod("1");
        deviceAlarm.setAlarmPriority("1");
        deviceAlarm.setAlarmTime(formatForGB.format(System.currentTimeMillis()));
        deviceAlarm.setAlarmTime(DateUtil.formatISO8601.format(System.currentTimeMillis()));
        deviceAlarm.setAlarmType("1");
        deviceAlarm.setLongitude(115.33333);
        deviceAlarm.setLatitude(39.33333);
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java
@@ -288,7 +288,8 @@
    public ResponseEntity<PageInfo> updateTransport(@PathVariable String deviceId, @PathVariable String streamMode){
        Device device = storager.queryVideoDevice(deviceId);
        device.setStreamMode(streamMode);
        storager.updateDevice(device);
//        storager.updateDevice(device);
        deviceService.updateDevice(device);
        return new ResponseEntity<>(null,HttpStatus.OK);
    }
@@ -305,51 +306,12 @@
    public ResponseEntity<WVPResult<String>> updateDevice(Device device){
        if (device != null && device.getDeviceId() != null) {
            Device deviceInStore = storager.queryVideoDevice(device.getDeviceId());
            if (!StringUtils.isEmpty(device.getName())) {
                deviceInStore.setName(device.getName());
            }
            if (!StringUtils.isEmpty(device.getCharset())) {
                deviceInStore.setCharset(device.getCharset());
            }
            if (!StringUtils.isEmpty(device.getMediaServerId())) {
                deviceInStore.setMediaServerId(device.getMediaServerId());
            }
            //  目录订阅相关的信息
            if (device.getSubscribeCycleForCatalog() > 0) {
                if (deviceInStore.getSubscribeCycleForCatalog() == 0 || deviceInStore.getSubscribeCycleForCatalog() != device.getSubscribeCycleForCatalog()) {
                    deviceInStore.setSubscribeCycleForCatalog(device.getSubscribeCycleForCatalog());
                    // 开启订阅
                    deviceService.addCatalogSubscribe(deviceInStore);
                }
            }else if (device.getSubscribeCycleForCatalog() == 0) {
                if (deviceInStore.getSubscribeCycleForCatalog() != 0) {
                    deviceInStore.setSubscribeCycleForCatalog(device.getSubscribeCycleForCatalog());
                    // 取消订阅
                    deviceService.removeCatalogSubscribe(deviceInStore);
                }
            }
            // 移动位置订阅相关的信息
            if (device.getSubscribeCycleForMobilePosition() > 0) {
                if (deviceInStore.getSubscribeCycleForMobilePosition() == 0 || deviceInStore.getSubscribeCycleForMobilePosition() != device.getSubscribeCycleForMobilePosition()) {
                    deviceInStore.setMobilePositionSubmissionInterval(device.getMobilePositionSubmissionInterval());
                    deviceInStore.setSubscribeCycleForMobilePosition(device.getSubscribeCycleForMobilePosition());
                    // 开启订阅
                    deviceService.addMobilePositionSubscribe(deviceInStore);
                }
            }else if (device.getSubscribeCycleForMobilePosition() == 0) {
                if (deviceInStore.getSubscribeCycleForMobilePosition() != 0) {
                    // 取消订阅
                    deviceService.removeMobilePositionSubscribe(deviceInStore);
                }
            }
            // TODO 报警订阅相关的信息
            storager.updateDevice(device);
            cmder.deviceInfoQuery(device);
            deviceService.updateDevice(device);
//            cmder.deviceInfoQuery(device);
        }
        WVPResult<String> result = new WVPResult<>();
        result.setCode(0);
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java
@@ -5,6 +5,8 @@
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.IPlayService;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
@@ -27,6 +29,7 @@
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import java.time.LocalDate;
import java.util.UUID;
@Api(tags = "国标录像")
@@ -60,15 +63,32 @@
            @ApiImplicitParam(name = "endTime", value = "结束时间", dataTypeClass = String.class),
    })
    @GetMapping("/query/{deviceId}/{channelId}")
    public DeferredResult<ResponseEntity<RecordInfo>> recordinfo(@PathVariable String deviceId,@PathVariable String channelId, String startTime,  String endTime){
    public DeferredResult<ResponseEntity<WVPResult<RecordInfo>>> recordinfo(@PathVariable String deviceId, @PathVariable String channelId, String startTime, String endTime){
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("录像信息查询 API调用,deviceId:%s ,startTime:%s, endTime:%s",deviceId, startTime, endTime));
        }
        DeferredResult<ResponseEntity<WVPResult<RecordInfo>>> result = new DeferredResult<>();
        if (!DateUtil.verification(startTime, DateUtil.formatter)){
            WVPResult<RecordInfo> wvpResult = new WVPResult<>();
            wvpResult.setCode(-1);
            wvpResult.setMsg("startTime error, format is " + DateUtil.yyyy_MM_dd_HH_mm_ss);
            ResponseEntity<WVPResult<RecordInfo>> resultResponseEntity = new ResponseEntity<>(wvpResult, HttpStatus.OK);
            result.setResult(resultResponseEntity);
            return result;
        }
        if (!DateUtil.verification(endTime, DateUtil.formatter)){
            WVPResult<RecordInfo> wvpResult = new WVPResult<>();
            wvpResult.setCode(-1);
            wvpResult.setMsg("endTime error, format is " + DateUtil.yyyy_MM_dd_HH_mm_ss);
            ResponseEntity<WVPResult<RecordInfo>> resultResponseEntity = new ResponseEntity<>(wvpResult, HttpStatus.OK);
            result.setResult(resultResponseEntity);
            return result;
        }
        Device device = storager.queryVideoDevice(deviceId);
        // 指定超时时间 1分钟30秒
        DeferredResult<ResponseEntity<RecordInfo>> result = new DeferredResult<>(90*1000L);
        String uuid = UUID.randomUUID().toString();
        int sn  =  (int)((Math.random()*9+1)*100000);
        String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + sn;
@@ -76,7 +96,10 @@
        msg.setId(uuid);
        msg.setKey(key);
        cmder.recordInfoQuery(device, channelId, startTime, endTime, sn, null, null, null, (eventResult -> {
            msg.setData("查询录像失败, status: " +  eventResult.statusCode + ", message: " + eventResult.msg );
            WVPResult<RecordInfo> wvpResult = new WVPResult<>();
            wvpResult.setCode(-1);
            wvpResult.setMsg("查询录像失败, status: " +  eventResult.statusCode + ", message: " + eventResult.msg);
            msg.setData(wvpResult);
            resultHolder.invokeResult(msg);
        }));
@@ -84,6 +107,10 @@
        resultHolder.put(key, uuid, result);
        result.onTimeout(()->{
            msg.setData("timeout");
            WVPResult<RecordInfo> wvpResult = new WVPResult<>();
            wvpResult.setCode(-1);
            wvpResult.setMsg("timeout");
            msg.setData(wvpResult);
            resultHolder.invokeResult(msg);
        });
        return result;
src/main/java/com/genersoft/iot/vmp/vmanager/log/LogController.java
@@ -3,6 +3,7 @@
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.service.ILogService;
import com.genersoft.iot.vmp.storager.dao.dto.LogDto;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.github.pagehelper.PageInfo;
import io.swagger.annotations.Api;
@@ -18,7 +19,6 @@
import org.springframework.web.bind.annotation.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
@Api(tags = "日志管理")
@CrossOrigin
@@ -33,8 +33,6 @@
    @Autowired
    private UserSetting userSetting;
    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /**
     *  分页查询日志
@@ -80,10 +78,10 @@
        try {
            if (startTime != null) {
                format.parse(startTime);
                DateUtil.format.parse(startTime);
            }
            if (endTime != null) {
                format.parse(endTime);
                DateUtil.format.parse(endTime);
            }
        } catch (ParseException e) {
            return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
src/main/java/com/genersoft/iot/vmp/vmanager/user/RoleController.java
@@ -2,9 +2,8 @@
import com.genersoft.iot.vmp.conf.security.SecurityUtils;
import com.genersoft.iot.vmp.service.IRoleService;
import com.genersoft.iot.vmp.service.IUserService;
import com.genersoft.iot.vmp.storager.dao.dto.Role;
import com.genersoft.iot.vmp.storager.dao.dto.User;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
@@ -13,12 +12,8 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.text.SimpleDateFormat;
import java.util.List;
@Api(tags = "角色管理")
@@ -29,8 +24,6 @@
    @Autowired
    private IRoleService roleService;
    private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @ApiOperation("添加角色")
    @ApiImplicitParams({
@@ -53,8 +46,8 @@
        Role role = new Role();
        role.setName(name);
        role.setAuthority(authority);
        role.setCreateTime(format.format(System.currentTimeMillis()));
        role.setUpdateTime(format.format(System.currentTimeMillis()));
        role.setCreateTime(DateUtil.getNow());
        role.setUpdateTime(DateUtil.getNow());
        int addResult = roleService.add(role);
src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java
@@ -6,6 +6,7 @@
import com.genersoft.iot.vmp.service.IUserService;
import com.genersoft.iot.vmp.storager.dao.dto.Role;
import com.genersoft.iot.vmp.storager.dao.dto.User;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
@@ -20,7 +21,6 @@
import org.springframework.web.bind.annotation.*;
import javax.security.sasl.AuthenticationException;
import java.text.SimpleDateFormat;
import java.util.List;
@Api(tags = "用户管理")
@@ -37,8 +37,6 @@
    @Autowired
    private IRoleService roleService;
    private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @ApiOperation("登录")
    @ApiImplicitParams({
@@ -135,8 +133,8 @@
            return new ResponseEntity<>(result, HttpStatus.OK);
        }
        user.setRole(role);
        user.setCreateTime(format.format(System.currentTimeMillis()));
        user.setUpdateTime(format.format(System.currentTimeMillis()));
        user.setCreateTime(DateUtil.getNow());
        user.setUpdateTime(DateUtil.getNow());
        int addResult = userService.addUser(user);
        result.setCode(addResult > 0 ? 0 : -1);
src/main/resources/all-application.yml
@@ -28,7 +28,7 @@
        poolMaxIdle: 500
        # [可选] 最大的等待时间(秒)
        poolMaxWait: 5
    # [可选] jdbc数据库配置, 项目使用sqlite作为数据库,一般不需要配置
    # [必选] jdbc数据库配置
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
src/main/resources/application-dev.yml
@@ -16,7 +16,6 @@
        password: face2020
        # [可选] 超时时间
        timeout: 10000
        # [可选] jdbc数据库配置, 项目使用sqlite作为数据库,一般不需要配置
        # mysql数据源
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
src/main/resources/application-docker.yml
@@ -16,7 +16,7 @@
        password: ${REDIS_PWD:root}
        # [可选] 超时时间
        timeout: 10000
    # [可选] jdbc数据库配置, 项目使用sqlite作为数据库,一般不需要配置
    # [必选] jdbc数据库配置
    datasource:
        # 使用mysql 打开23-28行注释, 删除29-36行
         name: wvp
src/test/java/com/genersoft/iot/vmp/service/impl/DeviceAlarmServiceImplTest.java
@@ -2,12 +2,12 @@
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
import com.genersoft.iot.vmp.service.IDeviceAlarmService;
import com.genersoft.iot.vmp.utils.DateUtil;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -17,8 +17,6 @@
    @Resource
    private IDeviceAlarmService deviceAlarmService;
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @org.junit.jupiter.api.Test
    void getAllAlarm() {
@@ -67,7 +65,7 @@
             */
            deviceAlarm.setAlarmMethod((int)(Math.random()*7 + 1) + "");
            Date date = randomDate("2021-01-01 00:00:00", "2021-06-01 00:00:00");
            deviceAlarm.setAlarmTime(format.format(date));
            deviceAlarm.setAlarmTime(DateUtil.format.format(date));
            /**
             * 报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级 警情-
             */
@@ -90,8 +88,8 @@
    private Date randomDate(String beginDate, String endDate) {
        try {
            Date start = format.parse(beginDate);//构造开始日期
            Date end = format.parse(endDate);//构造结束日期
            Date start = DateUtil.format.parse(beginDate);//构造开始日期
            Date end = DateUtil.format.parse(endDate);//构造结束日期
            //getTime()表示返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
            if (start.getTime() >= end.getTime()) {
                return null;
src/test/java/com/genersoft/iot/vmp/service/impl/RoleServiceImplTest.java
@@ -4,12 +4,12 @@
import com.genersoft.iot.vmp.service.IUserService;
import com.genersoft.iot.vmp.storager.dao.dto.Role;
import com.genersoft.iot.vmp.storager.dao.dto.User;
import com.genersoft.iot.vmp.utils.DateUtil;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.List;
@@ -20,7 +20,6 @@
    @Resource
    private IRoleService roleService;
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @org.junit.jupiter.api.Test
    void getAllUser() {
        List<Role> all = roleService.getAll();
@@ -35,8 +34,8 @@
            Role role = new Role();
            role.setName("test+" + i);
            role.setAuthority("adadadda");
            role.setCreateTime(format.format(System.currentTimeMillis()));
            role.setUpdateTime(format.format(System.currentTimeMillis()));
            role.setCreateTime(DateUtil.getNow());
            role.setUpdateTime(DateUtil.getNow());
            roleService.add(role);
        }
    }
src/test/java/com/genersoft/iot/vmp/service/impl/UserServiceImplTest.java
@@ -1,17 +1,14 @@
package com.genersoft.iot.vmp.service.impl;
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
import com.genersoft.iot.vmp.service.IDeviceAlarmService;
import com.genersoft.iot.vmp.service.IUserService;
import com.genersoft.iot.vmp.storager.dao.dto.Role;
import com.genersoft.iot.vmp.storager.dao.dto.User;
import com.genersoft.iot.vmp.utils.DateUtil;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
@@ -22,7 +19,6 @@
    @Resource
    private IUserService userService;
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @org.junit.jupiter.api.Test
    void getAllUser() {
@@ -42,8 +38,8 @@
            Role role = new Role();
            role.setId(1);
            user.setRole(role);
            user.setCreateTime(format.format(System.currentTimeMillis()));
            user.setUpdateTime(format.format(System.currentTimeMillis()));
            user.setCreateTime(DateUtil.getNow());
            user.setUpdateTime(DateUtil.getNow());
            userService.addUser(user);
        }
    }
@@ -62,7 +58,7 @@
        Role role = new Role();
        role.setId(2);
        user.setRole(role);
        user.setUpdateTime(format.format(System.currentTimeMillis()));
        user.setUpdateTime(DateUtil.getNow());
        userService.updateUsers(user);
    }
web_src/src/components/channelList.vue
@@ -241,10 +241,12 @@
          that.$message.error(res.data.msg);
        }
      }).catch(function (e) {
        that.isLoging = false;
        that.$message.error("请求超时");
      });
    },
    queryRecords: function (itemData) {
      var format = moment().format("YYYY-M-D");
      var format = moment().format("yyyy-MM-DD");
      let deviceId = this.deviceId;
      let channelId = itemData.channelId;
      this.$refs.devicePlayer.openDialog("record", deviceId, channelId, {date: format})
web_src/src/components/control.vue
@@ -576,7 +576,7 @@
      let that = this;
      this.$axios({
        method: 'get',
        url: '/zlm/' + that.mediaServerChoose + '/index/api/kick_session&id=' + id
        url: '/zlm/' + that.mediaServerChoose + '/index/api/kick_session?id=' + id
      }).then(function (res) {
        that.getAllSession();
        that.$message({
web_src/src/components/dialog/devicePlayer.vue
@@ -33,10 +33,100 @@
                    <div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
                        <span style="width: 5rem; line-height: 2.5rem; text-align: right;">资源地址:</span>
                        <el-input v-model="getPlayerShared.sharedRtmp" :disabled="true" >
                          <template slot="append">
                            <i class="cpoy-btn el-icon-document-copy"  title="点击拷贝" v-clipboard="getPlayerShared.sharedRtmp" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i>
                          </template>
                          <el-button slot="append" icon="el-icon-document-copy" title="点击拷贝" v-clipboard="getPlayerShared.sharedRtmp" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></el-button>
                            <el-dropdown slot="prepend" v-if="streamInfo" trigger="click" @command="copyUrl">
                              <el-button >
                                更多地址<i class="el-icon-arrow-down el-icon--right"></i>
                              </el-button>
                              <el-dropdown-menu slot="dropdown" >
                                <el-dropdown-item :command="streamInfo.flv">
                                  <el-tag >FLV:</el-tag>
                                  <span>{{ streamInfo.flv }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.https_flv">
                                  <el-tag >FLV(https):</el-tag>
                                  <span>{{ streamInfo.https_flv }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.ws_flv">
                                  <el-tag  >FLV(ws):</el-tag>
                                  <span >{{ streamInfo.ws_flv }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.wss_flv">
                                  <el-tag  >FLV(wss):</el-tag>
                                  <span>{{ streamInfo.wss_flv }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.fmp4">
                                  <el-tag >FMP4:</el-tag>
                                  <span>{{ streamInfo.fmp4 }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.https_fmp4">
                                  <el-tag >FMP4(https):</el-tag>
                                  <span>{{ streamInfo.https_fmp4 }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.ws_fmp4">
                                  <el-tag >FMP4(ws):</el-tag>
                                  <span>{{ streamInfo.ws_fmp4 }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.wss_fmp4">
                                  <el-tag >FMP4(wss):</el-tag>
                                  <span>{{ streamInfo.wss_fmp4 }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.hls">
                                  <el-tag>HLS:</el-tag>
                                  <span>{{ streamInfo.hls }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.https_hls">
                                  <el-tag >HLS(https):</el-tag>
                                  <span>{{ streamInfo.https_hls }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.ws_hls">
                                  <el-tag >HLS(ws):</el-tag>
                                  <span>{{ streamInfo.ws_hls }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.wss_hls">
                                  <el-tag >HLS(wss):</el-tag>
                                  <span>{{ streamInfo.wss_hls }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.ts">
                                  <el-tag>TS:</el-tag>
                                  <span>{{ streamInfo.ts }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.https_ts">
                                  <el-tag>TS(https):</el-tag>
                                  <span>{{ streamInfo.https_ts }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.ws_ts">
                                  <el-tag>TS(ws):</el-tag>
                                  <span>{{ streamInfo.ws_ts }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.wss_ts">
                                  <el-tag>TS(wss):</el-tag>
                                  <span>{{ streamInfo.wss_ts }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.rtc">
                                  <el-tag >RTC:</el-tag>
                                  <span>{{ streamInfo.rtc }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.rtmp">
                                  <el-tag >RTMP:</el-tag>
                                  <span>{{ streamInfo.rtmp }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.rtmps">
                                  <el-tag >RTMPS:</el-tag>
                                  <span>{{ streamInfo.rtmps }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.rtsp">
                                  <el-tag >RTSP:</el-tag>
                                  <span>{{ streamInfo.rtsp }}</span>
                                </el-dropdown-item>
                                <el-dropdown-item :command="streamInfo.rtsps">
                                  <el-tag >RTSPS:</el-tag>
                                  <span>{{ streamInfo.rtsps }}</span>
                                </el-dropdown-item>
                              </el-dropdown-menu>
                            </el-dropdown>
                        </el-input>
                    </div>
                </el-tab-pane>
                <!--{"code":0,"data":{"paths":["22-29-30.mp4"],"rootPath":"/home/kkkkk/Documents/ZLMediaKit/release/linux/Debug/www/record/hls/kkkkk/2020-05-11/"}}-->
@@ -117,27 +207,27 @@
                        <div class="control-panel">
                            <el-button-group>
                                <el-tag style="position :absolute; left: 0rem; top: 0rem; width: 5rem; text-align: center" size="medium" type="info">预置位编号</el-tag>
                                <el-tag style="position :absolute; left: 0rem; top: 0rem; width: 5rem; text-align: center" size="medium">预置位编号</el-tag>
                                <el-input-number style="position: absolute; left: 5rem; top: 0rem; width: 6rem" size="mini" v-model="presetPos" controls-position="right" :precision="0" :step="1" :min="1" :max="255"></el-input-number>
                                <el-button style="position: absolute; left: 11rem; top: 0rem; width: 5rem" size="mini" icon="el-icon-add-location" @click="presetPosition(129, presetPos)">设置</el-button>
                                <el-button style="position: absolute; left: 27rem; top: 0rem; width: 5rem" size="mini" type="primary" icon="el-icon-place" @click="presetPosition(130, presetPos)">调用</el-button>
                                <el-button style="position: absolute; left: 16rem; top: 0rem; width: 5rem" size="mini" icon="el-icon-delete-location" @click="presetPosition(131, presetPos)">删除</el-button>
                                <el-tag style="position :absolute; left: 0rem; top: 2.5rem; width: 5rem; text-align: center" size="medium" type="info">巡航速度</el-tag>
                                <el-tag style="position :absolute; left: 0rem; top: 2.5rem; width: 5rem; text-align: center" size="medium">巡航速度</el-tag>
                                <el-input-number style="position: absolute; left: 5rem; top: 2.5rem; width: 6rem" size="mini" v-model="cruisingSpeed" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>
                                <el-button style="position: absolute; left: 11rem; top: 2.5rem; width: 5rem" size="mini" icon="el-icon-loading" @click="setSpeedOrTime(134, cruisingGroup, cruisingSpeed)">设置</el-button>
                                <el-tag style="position :absolute; left: 16rem; top: 2.5rem; width: 5rem; text-align: center" size="medium" type="info">停留时间</el-tag>
                                <el-tag style="position :absolute; left: 16rem; top: 2.5rem; width: 5rem; text-align: center" size="medium">停留时间</el-tag>
                                <el-input-number style="position: absolute; left: 21rem; top: 2.5rem; width: 6rem" size="mini" v-model="cruisingTime" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>
                                <el-button style="position: absolute; left: 27rem; top: 2.5rem; width: 5rem" size="mini" icon="el-icon-timer" @click="setSpeedOrTime(135, cruisingGroup, cruisingTime)">设置</el-button>
                                <el-tag style="position :absolute; left: 0rem; top: 4.5rem; width: 5rem; text-align: center" size="medium" type="info">巡航组编号</el-tag>
                                <el-tag style="position :absolute; left: 0rem; top: 4.5rem; width: 5rem; text-align: center" size="medium">巡航组编号</el-tag>
                                <el-input-number style="position: absolute; left: 5rem; top: 4.5rem; width: 6rem" size="mini" v-model="cruisingGroup" controls-position="right" :precision="0" :min="0" :max="255"></el-input-number>
                                <el-button style="position: absolute; left: 11rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-add-location" @click="setCommand(132, cruisingGroup, presetPos)">添加点</el-button>
                                <el-button style="position: absolute; left: 16rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-delete-location" @click="setCommand(133, cruisingGroup, presetPos)">删除点</el-button>
                                <el-button style="position: absolute; left: 21rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-delete" @click="setCommand(133, cruisingGroup, 0)">删除组</el-button>
                                <el-button style="position: absolute; left: 27rem; top: 5rem; width: 5rem" size="mini" type="primary" icon="el-icon-video-camera-solid" @click="setCommand(136, cruisingGroup, 0)">巡航</el-button>
                                <el-tag style="position :absolute; left: 0rem; top: 7rem; width: 5rem; text-align: center" size="medium" type="info">扫描速度</el-tag>
                                <el-tag style="position :absolute; left: 0rem; top: 7rem; width: 5rem; text-align: center" size="medium">扫描速度</el-tag>
                                <el-input-number style="position: absolute; left: 5rem; top: 7rem; width: 6rem" size="mini" v-model="scanSpeed" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>
                                <el-button style="position: absolute; left: 11rem; top: 7rem; width: 5rem" size="mini" icon="el-icon-loading" @click="setSpeedOrTime(138, scanGroup, scanSpeed)">设置</el-button>
                                <el-tag style="position :absolute; left: 0rem; top: 9rem; width: 5rem; text-align: center" size="medium" type="info">扫描组编号</el-tag>
                                <el-tag style="position :absolute; left: 0rem; top: 9rem; width: 5rem; text-align: center" size="medium">扫描组编号</el-tag>
                                <el-input-number style="position: absolute; left: 5rem; top: 9rem; width: 6rem" size="mini" v-model="scanGroup" controls-position="right" :precision="0" :step="1" :min="0" :max="255"></el-input-number>
                                <el-button style="position: absolute; left: 11rem; top: 9rem; width: 5rem" size="mini" icon="el-icon-d-arrow-left" @click="setCommand(137, scanGroup, 1)">左边界</el-button>
                                <el-button style="position: absolute; left: 16rem; top: 9rem; width: 5rem" size="mini" icon="el-icon-d-arrow-right" @click="setCommand(137, scanGroup, 2)">右边界</el-button>
@@ -174,6 +264,7 @@
                    </div>
                </el-tab-pane>
            </el-tabs>
        </div>
    </el-dialog>
@@ -244,6 +335,7 @@
            seekTime: 0,
            recordStartTime: 0,
            showTimeText: "00:00:00",
            streamInfo: null,
        };
    },
    methods: {
@@ -306,6 +398,7 @@
            console.log(val)
        },
        play: function (streamInfo, hasAudio) {
            this.streamInfo = streamInfo;
            this.hasAudio = hasAudio;
            this.isLoging = false;
            // this.videoUrl = streamInfo.rtc;
@@ -453,9 +546,19 @@
                method: 'get',
                url: '/api/gb_record/query/' + this.deviceId + '/' + this.channelId + '?startTime=' + startTime + '&endTime=' + endTime
            }).then(function (res) {
                console.log(res)
                if(res.data.code === 0) {
                // 处理时间信息
                that.videoHistory.searchHistoryResult = res.data.recordList;
                  that.videoHistory.searchHistoryResult = res.data.data.recordList;
                that.recordsLoading = false;
                }else {
                  this.$message({
                    showClose: true,
                    message: res.data.msg,
                    type: "error",
                  });
                }
            }).catch(function (e) {
                console.log(e.message);
                // that.videoHistory.searchHistoryResult = falsificationData.recordData;
@@ -636,6 +739,14 @@
            console.log(resultArray)
            return resultArray;
        },
        copyUrl: function (dropdownItem){
            console.log(dropdownItem)
            this.$copyText(dropdownItem).then((e)=> {
              this.$message.success("成功拷贝到粘贴板");
            }, (e)=> {
            })
        },
        gbPlay(){
          console.log('前端控制:播放');
          this.$axios({
@@ -671,8 +782,13 @@
          this.$axios({
            method: 'get',
            url: `/api/playback/seek/${this.streamId }/` + Math.floor(this.seekTime * val / 100000)
          }).then(function (res) {});
        }
          }).then( (res)=> {
            setTimeout(()=>{
              this.$refs.videoPlayer.play(this.videoUrl)
            }, 600)
          });
        },
    }
};
web_src/src/components/dialog/recordDownload.vue
@@ -172,6 +172,7 @@
              isEnd: true,
            }
          }).then((res) => {
            console.log(res)
            if (res.data.code == 0) {
                this.percentage = parseFloat(res.data.data.percentage)*100
                 if (res.data.data[0].percentage === '1') {