Просмотр исходного кода

Merge branch '648540858:wvp-28181-2.0' into wvp-28181-2.0

hotcoffie 3 лет назад
Родитель
Сommit
1d753b48c0
79 измененных файлов с 1163 добавлено и 1209 удалено
  1. 0 1
      README.md
  2. 0 7
      pom.xml
  3. 0 2
      src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
  4. 2 3
      src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java
  5. 21 18
      src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java
  6. 3 5
      src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java
  7. 2 1
      src/main/java/com/genersoft/iot/vmp/conf/ThreadPoolTaskConfig.java
  8. 11 4
      src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
  9. 2 8
      src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java
  10. 4 4
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/CatalogData.java
  11. 0 13
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
  12. 11 3
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java
  13. 14 14
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java
  14. 37 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java
  15. 1 2
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java
  16. 9 25
      src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java
  17. 14 14
      src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java
  18. 25 0
      src/main/java/com/genersoft/iot/vmp/gb28181/event/device/RequestTimeoutEvent.java
  19. 41 0
      src/main/java/com/genersoft/iot/vmp/gb28181/event/device/RequestTimeoutEventImpl.java
  20. 8 17
      src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/KeepaliveTimeoutListenerForPlatform.java
  21. 0 63
      src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/KeepliveTimeoutListener.java
  22. 0 40
      src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/OfflineEvent.java
  23. 0 98
      src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/OfflineEventListener.java
  24. 0 51
      src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEvent.java
  25. 0 110
      src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEventListener.java
  26. 12 19
      src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java
  27. 91 0
      src/main/java/com/genersoft/iot/vmp/gb28181/session/RecordDataCatch.java
  28. 2 2
      src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java
  29. 10 18
      src/main/java/com/genersoft/iot/vmp/conf/runner/SipDeviceRunner.java
  30. 37 7
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java
  31. 0 76
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/CheckForAllRecordsThread.java
  32. 36 23
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  33. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
  34. 33 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
  35. 10 11
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
  36. 17 16
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java
  37. 21 22
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java
  38. 31 29
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java
  39. 2 3
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java
  40. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/RecordInfoQueryMessageHandler.java
  41. 0 3
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java
  42. 14 4
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceInfoResponseMessageHandler.java
  43. 13 6
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceStatusResponseMessageHandler.java
  44. 0 14
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/PresetQueryResponseMessageHandler.java
  45. 49 52
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java
  46. 0 57
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/DateUtil.java
  47. 1 11
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
  48. 27 59
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
  49. 2 7
      src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMStatusEventListener.java
  50. 62 7
      src/main/java/com/genersoft/iot/vmp/service/IDeviceService.java
  51. 1 1
      src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java
  52. 178 5
      src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
  53. 13 14
      src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
  54. 5 5
      src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
  55. 2 9
      src/main/java/com/genersoft/iot/vmp/service/impl/RedisAlarmMsgListener.java
  56. 0 17
      src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
  57. 0 16
      src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java
  58. 4 0
      src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java
  59. 5 0
      src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java
  60. 5 28
      src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
  61. 17 48
      src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
  62. 58 0
      src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java
  63. 1 1
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/MobilePosition/MobilePositionController.java
  64. 5 9
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/alarm/AlarmController.java
  65. 4 42
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java
  66. 30 3
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java
  67. 3 5
      src/main/java/com/genersoft/iot/vmp/vmanager/log/LogController.java
  68. 3 10
      src/main/java/com/genersoft/iot/vmp/vmanager/user/RoleController.java
  69. 3 5
      src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java
  70. 1 1
      src/main/resources/all-application.yml
  71. 0 1
      src/main/resources/application-dev.yml
  72. 1 1
      src/main/resources/application-docker.yml
  73. 4 6
      src/test/java/com/genersoft/iot/vmp/service/impl/DeviceAlarmServiceImplTest.java
  74. 3 4
      src/test/java/com/genersoft/iot/vmp/service/impl/RoleServiceImplTest.java
  75. 4 8
      src/test/java/com/genersoft/iot/vmp/service/impl/UserServiceImplTest.java
  76. 4 2
      web_src/src/components/channelList.vue
  77. 1 1
      web_src/src/components/control.vue
  78. 130 14
      web_src/src/components/dialog/devicePlayer.vue
  79. 1 0
      web_src/src/components/dialog/recordDownload.vue

+ 0 - 1
README.md

@@ -106,7 +106,6 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
 - [X] 添加RTMP视频
 - [X] 云端录像(需要部署单独服务配合使用)
 - [X] 多流媒体节点,自动选择负载最低的节点使用。
-- [X] 支持使用mysql作为数据库,默认sqlite3,开箱即用。
 - [X] WEB端支持播放H264与H265,音频支持G.711A/G.711U/AAC,覆盖国标常用编码格式。
 
 [//]: # (# docker快速体验)

+ 0 - 7
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>

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

@@ -47,8 +47,6 @@ public class VideoManagerConstants {
 	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";
 

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

@@ -4,6 +4,7 @@ import com.genersoft.iot.vmp.common.ApiSaveConstant;
 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.annotation.WebFilter;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
-import java.text.SimpleDateFormat;
 
 /**
  * @author lin
@@ -26,7 +26,6 @@ public class ApiAccessFilter extends OncePerRequestFilter {
 
     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 @@ public class ApiAccessFilter extends OncePerRequestFilter {
             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()),

+ 21 - 18
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.context.annotation.Bean;
 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 @@ public class DynamicTask {
      * 循环执行的任务
      * @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 class DynamicTask {
      */
     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);
         }
     }
 

+ 3 - 5
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 @@ public class MediaConfig{
         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;
     }

+ 2 - 1
src/main/java/com/genersoft/iot/vmp/conf/ThreadPoolTaskConfig.java

@@ -35,10 +35,11 @@ public class ThreadPoolTaskConfig {
      * 允许线程空闲时间(单位:默认为秒)
      */
     private static final int keepAliveTime = 30;
+
     /**
      * 缓冲队列大小
      */
-    private static final int queueCapacity = 500;
+    private static final int queueCapacity = 10000;
     /**
      * 线程池名前缀
      */

+ 11 - 4
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java

@@ -48,8 +48,15 @@ public class SipLayer{
 		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 @@ public class SipLayer{
 			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 @@ public class SipLayer{
 		} 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;
 	}
 

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

@@ -27,8 +27,7 @@ package com.genersoft.iot.vmp.gb28181.auth;
 
 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 @@ public class DigestServerAuthenticationHelper  {
      * @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);
     }
 

+ 4 - 4
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 @@ public class CatalogData {
         this.channelList = channelList;
     }
 
-    public Date getLastTime() {
+    public Instant getLastTime() {
         return lastTime;
     }
 
-    public void setLastTime(Date lastTime) {
+    public void setLastTime(Instant lastTime) {
         this.lastTime = lastTime;
     }
 

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

@@ -99,11 +99,6 @@ public class Device {
 	 */
 	private String mediaServerId;
 
-	/**
-	 * 首次注册
-	 */
-	private boolean firsRegister;
-
 	/**
 	 * 字符集, 支持 UTF-8 与 GB2312
 	 */
@@ -279,14 +274,6 @@ public class Device {
 		this.mediaServerId = mediaServerId;
 	}
 
-	public boolean isFirsRegister() {
-		return firsRegister;
-	}
-
-	public void setFirsRegister(boolean firsRegister) {
-		this.firsRegister = firsRegister;
-	}
-
 	public String getCharset() {
 		return charset;
 	}

+ 11 - 3
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 @@ public class RecordInfo {
 	private String name;
 	
 	private int sumNum;
+
+	private Instant lastTime;
 	
 	private List<RecordItem> recordList;
 
@@ -71,4 +71,12 @@ public class RecordInfo {
 	public void setSn(String sn) {
 		this.sn = sn;
 	}
+
+	public Instant getLastTime() {
+		return lastTime;
+	}
+
+	public void setLastTime(Instant lastTime) {
+		this.lastTime = lastTime;
+	}
 }

+ 14 - 14
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 @@ public class RecordItem  implements Comparable<RecordItem>{
 
 	@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) {
-				return -1;
-			}else {
-				return 1;
-			}
-		} catch (ParseException e) {
-			e.printStackTrace();
+		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;
 		}
-		return 0;
+
 	}
 }

+ 37 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java

@@ -76,6 +76,21 @@ public class SendRtpItem {
      */
     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;
+
     /**
      * 播放类型
      */
@@ -221,5 +236,27 @@ public class SendRtpItem {
         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;
+    }
 }

+ 1 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java

@@ -61,12 +61,11 @@ public class SubscribeHolder {
         // 添加任务处理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);

+ 9 - 25
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.ZLMOfflineEvent;
 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 @@ public class EventPublisher {
 
 	@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 class EventPublisher {
 	}
 
 
+	public void requestTimeOut(TimeoutEvent timeoutEvent) {
+		RequestTimeoutEvent requestTimeoutEvent = new RequestTimeoutEvent(this);
+		requestTimeoutEvent.setTimeoutEvent(timeoutEvent);
+		applicationEventPublisher.publishEvent(requestTimeoutEvent);
+	}
+
+
 	/**
 	 *
 	 * @param platformId

+ 14 - 14
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java

@@ -9,11 +9,14 @@ import org.springframework.stereotype.Component;
 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 @@ public class SipSubscribe {
 
     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(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 class SipSubscribe {
 
     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) {

+ 25 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/event/device/RequestTimeoutEvent.java

@@ -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;
+    }
+}

+ 41 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/event/device/RequestTimeoutEventImpl.java

@@ -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());
+            }
+        }
+    }
+}

+ 8 - 17
src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/KeepaliveTimeoutListenerForPlatform.java

@@ -17,9 +17,8 @@ import com.genersoft.iot.vmp.common.VideoManagerConstants;
 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 @@ public class KeepaliveTimeoutListenerForPlatform extends RedisKeyExpirationEvent
         // 平台心跳到期,需要重发, 判断是否已经多次未收到心跳回复, 多次未收到,则重新发起注册, 注册尝试多次未得到回复,则认为平台离线
         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 @@ public class KeepaliveTimeoutListenerForPlatform extends RedisKeyExpirationEvent
                 sipSubscribe.getErrorSubscribe(callId).response(eventResult);
             }
         }
-
     }
 }

+ 0 - 63
src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/KeepliveTimeoutListener.java

@@ -1,63 +0,0 @@
-package com.genersoft.iot.vmp.gb28181.event.offline;
-
-import com.genersoft.iot.vmp.conf.RedisKeyExpirationEventMessageListener;
-import com.genersoft.iot.vmp.conf.UserSetting;
-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.listener.RedisMessageListenerContainer;
-import org.springframework.stereotype.Component;
-
-import com.genersoft.iot.vmp.common.VideoManagerConstants;
-import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
-
-/**    
- * @description:设备心跳超时监听,借助redis过期特性,进行监听,监听到说明设备心跳超时,发送离线事件
- * @author: swwheihei
- * @date:   2020年5月6日 上午11:35:46     
- */
-@Component
-public class KeepliveTimeoutListener extends RedisKeyExpirationEventMessageListener {
-
-    private Logger logger = LoggerFactory.getLogger(KeepliveTimeoutListener.class);
-
-	@Autowired
-	private EventPublisher publisher;
-
-	@Autowired
-	private UserSetting userSetting;
-
-    public KeepliveTimeoutListener(RedisMessageListenerContainer listenerContainer, UserSetting userSetting) {
-        super(listenerContainer, userSetting);
-    }
-
-    @Override
-    public void init() {
-        if (!userSetting.getRedisConfig()) {
-            // 配置springboot默认Config为空,即不让应用去修改redis的默认配置,因为Redis服务出于安全会禁用CONFIG命令给远程用户使用
-            setKeyspaceNotificationsConfigParameter("");
-        }
-        super.init();
-    }
-
-
-	/**
-     * 监听失效的key,key格式为keeplive_deviceId
-     * @param message
-     * @param pattern
-     */
-    @Override
-    public void onMessage(Message message, byte[] pattern) {
-        //  获取失效的key
-        String expiredKey = message.toString();
-        String KEEPLIVEKEY_PREFIX = VideoManagerConstants.KEEPLIVEKEY_PREFIX + userSetting.getServerId() + "_";
-        if(!expiredKey.startsWith(KEEPLIVEKEY_PREFIX)){
-        	logger.debug("收到redis过期监听,但开头不是"+KEEPLIVEKEY_PREFIX+",忽略");
-        	return;
-        }
-        
-        String deviceId = expiredKey.substring(KEEPLIVEKEY_PREFIX.length(),expiredKey.length());
-        publisher.outlineEventPublish(deviceId, VideoManagerConstants.EVENT_OUTLINE_TIMEOUT);
-    }
-}

+ 0 - 40
src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/OfflineEvent.java

@@ -1,40 +0,0 @@
-package com.genersoft.iot.vmp.gb28181.event.offline;
-
-import org.springframework.context.ApplicationEvent;
-
-/**    
- * @description: 离线事件类   
- * @author: swwheihei
- * @date:   2020年5月6日 上午11:33:13     
- */
-public class OfflineEvent extends ApplicationEvent {
-	
-	/**
-	 *
-	 */
-	private static final long serialVersionUID = 1L;
-
-	public OfflineEvent(Object source) {
-		super(source);
-	}
-
-	private String deviceId;
-	
-	private String from;
-
-	public String getDeviceId() {
-		return deviceId;
-	}
-
-	public void setDeviceId(String deviceId) {
-		this.deviceId = deviceId;
-	}
-
-	public String getFrom() {
-		return from;
-	}
-
-	public void setFrom(String from) {
-		this.from = from;
-	}
-}

+ 0 - 98
src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/OfflineEventListener.java

@@ -1,98 +0,0 @@
-package com.genersoft.iot.vmp.gb28181.event.offline;
-
-import com.genersoft.iot.vmp.conf.UserSetting;
-import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
-import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
-import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
-import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
-import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
-import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
-import com.genersoft.iot.vmp.service.IMediaServerService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.ApplicationListener;
-import org.springframework.stereotype.Component;
-
-import com.genersoft.iot.vmp.common.VideoManagerConstants;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
-import com.genersoft.iot.vmp.utils.redis.RedisUtil;
-
-import java.util.List;
-
-/**
- * @description: 离线事件监听器,监听到离线后,修改设备离在线状态。 设备离线有两个来源:
- *               1、设备主动注销,发送注销指令,{@link com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.RegisterRequestProcessor}
- *               2、设备未知原因离线,心跳超时,{@link com.genersoft.iot.vmp.gb28181.event.offline.OfflineEventListener}
- * @author: swwheihei
- * @date: 2020年5月6日 下午1:51:23
- */
-@Component
-public class OfflineEventListener implements ApplicationListener<OfflineEvent> {
-
-	private final static Logger logger = LoggerFactory.getLogger(OfflineEventListener.class);
-	
-	@Autowired
-	private IVideoManagerStorage storager;
-
-	@Autowired
-	private VideoStreamSessionManager streamSession;
-	
-	@Autowired
-    private RedisUtil redis;
-
-	@Autowired
-    private UserSetting userSetting;
-
-	@Autowired
-    private EventPublisher eventPublisher;
-
-
-	@Autowired
-    private IMediaServerService mediaServerService;
-
-
-	@Autowired
-    private ZLMRTPServerFactory zlmrtpServerFactory;
-
-	@Override
-	public void onApplicationEvent(OfflineEvent event) {
-
-		logger.info("设备离线事件触发,deviceId:" + event.getDeviceId() + ",from:" + event.getFrom());
-
-		String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + userSetting.getServerId() + "_" + event.getDeviceId();
-
-		switch (event.getFrom()) {
-			// 心跳超时触发的离线事件,说明redis中已删除,无需处理
-			case VideoManagerConstants.EVENT_OUTLINE_TIMEOUT:
-				break;
-			// 设备主动注销触发的离线事件,需要删除redis中的超时监听
-			case VideoManagerConstants.EVENT_OUTLINE_UNREGISTER:
-				redis.del(key);
-				break;
-			default:
-				boolean exist = redis.hasKey(key);
-				if (exist) {
-					redis.del(key);
-				}
-		}
-
-		List<DeviceChannel> deviceChannelList = storager.queryOnlineChannelsByDeviceId(event.getDeviceId());
-		eventPublisher.catalogEventPublish(null, deviceChannelList, CatalogEvent.OFF);
-		// 处理离线监听
-		storager.outline(event.getDeviceId());
-
-		// TODO 离线取消订阅
-
-		// 离线释放所有ssrc
-		List<SsrcTransaction> ssrcTransactions = streamSession.getSsrcTransactionForAll(event.getDeviceId(), null, null, null);
-		if (ssrcTransactions != null && ssrcTransactions.size() > 0) {
-			for (SsrcTransaction ssrcTransaction : ssrcTransactions) {
-				mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
-				mediaServerService.closeRTPServer(event.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
-				streamSession.remove(event.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
-			}
-		}
-
-	}
-}

+ 0 - 51
src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEvent.java

@@ -1,51 +0,0 @@
-package com.genersoft.iot.vmp.gb28181.event.online;
-
-import com.genersoft.iot.vmp.gb28181.bean.Device;
-import org.springframework.context.ApplicationEvent;
-
-/**    
- * @description: 在线事件类   
- * @author: swwheihei
- * @date:   2020年5月6日 上午11:32:56     
- */
-public class OnlineEvent extends ApplicationEvent {
-
-	/**
-	 *
-	 */
-	private static final long serialVersionUID = 1L;
-
-	public OnlineEvent(Object source) {
-		super(source);
-	}
-
-	private Device device;
-	
-	private String from;
-
-	private int  expires;
-
-	public Device getDevice() {
-		return device;
-	}
-
-	public void setDevice(Device device) {
-		this.device = device;
-	}
-
-	public String getFrom() {
-		return from;
-	}
-
-	public void setFrom(String from) {
-		this.from = from;
-	}
-
-	public int getExpires() {
-		return expires;
-	}
-
-	public void setExpires(int expires) {
-		this.expires = expires;
-	}
-}

+ 0 - 110
src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEventListener.java

@@ -1,110 +0,0 @@
-package com.genersoft.iot.vmp.gb28181.event.online;
-
-import com.genersoft.iot.vmp.conf.SipConfig;
-import com.genersoft.iot.vmp.conf.UserSetting;
-import com.genersoft.iot.vmp.gb28181.bean.Device;
-import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
-import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
-import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
-import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
-import com.genersoft.iot.vmp.service.IDeviceService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.ApplicationListener;
-import org.springframework.stereotype.Component;
-
-import com.genersoft.iot.vmp.common.VideoManagerConstants;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
-import com.genersoft.iot.vmp.utils.redis.RedisUtil;
-
-import java.text.SimpleDateFormat;
-import java.util.List;
-
-/**
- * @description: 在线事件监听器,监听到离线后,修改设备离在线状态。 设备在线有两个来源:
- *               1、设备主动注销,发送注销指令
- *               2、设备未知原因离线,心跳超时
- * @author: swwheihei
- * @date: 2020年5月6日 下午1:51:23
- */
-@Component
-public class OnlineEventListener implements ApplicationListener<OnlineEvent> {
-	
-	private final static Logger logger = LoggerFactory.getLogger(OnlineEventListener.class);
-
-	@Autowired
-	private IVideoManagerStorage storager;
-
-	@Autowired
-	private IDeviceService deviceService;
-	
-	@Autowired
-    private RedisUtil redis;
-
-	@Autowired
-    private SipConfig sipConfig;
-
-	@Autowired
-    private UserSetting userSetting;
-
-	@Autowired
-    private EventPublisher eventPublisher;
-
-	@Autowired
-	private SIPCommander cmder;
-
-
-	private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-
-	@Override
-	public void onApplicationEvent(OnlineEvent event) {
-
-		logger.info("设备上线事件触发,deviceId:" + event.getDevice().getDeviceId() + ",from:" + event.getFrom());
-		Device device = event.getDevice();
-		if (device == null) {
-			return;
-		}
-		String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + userSetting.getServerId() + "_" + event.getDevice().getDeviceId();
-		Device deviceInStore = storager.queryVideoDevice(device.getDeviceId());
-		device.setOnline(1);
-		switch (event.getFrom()) {
-		// 注册时触发的在线事件,先在redis中增加超时超时监听
-		case VideoManagerConstants.EVENT_ONLINE_REGISTER:
-			// 超时时间
-			redis.set(key, event.getDevice().getDeviceId(), sipConfig.getKeepaliveTimeOut());
-			device.setRegisterTime(format.format(System.currentTimeMillis()));
-			if (deviceInStore == null) { //第一次上线
-				logger.info("[{}] 首次注册,查询设备信息以及通道信息", device.getDeviceId());
-				cmder.deviceInfoQuery(device);
-				deviceService.sync(device);
-			}
-			break;
-		// 设备主动发送心跳触发的在线事件
-		case VideoManagerConstants.EVENT_ONLINE_KEEPLIVE:
-			boolean exist = redis.hasKey(key);
-			// 先判断是否还存在,当设备先心跳超时后又发送心跳时,redis没有监听,需要增加
-			if (!exist) {
-				redis.set(key, event.getDevice().getDeviceId(), sipConfig.getKeepaliveTimeOut());
-			} else {
-				redis.expire(key, sipConfig.getKeepaliveTimeOut());
-			}
-			device.setKeepaliveTime(format.format(System.currentTimeMillis()));
-			break;
-		// 设备主动发送消息触发的在线事件
-		case VideoManagerConstants.EVENT_ONLINE_MESSAGE:
-
-			break;
-		}
-		// 处理上线监听
-		storager.updateDevice(device);
-		// 上线添加订阅
-		if (device.getSubscribeCycleForCatalog() > 0) {
-			// 查询在线设备那些开启了订阅,为设备开启定时的目录订阅
-			deviceService.addCatalogSubscribe(device);
-		}
-		if (device.getSubscribeCycleForMobilePosition() > 0) {
-			deviceService.addMobilePositionSubscribe(device);
-		}
-	}
-}

+ 12 - 19
src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java

@@ -4,25 +4,21 @@ import com.genersoft.iot.vmp.gb28181.bean.CatalogData;
 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 @@ public class CatalogDataCatch {
         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 @@ public class CatalogDataCatch {
             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 @@ public class CatalogDataCatch {
             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 @@ public class CatalogDataCatch {
     @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 @@ public class CatalogDataCatch {
                 }
                 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);
             }
         }

+ 91 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/session/RecordDataCatch.java

@@ -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);
+    }
+}

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

@@ -76,8 +76,8 @@ public class VideoStreamSessionManager {
 	}
 
 	
-	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;
 		}

+ 10 - 18
src/main/java/com/genersoft/iot/vmp/conf/runner/SipDeviceRunner.java

@@ -1,4 +1,4 @@
-package com.genersoft.iot.vmp.conf.runner;
+package com.genersoft.iot.vmp.gb28181.task;
 
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
@@ -10,11 +10,13 @@ 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)
@@ -34,26 +36,16 @@ public class SipDeviceRunner implements CommandLineRunner {
 
     @Override
     public void run(String... args) throws Exception {
-        // 读取redis没有心跳信息的则设置为离线,等收到下次心跳设置为在线
-        // 设置所有设备离线
-        storager.outlineForAll();
-        List<String> onlineForAll = redisCatchStorage.getOnlineForAll();
-        for (String deviceId : onlineForAll) {
-            storager.online(deviceId);
-            Device device = redisCatchStorage.getDevice(deviceId);
-            if (device != null ) {
-                if (device.getSubscribeCycleForCatalog() > 0) {
-                    // 查询在线设备那些开启了订阅,为设备开启定时的目录订阅
-                    deviceService.addCatalogSubscribe(device);
-                }
-                if (device.getSubscribeCycleForMobilePosition() > 0) {
-                    deviceService.addMobilePositionSubscribe(device);
-                }
+        List<Device> deviceList = deviceService.getAllOnlineDevice();
+
+        for (Device device : deviceList) {
+            if (deviceService.expire(device)){
+                deviceService.offline(device.getDeviceId());
+            }else {
+                deviceService.online(device);
             }
         }
         // 重置cseq计数
         redisCatchStorage.resetAllCSEQ();
-
-
     }
 }

+ 37 - 7
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.scheduling.annotation.Async;
 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 @@ public class SIPProcessorObserver implements ISIPProcessorObserver {
     @Autowired
     private SipSubscribe sipSubscribe;
 
+    @Autowired
+    private EventPublisher eventPublisher;
+
+
 
-//    @Autowired
-//    @Qualifier(value = "taskExecutor")
-//    private ThreadPoolTaskExecutor poolTaskExecutor;
 
     /**
      * 添加 request订阅
@@ -141,9 +148,32 @@ public class SIPProcessorObserver implements ISIPProcessorObserver {
      */
     @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

+ 0 - 76
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/CheckForAllRecordsThread.java

@@ -1,76 +0,0 @@
-package com.genersoft.iot.vmp.gb28181.transmit.callback;
-
-import com.genersoft.iot.vmp.gb28181.bean.RecordInfo;
-import com.genersoft.iot.vmp.gb28181.bean.RecordItem;
-import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.RecordInfoResponseMessageHandler;
-import com.genersoft.iot.vmp.utils.redis.RedisUtil;
-import org.slf4j.Logger;
-
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-@SuppressWarnings("unchecked")
-public class CheckForAllRecordsThread extends Thread {
-
-    private String key;
-
-    private RecordInfo recordInfo;
-
-    private RedisUtil redis;
-
-    private Logger logger;
-
-    private DeferredResultHolder deferredResultHolder;
-
-    public CheckForAllRecordsThread(String key, RecordInfo recordInfo) {
-        this.key = key;
-        this.recordInfo = recordInfo;
-    }
-
-    @Override
-    public void run() {
-
-        String cacheKey = this.key;
-
-        for (long stop = System.nanoTime() + TimeUnit.SECONDS.toNanos(10); stop > System.nanoTime();) {
-            List<Object> cacheKeys = redis.scan(cacheKey + "_*");
-            List<RecordItem> totalRecordList = new ArrayList<RecordItem>();
-            for (int i = 0; i < cacheKeys.size(); i++) {
-                totalRecordList.addAll((List<RecordItem>) redis.get(cacheKeys.get(i).toString()));
-            }
-            if (totalRecordList.size() < this.recordInfo.getSumNum()) {
-                logger.info("已获取" + totalRecordList.size() + "项录像数据,共" + this.recordInfo.getSumNum() + "项");
-            } else {
-                logger.info("录像数据已全部获取,共 {} 项", this.recordInfo.getSumNum());
-                this.recordInfo.setRecordList(totalRecordList);
-                for (int i = 0; i < cacheKeys.size(); i++) {
-                    redis.del(cacheKeys.get(i).toString());
-                }
-                break;
-            }
-        }
-        // 自然顺序排序, 元素进行升序排列
-        this.recordInfo.getRecordList().sort(Comparator.naturalOrder());
-        RequestMessage msg = new RequestMessage();
-        msg.setKey(DeferredResultHolder.CALLBACK_CMD_RECORDINFO + recordInfo.getDeviceId() + recordInfo.getSn());
-        msg.setData(recordInfo);
-        deferredResultHolder.invokeAllResult(msg);
-        logger.info("处理完成,返回结果");
-        RecordInfoResponseMessageHandler.threadNameList.remove(cacheKey);
-    }
-    
-	public void setRedis(RedisUtil redis) {
-		this.redis = redis;
-	}
-
-	public void setDeferredResultHolder(DeferredResultHolder deferredResultHolder) {
-		this.deferredResultHolder = deferredResultHolder;
-    }
-
-    public void setLogger(Logger logger) {
-        this.logger = logger;
-    }
-
-}

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

@@ -10,7 +10,7 @@ import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 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.Logger;
 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 @@ public class SIPCommander implements ISIPCommander {
 			//
 			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 @@ public class SIPCommander implements ISIPCommander {
 				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 @@ public class SIPCommander implements ISIPCommander {
 				}
 			}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 @@ public class SIPCommander implements ISIPCommander {
 
 			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 @@ public class SIPCommander implements ISIPCommander {
 				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 @@ public class SIPCommander implements ISIPCommander {
 				}
 			}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 @@ public class SIPCommander implements ISIPCommander {
 
 			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 @@ public class SIPCommander implements ISIPCommander {
 				}
 			}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 @@ public class SIPCommander implements ISIPCommander {
 					(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,10 +695,10 @@ public class SIPCommander implements ISIPCommander {
 	@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) {
+			if (transaction == null ) {
 				logger.warn("[ {} -> {}]停止视频流的时候发现事务已丢失", deviceId, channelId);
 				SipSubscribe.EventResult<Object> eventResult = new SipSubscribe.EventResult<>();
 				if (okEvent != null) {
@@ -1664,6 +1675,7 @@ public class SIPCommander implements ISIPCommander {
 			sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> {
 				errorEvent.response(eventResult);
 				sipSubscribe.removeErrorSubscribe(eventResult.callId);
+				sipSubscribe.removeOkSubscribe(eventResult.callId);
 			}));
 		}
 		// 添加订阅
@@ -1671,6 +1683,7 @@ public class SIPCommander implements ISIPCommander {
 			sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult ->{
 				okEvent.response(eventResult);
 				sipSubscribe.removeOkSubscribe(eventResult.callId);
+				sipSubscribe.removeErrorSubscribe(eventResult.callId);
 			});
 		}
 

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

@@ -4,7 +4,7 @@ import com.genersoft.iot.vmp.gb28181.bean.*;
 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;

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

@@ -4,8 +4,12 @@ import com.alibaba.fastjson.JSON;
 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.ZLMRTPServerFactory;
 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;
@@ -50,6 +56,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
 	@Autowired
     private IRedisCatchStorage redisCatchStorage;
 
+	@Autowired
+	private IVideoManagerStorage storager;
+
 	@Autowired
 	private ZLMRTPServerFactory zlmrtpServerFactory;
 
@@ -62,6 +71,12 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
 	@Autowired
 	private DynamicTask dynamicTask;
 
+	@Autowired
+	private ISIPCommander cmder;
+
+	@Autowired
+	private ISIPCommanderForPlatform commanderForPlatform;
+
 
 	/**   
 	 * 处理  ACK请求
@@ -78,6 +93,7 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
 		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 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
 			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

+ 10 - 11
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.MessageForPushChannel;
 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.header.CallIdHeader;
 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 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 
 				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 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 					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 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 							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);

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

@@ -252,14 +252,12 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 			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 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 					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);
@@ -324,10 +329,6 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 					eventPublisher.catalogEventPublish(null, channel, eventElement.getText().toUpperCase());
 
 				}
-
-				if (!redisCatchStorage.deviceIsOnline(deviceId)) {
-					publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_MESSAGE);
-				}
 			}
 		} catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
 			e.printStackTrace();

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

@@ -9,8 +9,10 @@ import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 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 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
     @Autowired
     private SIPProcessorObserver sipProcessorObserver;
 
+    @Autowired
+    private IDeviceService deviceService;
+
     @Override
     public void afterPropertiesSet() throws Exception {
         // 添加消息处理的订阅
@@ -76,13 +81,13 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
         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 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
 
             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 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
                 // 注册失败
                 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 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
                 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 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
             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 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
     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);

+ 31 - 29
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 @@ import java.text.ParseException;
 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,29 +44,36 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
 
     @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);
-                // 判断RPort是否改变,改变则说明路由nat信息变化,修改设备信息
-                // 获取到通信地址等信息
-                ViaHeader viaHeader = (ViaHeader) evt.getRequest().getHeader(ViaHeader.NAME);
-                String received = viaHeader.getReceived();
-                int rPort = viaHeader.getRPort();
-                // 解析本地地址替代
-                if (StringUtils.isEmpty(received) || rPort == -1) {
-                    received = viaHeader.getHost();
-                    rPort = viaHeader.getPort();
-                }
-                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);
+            }else {
+                // 对于已经离线的设备判断他的注册是否已经过期
+                if (!deviceService.expire(device)){
+                    device.setKeepaliveTime(DateUtil.getNow());
+                    // 判断RPort是否改变,改变则说明路由nat信息变化,修改设备信息
+                    // 获取到通信地址等信息
+                    ViaHeader viaHeader = (ViaHeader) evt.getRequest().getHeader(ViaHeader.NAME);
+                    String received = viaHeader.getReceived();
+                    int rPort = viaHeader.getRPort();
+                    // 解析本地地址替代
+                    if (StringUtils.isEmpty(received) || rPort == -1) {
+                        received = viaHeader.getHost();
+                        rPort = viaHeader.getPort();
+                    }
+                    if (device.getPort() != rPort) {
+                        device.setPort(rPort);
+                        device.setHostAddress(received.concat(":").concat(String.valueOf(rPort)));
+                    }
+                    device.setKeepaliveTime(DateUtil.getNow());
+                    deviceService.online(device);
+                    // 回复200 OK
+                    responseAck(evt, Response.OK);
                 }
             }
         } catch (SipException e) {

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

@@ -60,10 +60,9 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i
         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);

+ 1 - 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.cmd.impl.SIPCommanderFroPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.QueryMessageHandler;
-import com.genersoft.iot.vmp.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;

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

@@ -28,7 +28,6 @@ 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;
@@ -39,8 +38,6 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
     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;
 

+ 14 - 4
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.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.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 java.text.ParseException;
 
 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 @@ public class DeviceInfoResponseMessageHandler extends SIPRequestProcessorParent
     @Autowired
     private EventPublisher publisher;
 
+    @Autowired
+    private IDeviceService deviceService;
+
     @Override
     public void afterPropertiesSet() throws Exception {
         responseMessageHandler.addHandler(cmdType, this);
@@ -61,6 +68,11 @@ public class DeviceInfoResponseMessageHandler extends SIPRequestProcessorParent
     @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 @@ public class DeviceInfoResponseMessageHandler extends SIPRequestProcessorParent
             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 @@ public class DeviceInfoResponseMessageHandler extends SIPRequestProcessorParent
             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) {

+ 13 - 6
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.SIPRequestProcessorP
 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.RequestEvent;
 import javax.sip.SipException;
 import javax.sip.message.Response;
 import java.text.ParseException;
+import java.util.Objects;
 
 @Component
 public class DeviceStatusResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
@@ -34,12 +36,11 @@ public class DeviceStatusResponseMessageHandler extends SIPRequestProcessorParen
     @Autowired
     private ResponseMessageHandler responseMessageHandler;
 
-
     @Autowired
     private DeferredResultHolder deferredResultHolder;
 
     @Autowired
-    private EventPublisher publisher;
+    private IDeviceService deviceService;
 
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
@@ -53,6 +54,9 @@ public class DeviceStatusResponseMessageHandler extends SIPRequestProcessorParen
     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 @@ public class DeviceStatusResponseMessageHandler extends SIPRequestProcessorParen
             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

+ 0 - 14
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.slf4j.LoggerFactory;
 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;
 
@@ -44,8 +32,6 @@ public class PresetQueryResponseMessageHandler extends SIPRequestProcessorParent
     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;
 

+ 49 - 52
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.ParentPlatform;
 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.slf4j.LoggerFactory;
 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,11 +46,13 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
     private ResponseMessageHandler responseMessageHandler;
 
     @Autowired
-    private RedisUtil redis;
+    private RecordDataCatch recordDataCatch;
 
     @Autowired
     private DeferredResultHolder deferredResultHolder;
 
+
+
     @Autowired
     private EventPublisher eventPublisher;
 
@@ -66,32 +69,22 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
             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 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
                             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 class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
     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);
+    }
 }

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

@@ -1,57 +0,0 @@
-package com.genersoft.iot.vmp.gb28181.utils;
-
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-
-/**    
- * @description:时间工具类,主要处理ISO 8601格式转换
- * @author: swwheihei
- * @date:   2020年5月8日 下午3:24:42     
- */
-public class DateUtil {
-
-	//private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
-	private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss";
-    private static final String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss";
-    
-	public static String yyyy_MM_dd_HH_mm_ssToISO8601(String formatTime) {
-
-        SimpleDateFormat oldsdf = new SimpleDateFormat(yyyy_MM_dd_HH_mm_ss, Locale.getDefault());
-        SimpleDateFormat newsdf = new SimpleDateFormat(yyyy_MM_dd_T_HH_mm_ss_SSSXXX, Locale.getDefault());
-        try {
-            return newsdf.format(oldsdf.parse(formatTime));
-        } catch (ParseException e) {
-            e.printStackTrace();
-        }
-        return "";
-    }
-	
-	public static String ISO8601Toyyyy_MM_dd_HH_mm_ss(String formatTime) {
-
-        SimpleDateFormat oldsdf = new SimpleDateFormat(yyyy_MM_dd_T_HH_mm_ss_SSSXXX, Locale.getDefault());
-        SimpleDateFormat newsdf = new SimpleDateFormat(yyyy_MM_dd_HH_mm_ss, Locale.getDefault());
-        try {
-            return newsdf.format(oldsdf.parse(formatTime));
-        } catch (ParseException e) {
-            e.printStackTrace();
-        }
-        return "";
-    }
-	
-	public static long yyyy_MM_dd_HH_mm_ssToTimestamp(String formatTime) {
-		SimpleDateFormat format=new SimpleDateFormat(yyyy_MM_dd_HH_mm_ss);
-		//设置要读取的时间字符串格式
-		Date date;
-		try {
-			date = format.parse(formatTime);
-			Long timestamp=date.getTime()/1000;
-			//转换为Date类
-			return timestamp;
-		} catch (ParseException e) {
-			e.printStackTrace();
-		}
-		return 0;
-	}
-}

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

@@ -246,17 +246,7 @@ public class ZLMRTPServerFactory {
      * 调用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);
     }
 
     /**

+ 27 - 59
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java

@@ -55,9 +55,6 @@ public class ZLMRunner implements CommandLineRunner {
     @Autowired
     private DynamicTask dynamicTask;
 
-    @Qualifier("taskExecutor")
-    @Autowired
-    private ThreadPoolTaskExecutor taskExecutor;
 
     @Override
     public void run(String... strings) throws Exception {
@@ -92,7 +89,7 @@ public class ZLMRunner implements CommandLineRunner {
                 });
 
         // 获取zlm信息
-        logger.info("[zlm接入]等待默认zlm中...");
+        logger.info("[zlm] 等待默认zlm中...");
 
         // 获取所有的zlm, 并开启主动连接
         List<MediaServerItem> all = mediaServerService.getAllFromDatabase();
@@ -105,9 +102,7 @@ public class ZLMRunner implements CommandLineRunner {
                 startGetMedia = new HashMap<>();
             }
             startGetMedia.put(mediaServerItem.getId(), true);
-            taskExecutor.execute(()->{
-                connectZlmServer(mediaServerItem);
-            });
+            connectZlmServer(mediaServerItem);
         }
         String taskKey = "zlm-connect-timeout";
         dynamicTask.startDelay(taskKey, ()->{
@@ -119,21 +114,37 @@ public class ZLMRunner implements CommandLineRunner {
                 startGetMedia = null;
             }
         //  TODO 清理数据库中与redis不匹配的zlm
-        }, 6 * 1000 );
+        }, 60 * 1000 );
     }
 
     @Async
     public void connectZlmServer(MediaServerItem mediaServerItem){
-        ZLMServerConfig zlmServerConfig = getMediaServerConfig(mediaServerItem, 1);
-        if (zlmServerConfig != null) {
-            zlmServerConfig.setIp(mediaServerItem.getIp());
-            zlmServerConfig.setHttpPort(mediaServerItem.getHttpPort());
+        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(zlmServerConfig);
+            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 @@ public class ZLMRunner implements CommandLineRunner {
                 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());
-//            }
-//        }
-//    }
 }

+ 2 - 7
src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMStatusEventListener.java

@@ -7,12 +7,10 @@ import com.genersoft.iot.vmp.service.IStreamPushService;
 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 @@ public class ZLMStatusEventListener {
 	@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 @@ public class ZLMStatusEventListener {
 	@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());

+ 62 - 7
src/main/java/com/genersoft/iot/vmp/service/IDeviceService.java

@@ -3,56 +3,111 @@ package com.genersoft.iot.vmp.service;
 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);
 }

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

@@ -44,7 +44,7 @@ public interface IMediaServerService {
 
     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);
 

+ 178 - 5
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java

@@ -2,21 +2,27 @@ package com.genersoft.iot.vmp.service.impl;
 
 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;
 
 /**
  * 设备业务(目录订阅)
@@ -26,6 +32,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 @@ public class DeviceServiceImpl implements IDeviceService {
     @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 @@ public class DeviceServiceImpl implements IDeviceService {
         // 提前开始刷新订阅
         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 @@ public class DeviceServiceImpl implements IDeviceService {
         // 设置最小值为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 @@ public class DeviceServiceImpl implements IDeviceService {
             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);
+        }
+    }
 }

+ 13 - 14
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java

@@ -18,6 +18,7 @@ import com.genersoft.iot.vmp.service.IStreamProxyService;
 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 @@ public class MediaServerServiceImpl implements IMediaServerService {
     @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 @@ public class MediaServerServiceImpl implements IMediaServerService {
     }
 
     @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 @@ public class MediaServerServiceImpl implements IMediaServerService {
         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 @@ public class MediaServerServiceImpl implements IMediaServerService {
     @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 @@ public class MediaServerServiceImpl implements IMediaServerService {
      */
     @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 @@ public class MediaServerServiceImpl implements IMediaServerService {
             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 @@ public class MediaServerServiceImpl implements IMediaServerService {
      */
     @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 @@ public class MediaServerServiceImpl implements IMediaServerService {
 
         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());
         }
 

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

@@ -13,7 +13,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.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 @@ public class PlayServiceImpl implements IPlayService {
             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 @@ public class PlayServiceImpl implements IPlayService {
             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 @@ public class PlayServiceImpl implements IPlayService {
             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 @@ public class PlayServiceImpl implements IPlayService {
             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);
     }

+ 2 - 9
src/main/java/com/genersoft/iot/vmp/service/impl/RedisAlarmMsgListener.java

@@ -4,20 +4,15 @@ import com.alibaba.fastjson.JSON;
 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 {
@@ -33,8 +28,6 @@ public class RedisAlarmMsgListener implements MessageListener {
     @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) {
         logger.info("收到来自REDIS的ALARM通知: {}", new String(message.getBody()));
@@ -52,7 +45,7 @@ public class RedisAlarmMsgListener implements MessageListener {
         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);

+ 0 - 17
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java

@@ -111,23 +111,6 @@ public interface IRedisCatchStorage {
      */
     void clearCatchByDeviceId(String deviceId);
 
-    /**
-     * 获取mediaServer节点
-     * @param mediaServerId
-     * @return
-     */
-//    MediaServerItem getMediaInfo(String mediaServerId);
-
-    /**
-     * 设置所有设备离线
-     */
-    void outlineForAll();
-
-    /**
-     * 获取所有在线的
-     */
-    List<String> getOnlineForAll();
-
     /**
      * 在redis添加wvp的信息
      */

+ 0 - 16
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java

@@ -25,22 +25,6 @@ public interface IVideoManagerStorage {
 	 * @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);
 
 	/**
 	 * 添加设备通道

+ 4 - 0
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java

@@ -257,4 +257,8 @@ public interface DeviceChannelMapper {
 
     @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();
+
 }

+ 5 - 0
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java

@@ -99,4 +99,9 @@ public interface DeviceMapper {
 
     @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);
 }

+ 5 - 28
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java

@@ -14,13 +14,13 @@ import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
 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")
@@ -38,8 +38,6 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
     @Autowired
     private UserSetting userSetting;
 
-    private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-
     @Override
     public Long getCSEQ(String method) {
         String key = VideoManagerConstants.SIP_CSEQ_PREFIX  + userSetting.getServerId() + "_" +  method;
@@ -469,26 +467,6 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
         }
     }
 
-    @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();
@@ -638,7 +616,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
     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 class RedisCatchStorageImpl implements IRedisCatchStorage {
     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 class RedisCatchStorageImpl implements IRedisCatchStorage {
     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 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
 
     @Override
     public boolean deviceIsOnline(String deviceId) {
-        String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + userSetting.getServerId() + "_" + deviceId;
-        return redis.hasKey(key);
+        return getDevice(deviceId).getOnline() == 1;
     }
 }

+ 17 - 48
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java

@@ -13,6 +13,7 @@ import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 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.TransactionStatus;
 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 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 	@Autowired
     private ParentPlatformMapper parentPlatformMapper;
 
-	private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-
-
 	/**
 	 * 根据设备ID判断设备是否存在
 	 *
@@ -105,45 +103,6 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 		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 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 		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 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 					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 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 					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 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 		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 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 		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 {

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

@@ -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;
+        }
+    }
+}

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

@@ -169,7 +169,7 @@ public class MobilePositionController {
         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 += ",成功";

+ 5 - 9
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/alarm/AlarmController.java

@@ -9,6 +9,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
 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.util.StringUtils;
 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 = "报警信息管理")
@@ -46,9 +45,6 @@ public class AlarmController {
     @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 @@ public class AlarmController {
 
         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 @@ public class AlarmController {
         }
         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 @@ public class AlarmController {
         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);

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

@@ -288,7 +288,8 @@ public class DeviceQuery {
 	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 class DeviceQuery {
 	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);

+ 30 - 3
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java

@@ -5,6 +5,8 @@ import com.genersoft.iot.vmp.common.StreamInfo;
 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.callback.DeferredResultHolder;
 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 @@ public class GBRecordController {
 			@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 @@ public class GBRecordController {
 		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 @@ public class GBRecordController {
 		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;

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

@@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.vmanager.log;
 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.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 
 import java.text.ParseException;
-import java.text.SimpleDateFormat;
 
 @Api(tags = "日志管理")
 @CrossOrigin
@@ -34,8 +34,6 @@ public class LogController {
     @Autowired
     private UserSetting userSetting;
 
-    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-
     /**
      *  分页查询日志
      *
@@ -80,10 +78,10 @@ public class LogController {
 
         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);

+ 3 - 10
src/main/java/com/genersoft/iot/vmp/vmanager/user/RoleController.java

@@ -2,9 +2,8 @@ package com.genersoft.iot.vmp.vmanager.user;
 
 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 io.swagger.annotations.ApiOperation;
 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 = "角色管理")
@@ -30,8 +25,6 @@ public class RoleController {
     @Autowired
     private IRoleService roleService;
 
-    private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-
     @ApiOperation("添加角色")
     @ApiImplicitParams({
             @ApiImplicitParam(name = "name", required = true, value = "角色名", dataTypeClass = String.class),
@@ -53,8 +46,8 @@ public class RoleController {
         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);
 

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

@@ -6,6 +6,7 @@ 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;
@@ -20,7 +21,6 @@ import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 
 import javax.security.sasl.AuthenticationException;
-import java.text.SimpleDateFormat;
 import java.util.List;
 
 @Api(tags = "用户管理")
@@ -38,8 +38,6 @@ public class UserController {
     @Autowired
     private IRoleService roleService;
 
-    private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-
     @ApiOperation("登录")
     @ApiImplicitParams({
             @ApiImplicitParam(name = "username", required = true, value = "用户名", dataTypeClass = String.class),
@@ -135,8 +133,8 @@ public class UserController {
             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);

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

@@ -28,7 +28,7 @@ spring:
         poolMaxIdle: 500
         # [可选] 最大的等待时间(秒)
         poolMaxWait: 5
-    # [可选] jdbc数据库配置, 项目使用sqlite作为数据库,一般不需要配
+    # [必选] jdbc数据库配
     datasource:
         type: com.alibaba.druid.pool.DruidDataSource
         driver-class-name: com.mysql.cj.jdbc.Driver

+ 0 - 1
src/main/resources/application-dev.yml

@@ -16,7 +16,6 @@ spring:
         password: face2020
         # [可选] 超时时间
         timeout: 10000
-        # [可选] jdbc数据库配置, 项目使用sqlite作为数据库,一般不需要配置
         # mysql数据源
     datasource:
         type: com.alibaba.druid.pool.DruidDataSource

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

@@ -16,7 +16,7 @@ spring:
         password: ${REDIS_PWD:root}
         # [可选] 超时时间
         timeout: 10000
-    # [可选] jdbc数据库配置, 项目使用sqlite作为数据库,一般不需要配
+    # [必选] jdbc数据库配
     datasource:
         # 使用mysql 打开23-28行注释, 删除29-36行
          name: wvp

+ 4 - 6
src/test/java/com/genersoft/iot/vmp/service/impl/DeviceAlarmServiceImplTest.java

@@ -2,12 +2,12 @@ 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.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;
 
 
@@ -18,8 +18,6 @@ class DeviceAlarmServiceImplTest {
     @Resource
     private IDeviceAlarmService deviceAlarmService;
 
-    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-
     @org.junit.jupiter.api.Test
     void getAllAlarm() {
 //        deviceAlarmService.getAllAlarm(0, 10000, "11111111111111111111",null,null,null, null, null);
@@ -67,7 +65,7 @@ class DeviceAlarmServiceImplTest {
              */
             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 @@ class DeviceAlarmServiceImplTest {
     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;

+ 3 - 4
src/test/java/com/genersoft/iot/vmp/service/impl/RoleServiceImplTest.java

@@ -4,12 +4,12 @@ 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 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 @@ class RoleServiceImplTest {
     @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 @@ class RoleServiceImplTest {
             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);
         }
     }

+ 4 - 8
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 @@ class UserServiceImplTest {
     @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 @@ class UserServiceImplTest {
             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 @@ class UserServiceImplTest {
         Role role = new Role();
         role.setId(2);
         user.setRole(role);
-        user.setUpdateTime(format.format(System.currentTimeMillis()));
+        user.setUpdateTime(DateUtil.getNow());
         userService.updateUsers(user);
     }
 

+ 4 - 2
web_src/src/components/channelList.vue

@@ -237,14 +237,16 @@ export default {
             that.initData();
           }, 1000)
 
-        } else {
+        }else{
           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})

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

@@ -576,7 +576,7 @@ export default {
       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({

+ 130 - 14
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 @@ export default {
             seekTime: 0,
             recordStartTime: 0,
             showTimeText: "00:00:00",
+            streamInfo: null,
         };
     },
     methods: {
@@ -306,6 +398,7 @@ export default {
             console.log(val)
         },
         play: function (streamInfo, hasAudio) {
+            this.streamInfo = streamInfo;
             this.hasAudio = hasAudio;
             this.isLoging = false;
             // this.videoUrl = streamInfo.rtc;
@@ -453,9 +546,19 @@ export default {
                 method: 'get',
                 url: '/api/gb_record/query/' + this.deviceId + '/' + this.channelId + '?startTime=' + startTime + '&endTime=' + endTime
             }).then(function (res) {
-                // 处理时间信息
-                that.videoHistory.searchHistoryResult = res.data.recordList;
-                that.recordsLoading = false;
+                console.log(res)
+                if(res.data.code === 0) {
+                  // 处理时间信息
+                  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 @@ export default {
             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 @@ export default {
           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)
+          });
+        },
+
 
     }
 };

+ 1 - 0
web_src/src/components/dialog/recordDownload.vue

@@ -172,6 +172,7 @@ export default {
               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') {