Browse Source

优化代码结构,提供NVR录像检索接口

songww 5 years ago
parent
commit
bf7ab3fe2b
18 changed files with 870 additions and 236 deletions
  1. 5 27
      src/main/java/com/genersoft/iot/vmp/conf/RedisConfig.java
  2. 0 1
      src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java
  3. 100 73
      src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
  4. 26 0
      src/main/java/com/genersoft/iot/vmp/gb28181/auth/RegisterLogicHandler.java
  5. 51 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java
  6. 99 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java
  7. 42 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java
  8. 51 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/RequestMessage.java
  9. 29 27
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  10. 70 56
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  11. 112 42
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java
  12. 3 3
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java
  13. 40 0
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/DateUtil.java
  14. 152 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
  15. 29 5
      src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java
  16. 7 1
      src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java
  17. 7 1
      src/main/java/com/genersoft/iot/vmp/vmanager/ptz/PtzController.java
  18. 47 0
      src/main/java/com/genersoft/iot/vmp/vmanager/record/RecordController.java

+ 5 - 27
src/main/java/com/genersoft/iot/vmp/conf/RedisConfig.java

@@ -12,20 +12,19 @@ import com.alibaba.fastjson.parser.ParserConfig;
 import com.genersoft.iot.vmp.utils.redis.FastJsonRedisSerializer;
 
 /**
- * @Description:Redis中间件配置类
+ * @Description:Redis中间件配置类,使用spring-data-redis集成,自动从application.yml中加载redis配置
  * @author: songww
  * @date: 2019年5月30日 上午10:58:25
  * 
  */
 @Configuration
-// @EnableCaching
 public class RedisConfig extends CachingConfigurerSupport {
 
 	@Bean("redisTemplate")
 	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
 		RedisTemplate<Object, Object> template = new RedisTemplate<>();
 		template.setConnectionFactory(redisConnectionFactory);
-		ParserConfig.getGlobalInstance().setAutoTypeSupport(true); 
+		// 使用fastjson进行序列化处理,提高解析效率
 		FastJsonRedisSerializer<Object> serializer = new FastJsonRedisSerializer<Object>(Object.class);
 		// value值的序列化采用fastJsonRedisSerializer
 		template.setValueSerializer(serializer);
@@ -33,8 +32,9 @@ public class RedisConfig extends CachingConfigurerSupport {
 		// key的序列化采用StringRedisSerializer
 		template.setKeySerializer(new StringRedisSerializer());
 		template.setHashKeySerializer(new StringRedisSerializer());
-
 		template.setConnectionFactory(redisConnectionFactory);
+		// 使用fastjson时需设置此项,否则会报异常not support type
+		ParserConfig.getGlobalInstance().setAutoTypeSupport(true); 
 		return template;
 	}
 
@@ -53,27 +53,5 @@ public class RedisConfig extends CachingConfigurerSupport {
         container.setConnectionFactory(connectionFactory);
         return container;
     }
-//	@Bean
-//	RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
-//			MessageListenerAdapter listenerAdapter) {
-//
-//		RedisMessageListenerContainer container = new RedisMessageListenerContainer();
-//		container.setConnectionFactory(connectionFactory);
-//		// 订阅了一个叫通道
-//		container.addMessageListener(listenerAdapter, new PatternTopic(VideoManagerConstants.KEEPLIVEKEY_PREFIX+"*"));
-//		// 这个container 可以添加多个 messageListener
-//		return container;
-//	}
-	
-//	/**
-//     * 消息监听器适配器,绑定消息处理器,利用反射技术调用消息处理器的业务方法
-//     * @param receiver
-//     * @return
-//     */
-//    @Bean
-//    MessageListenerAdapter listenerAdapter(MessageReceiver receiver) {
-//        //这个地方 是给messageListenerAdapter 传入一个消息接受的处理器,利用反射的方法调用“receiveMessage”
-//        //也有好几个重载方法,这边默认调用处理器的方法 叫handleMessage 可以自己到源码里面看
-//        return new MessageListenerAdapter(receiver, "receiveMessage");
-//    }
+
 }

+ 0 - 1
src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java

@@ -16,7 +16,6 @@ public class SipConfig {
 	String sipPassword;
 	@Value("${media.ip}")
 	String mediaIp;
-	
 	@Value("${media.port}")
 	Integer mediaPort;
 	

+ 100 - 73
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java

@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181;
 
 import java.util.Properties;
 
+import javax.annotation.PostConstruct;
 import javax.sip.DialogTerminatedEvent;
 import javax.sip.IOExceptionEvent;
 import javax.sip.ListeningPoint;
@@ -26,7 +27,6 @@ import javax.sip.message.Response;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
 import org.springframework.stereotype.Component;
 
 import com.genersoft.iot.vmp.conf.SipConfig;
@@ -37,70 +37,78 @@ import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor;
 import gov.nist.javax.sip.SipStackImpl;
 
 @Component
-public class SipLayer implements SipListener{
-	
+public class SipLayer implements SipListener, Runnable {
+
 	private final static Logger logger = LoggerFactory.getLogger(SipLayer.class);
-	
+
 	@Autowired
 	private SipConfig config;
-	
+
 	private SipProvider tcpSipProvider;
-	
+
 	private SipProvider udpSipProvider;
-	
+
 	@Autowired
 	private SIPProcessorFactory processorFactory;
-	
+
 	private SipStack sipStack;
-	
+
 	private AddressFactory addressFactory;
 	private HeaderFactory headerFactory;
 	private MessageFactory messageFactory;
 
-	@Bean
-	private boolean initSipServer() throws Exception {
+	@PostConstruct
+	private void initSipServer() {
+		Thread thread=new Thread(this);
+        thread.setDaemon(true);
+        thread.setName("sip server thread start");
+        thread.start();
+	}
+
+	@Override
+	public void run() {
 		SipFactory sipFactory = SipFactory.getInstance();
 		sipFactory.setPathName("gov.nist");
-		headerFactory = sipFactory.createHeaderFactory();
-		addressFactory = sipFactory.createAddressFactory();
-		messageFactory = sipFactory.createMessageFactory();
-		
-		Properties properties = new Properties();
-		properties.setProperty("javax.sip.STACK_NAME", "GB28181_SIP");
-		properties.setProperty("javax.sip.IP_ADDRESS", config.getSipIp());
-		/**
-		 * sip_server_log.log 和 sip_debug_log.log public static final int TRACE_NONE =
-		 * 0; public static final int TRACE_MESSAGES = 16; public static final int
-		 * TRACE_EXCEPTION = 17; public static final int TRACE_DEBUG = 32;
-		 */
-		properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "16");
-		properties.setProperty("gov.nist.javax.sip.SERVER_LOG", "sip_server_log");
-		properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "sip_debug_log");
-		sipStack = (SipStackImpl) sipFactory.createSipStack(properties);
-		
 		try {
+			headerFactory = sipFactory.createHeaderFactory();
+
+			addressFactory = sipFactory.createAddressFactory();
+			messageFactory = sipFactory.createMessageFactory();
+
+			Properties properties = new Properties();
+			properties.setProperty("javax.sip.STACK_NAME", "GB28181_SIP");
+			properties.setProperty("javax.sip.IP_ADDRESS", config.getSipIp());
+			properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "false");
+			/**
+			 * sip_server_log.log 和 sip_debug_log.log public static final int TRACE_NONE =
+			 * 0; public static final int TRACE_MESSAGES = 16; public static final int
+			 * TRACE_EXCEPTION = 17; public static final int TRACE_DEBUG = 32;
+			 */
+			properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "0");
+			properties.setProperty("gov.nist.javax.sip.SERVER_LOG", "sip_server_log");
+			properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "sip_debug_log");
+			sipStack = (SipStackImpl) sipFactory.createSipStack(properties);
+
 			startTcpListener();
 			startUdpListener();
 		} catch (Exception e) {
-			logger.error("Sip Server 启动失败! port {"+config.getSipPort()+"}");
+			logger.error("Sip Server 启动失败! port {" + config.getSipPort() + "}");
 			e.printStackTrace();
-			throw e;
 		}
-		logger.info("Sip Server 启动成功 port {"+config.getSipPort()+"}");
-		return true;
+		logger.info("Sip Server 启动成功 port {" + config.getSipPort() + "}");
 	}
-	
+
 	private void startTcpListener() throws Exception {
 		ListeningPoint tcpListeningPoint = sipStack.createListeningPoint(config.getSipIp(), config.getSipPort(), "TCP");
 		tcpSipProvider = sipStack.createSipProvider(tcpListeningPoint);
 		tcpSipProvider.addSipListener(this);
-    }
-	
-    private void startUdpListener() throws Exception {
+	}
+
+	private void startUdpListener() throws Exception {
 		ListeningPoint udpListeningPoint = sipStack.createListeningPoint(config.getSipIp(), config.getSipPort(), "UDP");
 		udpSipProvider = sipStack.createSipProvider(udpListeningPoint);
 		udpSipProvider.addSipListener(this);
-    }
+	}
 
 	/**
 	 * SIP服务端接收消息的方法 Content 里面是GBK编码 This method is called by the SIP stack when a
@@ -118,60 +126,80 @@ public class SipLayer implements SipListener{
 		int status = response.getStatusCode();
 		if ((status >= 200) && (status < 300)) { // Success!
 			ISIPResponseProcessor processor = processorFactory.createResponseProcessor(evt);
-			processor.process(evt,this,config);
+			processor.process(evt, this, config);
 		} else {
-			logger.warn("接收到失败的response响应!status:"+status+",message:"+response.getContent().toString());
+			logger.warn("接收到失败的response响应!status:" + status + ",message:" + response.getContent().toString());
 		}
-		//trying不会回复
-		if(status == Response.TRYING){
+		// trying不会回复
+		if (status == Response.TRYING) {
 
 		}
 	}
 
-	/**   
-	 * <p>Title: processTimeout</p>   
-	 * <p>Description: </p>   
-	 * @param timeoutEvent    
-	 */  
+	/**
+	 * <p>
+	 * Title: processTimeout
+	 * </p>
+	 * <p>
+	 * Description:
+	 * </p>
+	 * 
+	 * @param timeoutEvent
+	 */
 	@Override
 	public void processTimeout(TimeoutEvent timeoutEvent) {
 		// TODO Auto-generated method stub
-		
+
 	}
 
-	/**   
-	 * <p>Title: processIOException</p>   
-	 * <p>Description: </p>   
-	 * @param exceptionEvent    
-	 */  
+	/**
+	 * <p>
+	 * Title: processIOException
+	 * </p>
+	 * <p>
+	 * Description:
+	 * </p>
+	 * 
+	 * @param exceptionEvent
+	 */
 	@Override
 	public void processIOException(IOExceptionEvent exceptionEvent) {
 		// TODO Auto-generated method stub
-		
+
 	}
 
-	/**   
-	 * <p>Title: processTransactionTerminated</p>   
-	 * <p>Description: </p>   
-	 * @param transactionTerminatedEvent    
-	 */  
+	/**
+	 * <p>
+	 * Title: processTransactionTerminated
+	 * </p>
+	 * <p>
+	 * Description:
+	 * </p>
+	 * 
+	 * @param transactionTerminatedEvent
+	 */
 	@Override
 	public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) {
 		// TODO Auto-generated method stub
-		
+
 	}
 
-	/**   
-	 * <p>Title: processDialogTerminated</p>   
-	 * <p>Description: </p>   
-	 * @param dialogTerminatedEvent    
-	 */  
+	/**
+	 * <p>
+	 * Title: processDialogTerminated
+	 * </p>
+	 * <p>
+	 * Description:
+	 * </p>
+	 * 
+	 * @param dialogTerminatedEvent
+	 */
 	@Override
 	public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) {
 		// TODO Auto-generated method stub
-		
+
 	}
-	
+
 	private ServerTransaction getServerTransaction(RequestEvent evt) {
 		Request request = evt.getRequest();
 		ServerTransaction serverTransaction = evt.getServerTransaction();
@@ -185,11 +213,11 @@ public class SipLayer implements SipListener{
 
 		if (serverTransaction == null) {
 			try {
-			if (isTcp) {
-				serverTransaction = tcpSipProvider.getNewServerTransaction(request);
-			} else {
-				serverTransaction = udpSipProvider.getNewServerTransaction(request);
-			}
+				if (isTcp) {
+					serverTransaction = tcpSipProvider.getNewServerTransaction(request);
+				} else {
+					serverTransaction = udpSipProvider.getNewServerTransaction(request);
+				}
 			} catch (TransactionAlreadyExistsException e) {
 				e.printStackTrace();
 			} catch (TransactionUnavailableException e) {
@@ -199,7 +227,6 @@ public class SipLayer implements SipListener{
 		return serverTransaction;
 	}
 
-
 	public AddressFactory getAddressFactory() {
 		return addressFactory;
 	}
@@ -219,5 +246,5 @@ public class SipLayer implements SipListener{
 	public SipProvider getUdpSipProvider() {
 		return udpSipProvider;
 	}
-	
+
 }

+ 26 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/auth/RegisterLogicHandler.java

@@ -0,0 +1,26 @@
+package com.genersoft.iot.vmp.gb28181.auth;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
+
+/**    
+ * @Description:注册逻辑处理,当设备注册后触发逻辑。
+ * @author: songww
+ * @date:   2020年5月8日 下午9:41:46     
+ */
+@Component
+public class RegisterLogicHandler {
+
+	@Autowired
+	private SIPCommander cmder;
+	
+	public void onRegister(Device device) {
+		// TODO 后续处理,只有第一次注册时调用查询设备信息,如需更新调用更新API接口
+		cmder.deviceInfoQuery(device);
+		
+		cmder.catalogQuery(device);
+	}
+}

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

@@ -0,0 +1,51 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+import java.util.List;
+
+/**    
+ * @Description:设备录像信息bean 
+ * @author: songww
+ * @date:   2020年5月8日 下午2:05:56     
+ */
+public class RecordInfo {
+
+	private String deviceId;
+	
+	private String name;
+	
+	private int sumNum;
+	
+	private List<RecordItem> recordList;
+
+	public String getDeviceId() {
+		return deviceId;
+	}
+
+	public void setDeviceId(String deviceId) {
+		this.deviceId = deviceId;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public int getSumNum() {
+		return sumNum;
+	}
+
+	public void setSumNum(int sumNum) {
+		this.sumNum = sumNum;
+	}
+
+	public List<RecordItem> getRecordList() {
+		return recordList;
+	}
+
+	public void setRecordList(List<RecordItem> recordList) {
+		this.recordList = recordList;
+	}
+}

+ 99 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java

@@ -0,0 +1,99 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+/**    
+ * @Description:设备录像bean 
+ * @author: songww
+ * @date:   2020年5月8日 下午2:06:54     
+ */
+public class RecordItem {
+
+	private String deviceId;
+	
+	private String name;
+	
+	private String filePath;
+	
+	private String address;
+	
+	private String startTime;
+	
+	private String endTime;
+	
+	private int secrecy;
+	
+	private String type;
+	
+	private String recordId;
+
+	public String getDeviceId() {
+		return deviceId;
+	}
+
+	public void setDeviceId(String deviceId) {
+		this.deviceId = deviceId;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public String getFilePath() {
+		return filePath;
+	}
+
+	public void setFilePath(String filePath) {
+		this.filePath = filePath;
+	}
+
+	public String getAddress() {
+		return address;
+	}
+
+	public void setAddress(String address) {
+		this.address = address;
+	}
+
+	public String getStartTime() {
+		return startTime;
+	}
+
+	public void setStartTime(String startTime) {
+		this.startTime = startTime;
+	}
+
+	public int getSecrecy() {
+		return secrecy;
+	}
+
+	public void setSecrecy(int secrecy) {
+		this.secrecy = secrecy;
+	}
+
+	public String getType() {
+		return type;
+	}
+
+	public void setType(String type) {
+		this.type = type;
+	}
+
+	public String getRecordId() {
+		return recordId;
+	}
+
+	public void setRecordId(String recordId) {
+		this.recordId = recordId;
+	}
+
+	public String getEndTime() {
+		return endTime;
+	}
+
+	public void setEndTime(String endTime) {
+		this.endTime = endTime;
+	}
+}

+ 42 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java

@@ -0,0 +1,42 @@
+package com.genersoft.iot.vmp.gb28181.transmit.callback;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.async.DeferredResult;
+
+/**    
+ * @Description:TODO(这里用一句话描述这个类的作用)   
+ * @author: songww
+ * @date:   2020年5月8日 下午7:59:05     
+ */
+@Component
+public class DeferredResultHolder {
+	
+	public static final String CALLBACK_CMD_DEVICEINFO = "CALLBACK_DEVICEINFO";
+	
+	public static final String CALLBACK_CMD_CATALOG = "CALLBACK_CATALOG";
+	
+	public static final String CALLBACK_CMD_RECORDINFO = "CALLBACK_RECORDINFO";
+
+	private Map<String, DeferredResult> map = new HashMap<String, DeferredResult>();
+	
+	public void put(String key, DeferredResult result) {
+		map.put(key, result);
+	}
+	
+	public DeferredResult get(String key) {
+		return map.get(key);
+	}
+	
+	public void invokeResult(RequestMessage msg) {
+		DeferredResult result = map.get(msg.getId());
+		if (result == null) {
+			return;
+		}
+		result.setResult(new ResponseEntity<>(msg.getData(),HttpStatus.OK));
+	}
+}

+ 51 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/RequestMessage.java

@@ -0,0 +1,51 @@
+package com.genersoft.iot.vmp.gb28181.transmit.callback;
+
+/**    
+ * @Description:TODO(这里用一句话描述这个类的作用)   
+ * @author: songww
+ * @date:   2020年5月8日 下午1:09:18     
+ */
+public class RequestMessage {
+	
+	private String id;
+
+	private String deviceId;
+	
+	private String type;
+	
+	private Object data;
+	
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public String getDeviceId() {
+		return deviceId;
+	}
+
+	public void setDeviceId(String deviceId) {
+		this.deviceId = deviceId;
+		this.id = type + deviceId;
+	}
+
+	public String getType() {
+		return type;
+	}
+
+	public void setType(String type) {
+		this.type = type;
+		this.id = type + deviceId;
+	}
+
+	public Object getData() {
+		return data;
+	}
+
+	public void setData(Object data) {
+		this.data = data;
+	}
+}

+ 29 - 27
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java

@@ -12,48 +12,48 @@ public interface ISIPCommander {
 	/**
 	 * 云台方向放控制,使用配置文件中的默认镜头移动速度
 	 * 
-	 * @param deviceId  控制设备
+	 * @param device  控制设备
 	 * @param channelId  预览通道
 	 * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
      * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
      * @param moveSpeed  镜头移动速度
 	 */
-	public boolean ptzdirectCmd(String deviceId,String channelId,int leftRight, int upDown);
+	public boolean ptzdirectCmd(Device device,String channelId,int leftRight, int upDown);
 	
 	/**
 	 * 云台方向放控制
 	 * 
-	 * @param deviceId  控制设备
+	 * @param device  控制设备
 	 * @param channelId  预览通道
 	 * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
      * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
      * @param moveSpeed  镜头移动速度
 	 */
-	public boolean ptzdirectCmd(String deviceId,String channelId,int leftRight, int upDown, int moveSpeed);
+	public boolean ptzdirectCmd(Device device,String channelId,int leftRight, int upDown, int moveSpeed);
 	
 	/**
 	 * 云台缩放控制,使用配置文件中的默认镜头缩放速度
 	 * 
-	 * @param deviceId  控制设备
+	 * @param device  控制设备
 	 * @param channelId  预览通道
      * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
 	 */
-	public boolean ptzZoomCmd(String deviceId,String channelId,int inOut);
+	public boolean ptzZoomCmd(Device device,String channelId,int inOut);
 	
 	/**
 	 * 云台缩放控制
 	 * 
-	 * @param deviceId  控制设备
+	 * @param device  控制设备
 	 * @param channelId  预览通道
      * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
      * @param zoomSpeed  镜头缩放速度
 	 */
-	public boolean ptzZoomCmd(String deviceId,String channelId,int inOut, int moveSpeed);
+	public boolean ptzZoomCmd(Device device,String channelId,int inOut, int moveSpeed);
 	
 	/**
 	 * 云台控制,支持方向与缩放控制
 	 * 
-	 * @param deviceId  控制设备
+	 * @param device  控制设备
 	 * @param channelId  预览通道
 	 * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
      * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
@@ -61,67 +61,67 @@ public interface ISIPCommander {
      * @param moveSpeed  镜头移动速度
      * @param zoomSpeed  镜头缩放速度
 	 */
-	public boolean ptzCmd(String deviceId,String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed);
+	public boolean ptzCmd(Device device,String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed);
 	
 	/**
 	 * 请求预览视频流
 	 * 
-	 * @param deviceId  视频设备
+	 * @param device  视频设备
 	 * @param channelId  预览通道
 	 */
-	public String playStreamCmd(String deviceId,String channelId);
+	public String playStreamCmd(Device device,String channelId);
 	
 	/**
 	 * 语音广播
 	 * 
-	 * @param deviceId  视频设备
+	 * @param device  视频设备
 	 * @param channelId  预览通道
 	 */
-	public String audioBroadcastCmd(String deviceId,String channelId);
+	public boolean audioBroadcastCmd(Device device,String channelId);
 	
 	/**
 	 * 音视频录像控制
 	 * 
-	 * @param deviceId  视频设备
+	 * @param device  视频设备
 	 * @param channelId  预览通道
 	 */
-	public String recordCmd(String deviceId,String channelId);
+	public boolean recordCmd(Device device,String channelId);
 	
 	/**
 	 * 报警布防/撤防命令
 	 * 
-	 * @param deviceId  视频设备
+	 * @param device  视频设备
 	 */
-	public String guardCmd(String deviceId);
+	public boolean guardCmd(Device device);
 	
 	/**
 	 * 报警复位命令
 	 * 
-	 * @param deviceId  视频设备
+	 * @param device  视频设备
 	 */
-	public String alarmCmd(String deviceId);
+	public boolean alarmCmd(Device device);
 	
 	/**
 	 * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
 	 * 
-	 * @param deviceId  视频设备
+	 * @param device  视频设备
 	 * @param channelId  预览通道
 	 */
-	public String iFameCmd(String deviceId,String channelId);
+	public boolean iFameCmd(Device device,String channelId);
 	
 	/**
 	 * 看守位控制命令
 	 * 
-	 * @param deviceId  视频设备
+	 * @param device  视频设备
 	 */
-	public String homePositionCmd(String deviceId);
+	public boolean homePositionCmd(Device device);
 	
 	/**
 	 * 设备配置命令
 	 * 
-	 * @param deviceId  视频设备
+	 * @param device  视频设备
 	 */
-	public String deviceConfigCmd(String deviceId);
+	public boolean deviceConfigCmd(Device device);
 	
 	
 	/**
@@ -150,8 +150,10 @@ public interface ISIPCommander {
 	 * 查询录像信息
 	 * 
 	 * @param device 视频设备
+	 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
+	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
 	 */
-	public boolean recordInfoQuery(Device device);
+	public boolean recordInfoQuery(Device device, String startTime, String endTime);
 	
 	/**
 	 * 查询报警信息

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

@@ -8,6 +8,7 @@ import javax.sip.SipException;
 import javax.sip.message.Request;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.stereotype.Component;
 
 import com.genersoft.iot.vmp.conf.SipConfig;
@@ -15,7 +16,7 @@ import com.genersoft.iot.vmp.gb28181.SipLayer;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
 
 /**    
  * @Description:设备能力接口,用于定义设备的控制、查询能力   
@@ -34,66 +35,63 @@ public class SIPCommander implements ISIPCommander {
 	@Autowired
 	private SipLayer sipLayer;
 	
-	@Autowired
-	private IVideoManagerStorager storager;
-	 
 	/**
 	 * 云台方向放控制,使用配置文件中的默认镜头移动速度
 	 * 
-	 * @param deviceId  控制设备
+	 * @param device  控制设备
 	 * @param channelId  预览通道
 	 * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
      * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
      * @param moveSpeed  镜头移动速度
 	 */
 	@Override
-	public boolean ptzdirectCmd(String deviceId, String channelId, int leftRight, int upDown) {
-		return ptzCmd(deviceId, channelId, leftRight, upDown, 0, config.getSpeed(), 0);
+	public boolean ptzdirectCmd(Device device, String channelId, int leftRight, int upDown) {
+		return ptzCmd(device, channelId, leftRight, upDown, 0, config.getSpeed(), 0);
 	}
 
 	/**
 	 * 云台方向放控制
 	 * 
-	 * @param deviceId  控制设备
+	 * @param device  控制设备
 	 * @param channelId  预览通道
 	 * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
      * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
      * @param moveSpeed  镜头移动速度
 	 */
 	@Override
-	public boolean ptzdirectCmd(String deviceId, String channelId, int leftRight, int upDown, int moveSpeed) {
-		return ptzCmd(deviceId, channelId, leftRight, upDown, 0, moveSpeed, 0);
+	public boolean ptzdirectCmd(Device device, String channelId, int leftRight, int upDown, int moveSpeed) {
+		return ptzCmd(device, channelId, leftRight, upDown, 0, moveSpeed, 0);
 	}
 
 	/**
 	 * 云台缩放控制,使用配置文件中的默认镜头缩放速度
 	 * 
-	 * @param deviceId  控制设备
+	 * @param device  控制设备
 	 * @param channelId  预览通道
      * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
 	 */  
 	@Override
-	public boolean ptzZoomCmd(String deviceId, String channelId, int inOut) {
-		return ptzCmd(deviceId, channelId, 0, 0, inOut, 0, config.getSpeed());
+	public boolean ptzZoomCmd(Device device, String channelId, int inOut) {
+		return ptzCmd(device, channelId, 0, 0, inOut, 0, config.getSpeed());
 	}
 
 	/**
 	 * 云台缩放控制
 	 * 
-	 * @param deviceId  控制设备
+	 * @param device  控制设备
 	 * @param channelId  预览通道
      * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
      * @param zoomSpeed  镜头缩放速度
 	 */ 
 	@Override
-	public boolean ptzZoomCmd(String deviceId, String channelId, int inOut, int zoomSpeed) {
-		return ptzCmd(deviceId, channelId, 0, 0, inOut, 0, zoomSpeed);
+	public boolean ptzZoomCmd(Device device, String channelId, int inOut, int zoomSpeed) {
+		return ptzCmd(device, channelId, 0, 0, inOut, 0, zoomSpeed);
 	}
   
 	/**
 	 * 云台控制,支持方向与缩放控制
 	 * 
-	 * @param deviceId  控制设备
+	 * @param device  控制设备
 	 * @param channelId  预览通道
 	 * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
      * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
@@ -102,10 +100,9 @@ public class SIPCommander implements ISIPCommander {
      * @param zoomSpeed  镜头缩放速度
 	 */
 	@Override
-	public boolean ptzCmd(String deviceId, String channelId, int leftRight, int upDown, int inOut, int moveSpeed,
+	public boolean ptzCmd(Device device, String channelId, int leftRight, int upDown, int inOut, int moveSpeed,
 			int zoomSpeed) {
 		try {
-			Device device = storager.queryVideoDevice(deviceId);
 			StringBuffer ptzXml = new StringBuffer(200);
 			ptzXml.append("<?xml version=\"1.0\" ?>");
 			ptzXml.append("<Control>");
@@ -119,7 +116,7 @@ public class SIPCommander implements ISIPCommander {
 			
 			Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), "ViaPtzBranch", "FromPtzTag", "ToPtzTag");
 			
-			transmitRequest(device.getTransport(), request);
+			transmitRequest(device, request);
 			
 			return true;
 		} catch (SipException | ParseException | InvalidArgumentException e) {
@@ -131,15 +128,13 @@ public class SIPCommander implements ISIPCommander {
 	/**
 	 * 请求预览视频流
 	 * 
-	 * @param deviceId  视频设备
+	 * @param device  视频设备
 	 * @param channelId  预览通道
 	 */  
 	@Override
-	public String playStreamCmd(String deviceId, String channelId) {
+	public String playStreamCmd(Device device, String channelId) {
 		try {
 			
-			Device device = storager.queryVideoDevice(deviceId);
-			
 			//生成ssrc标识数据流 10位数字
 			String ssrc = "";
 			Random random = new Random();
@@ -170,7 +165,7 @@ public class SIPCommander implements ISIPCommander {
 	        
 	        Request request = headerProvider.createInviteRequest(device, content.toString(), null, "live", null);
 	
-	        transmitRequest(device.getTransport(), request);
+	        transmitRequest(device, request);
 			return ssrc;
 		} catch ( SipException | ParseException | InvalidArgumentException e) {
 			e.printStackTrace();
@@ -181,81 +176,81 @@ public class SIPCommander implements ISIPCommander {
 	/**
 	 * 语音广播
 	 * 
-	 * @param deviceId  视频设备
+	 * @param device  视频设备
 	 * @param channelId  预览通道
 	 */
 	@Override
-	public String audioBroadcastCmd(String deviceId, String channelId) {
+	public boolean audioBroadcastCmd(Device device, String channelId) {
 		// TODO Auto-generated method stub
-		return null;
+		return false;
 	}
 
 	/**
 	 * 音视频录像控制
 	 * 
-	 * @param deviceId  视频设备
+	 * @param device  视频设备
 	 * @param channelId  预览通道
 	 */  
 	@Override
-	public String recordCmd(String deviceId, String channelId) {
+	public boolean recordCmd(Device device, String channelId) {
 		// TODO Auto-generated method stub
-		return null;
+		return false;
 	}
 
 	/**
 	 * 报警布防/撤防命令
 	 * 
-	 * @param deviceId  视频设备
+	 * @param device  视频设备
 	 */  
 	@Override
-	public String guardCmd(String deviceId) {
+	public boolean guardCmd(Device device) {
 		// TODO Auto-generated method stub
-		return null;
+		return false;
 	}
 
 	/**
 	 * 报警复位命令
 	 * 
-	 * @param deviceId  视频设备
+	 * @param device  视频设备
 	 */  
 	@Override
-	public String alarmCmd(String deviceId) {
+	public boolean alarmCmd(Device device) {
 		// TODO Auto-generated method stub
-		return null;
+		return false;
 	}
 
 	/**
 	 * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
 	 * 
-	 * @param deviceId  视频设备
+	 * @param device  视频设备
 	 * @param channelId  预览通道
 	 */ 
 	@Override
-	public String iFameCmd(String deviceId, String channelId) {
+	public boolean iFameCmd(Device device, String channelId) {
 		// TODO Auto-generated method stub
-		return null;
+		return false;
 	}
 
 	/**
 	 * 看守位控制命令
 	 * 
-	 * @param deviceId  视频设备
+	 * @param device  视频设备
 	 */  
 	@Override
-	public String homePositionCmd(String deviceId) {
+	public boolean homePositionCmd(Device device) {
 		// TODO Auto-generated method stub
-		return null;
+		return false;
 	}
 
 	/**
 	 * 设备配置命令
 	 * 
-	 * @param deviceId  视频设备
+	 * @param device  视频设备
 	 */  
 	@Override
-	public String deviceConfigCmd(String deviceId) {
+	public boolean deviceConfigCmd(Device device) {
 		// TODO Auto-generated method stub
-		return null;
+		return false;
 	}
 
 	/**
@@ -286,8 +281,8 @@ public class SIPCommander implements ISIPCommander {
 			catalogXml.append("</Query>");
 			
 			Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaDeviceInfoBranch", "FromDeviceInfoTag", "ToDeviceInfoTag");
+			transmitRequest(device, request);
 			
-			transmitRequest(device.getTransport(), request);
 		} catch (SipException | ParseException | InvalidArgumentException e) {
 			e.printStackTrace();
 			return false;
@@ -312,9 +307,7 @@ public class SIPCommander implements ISIPCommander {
 			catalogXml.append("</Query>");
 			
 			Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaCatalogBranch", "FromCatalogTag", "ToCatalogTag");
-			
-			transmitRequest(device.getTransport(), request);
-
+			transmitRequest(device, request);
 		} catch (SipException | ParseException | InvalidArgumentException e) {
 			e.printStackTrace();
 			return false;
@@ -326,11 +319,32 @@ public class SIPCommander implements ISIPCommander {
 	 * 查询录像信息
 	 * 
 	 * @param device 视频设备
+	 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
+	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
 	 */  
 	@Override
-	public boolean recordInfoQuery(Device device) {
-		// TODO Auto-generated method stub
-		return false;
+	public boolean recordInfoQuery(Device device, String startTime, String endTime) {
+		
+		try {
+			StringBuffer catalogXml = new StringBuffer(200);
+			catalogXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>");
+			catalogXml.append("<Query>");
+			catalogXml.append("<CmdType>RecordInfo</CmdType>");
+			catalogXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>");
+			catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>");
+			catalogXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>");
+			catalogXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "</EndTime>");
+			// 大华NVR要求必须增加一个值为all的文本元素节点Type
+			catalogXml.append("<Type>all</Type>");
+			catalogXml.append("</Query>");
+			
+			Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaRecordInfoBranch", "FromRecordInfoTag", "ToRecordInfoTag");
+			transmitRequest(device, request);
+		} catch (SipException | ParseException | InvalidArgumentException e) {
+			e.printStackTrace();
+			return false;
+		}
+		return true;
 	}
 
 	/**
@@ -377,10 +391,10 @@ public class SIPCommander implements ISIPCommander {
 		return false;
 	}
 	
-	private void transmitRequest(String transport, Request request) throws SipException {
-		if(transport.equals("TCP")) {
+	private void transmitRequest(Device device, Request request) throws SipException {
+		if(device.getTransport().equals("TCP")) {
 			sipLayer.getTcpSipProvider().sendRequest(request);
-		} else if(transport.equals("UDP")) {
+		} else if(device.getTransport().equals("UDP")) {
 			sipLayer.getUdpSipProvider().sendRequest(request);
 		}
 	}

+ 112 - 42
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java

@@ -2,8 +2,10 @@ package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
 
 import java.io.ByteArrayInputStream;
 import java.text.ParseException;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 
 import javax.sip.InvalidArgumentException;
@@ -24,9 +26,14 @@ import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.gb28181.SipLayer;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
+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.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.request.ISIPRequestProcessor;
+import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
 import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
 
@@ -51,6 +58,9 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
 	@Autowired
 	private EventPublisher publisher;
 	
+	@Autowired
+	private DeferredResultHolder deferredResultHolder;
+	
 	/**   
 	 * 处理MESSAGE请求
 	 *  
@@ -74,21 +84,49 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
 			processMessageDeviceInfo(evt);
 		} else if (new String(request.getRawContent()).contains("<CmdType>Alarm</CmdType>")) {
 			processMessageAlarm(evt);
+		} else if (new String(request.getRawContent()).contains("<CmdType>recordInfo</CmdType>")) {
+			processMessageRecordInfo(evt);
 		}
 		
 	}
 	
+	/**
+	 * 收到deviceInfo设备信息请求 处理
+	 * @param evt
+	 */
+	private void processMessageDeviceInfo(RequestEvent evt) {
+		try {
+			Element rootElement = getRootElement(evt);
+			Element deviceIdElement = rootElement.element("DeviceID");
+			String deviceId = deviceIdElement.getText().toString();
+			
+			Device device = storager.queryVideoDevice(deviceId);
+			if (device == null) {
+				return;
+			}
+			device.setName(XmlUtil.getText(rootElement,"DeviceName"));
+			device.setManufacturer(XmlUtil.getText(rootElement,"Manufacturer"));
+			device.setModel(XmlUtil.getText(rootElement,"Model"));
+			device.setFirmware(XmlUtil.getText(rootElement,"Firmware"));
+			storager.update(device);
+			
+			RequestMessage msg = new RequestMessage();
+			msg.setDeviceId(deviceId);
+			msg.setType(DeferredResultHolder.CALLBACK_CMD_DEVICEINFO);
+			msg.setData(device);
+			deferredResultHolder.invokeResult(msg);
+		} catch (DocumentException e) {
+			e.printStackTrace();
+		}
+	}
+	
 	/***
 	 * 收到catalog设备目录列表请求 处理
 	 * @param evt
 	 */
 	private void processMessageCatalogList(RequestEvent evt) {
 		try {
-			Request request = evt.getRequest();
-			SAXReader reader = new SAXReader();
-			reader.setEncoding("GB2312");
-			Document xml = reader.read(new ByteArrayInputStream(request.getRawContent()));
-			Element rootElement = xml.getRootElement();
+			Element rootElement = getRootElement(evt);
 			Element deviceIdElement = rootElement.element("DeviceID");
 			String deviceId = deviceIdElement.getText().toString();
 			Element deviceListElement = rootElement.element("DeviceList");
@@ -152,6 +190,11 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
 				}
 				// 更新
 				storager.update(device);
+				RequestMessage msg = new RequestMessage();
+				msg.setDeviceId(deviceId);
+				msg.setType(DeferredResultHolder.CALLBACK_CMD_CATALOG);
+				msg.setData(device);
+				deferredResultHolder.invokeResult(msg);
 			}
 		} catch (DocumentException e) {
 			e.printStackTrace();
@@ -159,16 +202,12 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
 	}
 	
 	/***
-	 * 收到deviceInfo设备信息请求 处理
+	 * 收到alarm设备报警信息 处理
 	 * @param evt
 	 */
-	private void processMessageDeviceInfo(RequestEvent evt) {
+	private void processMessageAlarm(RequestEvent evt) {
 		try {
-			Request request = evt.getRequest();
-			SAXReader reader = new SAXReader();
-			// reader.setEncoding("GB2312");
-			Document xml = reader.read(new ByteArrayInputStream(request.getRawContent()));
-			Element rootElement = xml.getRootElement();
+			Element rootElement = getRootElement(evt);
 			Element deviceIdElement = rootElement.element("DeviceID");
 			String deviceId = deviceIdElement.getText().toString();
 			
@@ -188,52 +227,83 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
 	}
 	
 	/***
-	 * 收到alarm设备报警信息 处理
+	 * 收到keepalive请求 处理
 	 * @param evt
 	 */
-	private void processMessageAlarm(RequestEvent evt) {
+	private void processMessageKeepAlive(RequestEvent evt){
 		try {
 			Request request = evt.getRequest();
-			SAXReader reader = new SAXReader();
-			// reader.setEncoding("GB2312");
-			Document xml = reader.read(new ByteArrayInputStream(request.getRawContent()));
-			Element rootElement = xml.getRootElement();
+			Response response = layer.getMessageFactory().createResponse(Response.OK,request);
+			Element rootElement = getRootElement(evt);
 			Element deviceIdElement = rootElement.element("DeviceID");
-			String deviceId = deviceIdElement.getText().toString();
-			
-			Device device = storager.queryVideoDevice(deviceId);
-			if (device == null) {
-				return;
-			}
-			device.setName(XmlUtil.getText(rootElement,"DeviceName"));
-			device.setManufacturer(XmlUtil.getText(rootElement,"Manufacturer"));
-			device.setModel(XmlUtil.getText(rootElement,"Model"));
-			device.setFirmware(XmlUtil.getText(rootElement,"Firmware"));
-			storager.update(device);
-			cmder.catalogQuery(device);
-		} catch (DocumentException e) {
+			transaction.sendResponse(response);
+			publisher.onlineEventPublish(deviceIdElement.getText(), VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
+		} catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
 			e.printStackTrace();
 		}
 	}
 	
 	/***
-	 * 收到keepalive请求 处理
+	 * 收到catalog设备目录列表请求 处理
 	 * @param evt
 	 */
-	private void processMessageKeepAlive(RequestEvent evt){
+	private void processMessageRecordInfo(RequestEvent evt) {
 		try {
-			Request request = evt.getRequest();
-			Response response = layer.getMessageFactory().createResponse(Response.OK,request);
-			SAXReader reader = new SAXReader();
-			Document xml = reader.read(new ByteArrayInputStream(request.getRawContent()));
-			// reader.setEncoding("GB2312");
-			Element rootElement = xml.getRootElement();
+			RecordInfo recordInfo = new RecordInfo();
+			Element rootElement = getRootElement(evt);
 			Element deviceIdElement = rootElement.element("DeviceID");
-			transaction.sendResponse(response);
-			publisher.onlineEventPublish(deviceIdElement.getText(), VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
-		} catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
+			String deviceId = deviceIdElement.getText().toString();
+			recordInfo.setDeviceId(deviceId);
+			recordInfo.setName(XmlUtil.getText(rootElement,"Name"));
+			recordInfo.setSumNum(Integer.parseInt(XmlUtil.getText(rootElement,"SumNum")));
+			Element recordListElement = rootElement.element("RecordList");
+			if (recordListElement == null) {
+				return;
+			}
+			
+			Iterator<Element> recordListIterator = recordListElement.elementIterator();
+			if (recordListIterator != null) {
+				
+				List<RecordItem> recordList = new ArrayList<RecordItem>();
+				RecordItem record = new RecordItem();
+				// 遍历DeviceList
+				while (recordListIterator.hasNext()) {
+					Element itemRecord = recordListIterator.next();
+					Element recordElement = itemRecord.element("DeviceID");
+					if (recordElement == null) {
+						continue;
+					}
+					record.setDeviceId(XmlUtil.getText(itemRecord,"DeviceID"));
+					record.setName(XmlUtil.getText(itemRecord,"Name"));
+					record.setFilePath(XmlUtil.getText(itemRecord,"FilePath"));
+					record.setAddress(XmlUtil.getText(itemRecord,"Address"));
+					record.setStartTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(XmlUtil.getText(itemRecord,"StartTime")));
+					record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(XmlUtil.getText(itemRecord,"EndTime")));
+					record.setSecrecy(itemRecord.element("Secrecy") == null? 0:Integer.parseInt(XmlUtil.getText(itemRecord,"Secrecy")));
+					record.setType(XmlUtil.getText(itemRecord,"Type"));
+					record.setRecordId(XmlUtil.getText(itemRecord,"RecordID"));
+					recordList.add(record);
+				}
+				recordInfo.setRecordList(recordList);
+			}
+			
+			
+			RequestMessage msg = new RequestMessage();
+			msg.setDeviceId(deviceId);
+			msg.setType(DeferredResultHolder.CALLBACK_CMD_RECORDINFO);
+			msg.setData(recordInfo);
+			deferredResultHolder.invokeResult(msg);
+		} catch (DocumentException e) {
 			e.printStackTrace();
 		}
 	}
+	
+	private Element getRootElement(RequestEvent evt) throws DocumentException {
+		Request request = evt.getRequest();
+		SAXReader reader = new SAXReader();
+		reader.setEncoding("GB2312");
+		Document xml = reader.read(new ByteArrayInputStream(request.getRawContent()));
+		return xml.getRootElement();
+	}
 
 }

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

@@ -25,10 +25,10 @@ import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.gb28181.SipLayer;
 import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper;
+import com.genersoft.iot.vmp.gb28181.auth.RegisterLogicHandler;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.Host;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
-import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
 
@@ -48,7 +48,7 @@ public class RegisterRequestProcessor implements ISIPRequestProcessor {
 	private SipConfig config;
 	
 	@Autowired
-	private SIPCommander cmder;
+	private RegisterLogicHandler handler;
 	
 	@Autowired
 	private IVideoManagerStorager storager;
@@ -149,7 +149,7 @@ public class RegisterRequestProcessor implements ISIPRequestProcessor {
 				System.out.println("注册成功! deviceId:" + device.getDeviceId());
 				storager.update(device);
 				publisher.onlineEventPublish(device.getDeviceId(), VideoManagerConstants.EVENT_ONLINE_REGISTER);
-				cmder.deviceInfoQuery(device);
+				handler.onRegister(device);
 			} else if (registerFlag == 2) {
 				System.out.println("注销成功! deviceId:" + device.getDeviceId());
 				publisher.outlineEventPublish(device.getDeviceId(), VideoManagerConstants.EVENT_OUTLINE_UNREGISTER);

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

@@ -0,0 +1,40 @@
+package com.genersoft.iot.vmp.gb28181.utils;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+
+/**    
+ * @Description:时间工具类,主要处理ISO 8601格式转换
+ * @author: songww
+ * @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_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 "";
+    }
+}

+ 152 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java

@@ -0,0 +1,152 @@
+package com.genersoft.iot.vmp.media.zlm;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**    
+ * @Description:针对 ZLMediaServer的hook事件监听
+ * @author: songww
+ * @date:   2020年5月8日 上午10:46:48     
+ */
+@RestController
+@RequestMapping("/hook/zlm")
+public class ZLMHttpHookListener {
+
+	private final static Logger logger = LoggerFactory.getLogger(ZLMHttpHookListener.class);
+	
+	/**
+	 * 流量统计事件,播放器或推流器断开时并且耗用流量超过特定阈值时会触发此事件,阈值通过配置文件general.flowThreshold配置;此事件对回复不敏感。
+	 *  
+	 */
+	@PostMapping("/on_flow_report")
+	public ResponseEntity onFlowReport(){
+		// TODO Auto-generated method stub
+		
+		return null;
+	}
+	
+	/**
+	 * 访问http文件服务器上hls之外的文件时触发。
+	 *  
+	 */
+	@PostMapping("/on_http_access")
+	public ResponseEntity onHttpAccess(){
+		// TODO Auto-generated method stub
+		
+		return null;
+	}
+	
+	/**
+	 * 播放器鉴权事件,rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件。
+	 *  
+	 */
+	@PostMapping("/on_play")
+	public ResponseEntity onPlay(){
+		// TODO Auto-generated method stub
+		
+		return null;
+	}
+	
+	/**
+	 * rtsp/rtmp/rtp推流鉴权事件。
+	 *  
+	 */
+	@PostMapping("/on_publish")
+	public ResponseEntity onPublish(){
+		// TODO Auto-generated method stub
+		
+		return null;
+	}
+	
+	/**
+	 * 录制mp4完成后通知事件;此事件对回复不敏感。
+	 *  
+	 */
+	@PostMapping("/on_record_mp4")
+	public ResponseEntity onRecordMp4(){
+		// TODO Auto-generated method stub
+		
+		return null;
+	}
+	
+	/**
+	 * 该rtsp流是否开启rtsp专用方式的鉴权事件,开启后才会触发on_rtsp_auth事件。需要指出的是rtsp也支持url参数鉴权,它支持两种方式鉴权。
+	 *  
+	 */
+	@PostMapping("/on_rtsp_auth")
+	public ResponseEntity onRtspAuth(){
+		// TODO Auto-generated method stub
+		
+		return null;
+	}
+	
+	/**
+	 * rtsp专用的鉴权事件,先触发on_rtsp_realm事件然后才会触发on_rtsp_auth事件。
+	 *  
+	 */
+	@PostMapping("/on_rtsp_realm")
+	public ResponseEntity onRtspRealm(){
+		// TODO Auto-generated method stub
+		
+		return null;
+	}
+	
+	/**
+	 * shell登录鉴权,ZLMediaKit提供简单的telnet调试方式,使用telnet 127.0.0.1 9000能进入MediaServer进程的shell界面。
+	 *  
+	 */
+	@PostMapping("/on_shell_login")
+	public ResponseEntity onShellLogin(){
+		// TODO Auto-generated method stub
+		
+		return null;
+	}
+	
+	/**
+	 * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。
+	 *  
+	 */
+	@PostMapping("/on_stream_changed")
+	public ResponseEntity onStreamChanged(){
+		// TODO Auto-generated method stub
+		
+		return null;
+	}
+	
+	/**
+	 * 流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流。
+	 *  
+	 */
+	@PostMapping("/on_stream_none_reader")
+	public ResponseEntity onStreamNoneReader(){
+		// TODO Auto-generated method stub
+		
+		return null;
+	}
+	
+	/**
+	 * 流未找到事件,用户可以在此事件触发时,立即去拉流,这样可以实现按需拉流;此事件对回复不敏感。
+	 *  
+	 */
+	@PostMapping("/on_stream_not_found")
+	public ResponseEntity onStreamNotFound(){
+		// TODO Auto-generated method stub
+		
+		return null;
+	}
+	
+	/**
+	 * 服务器启动事件,可以用于监听服务器崩溃重启;此事件对回复不敏感。
+	 *  
+	 */
+	@PostMapping("/on_server_started")
+	public ResponseEntity onServerStarted(){
+		// TODO Auto-generated method stub
+		
+		return null;
+	}
+}

+ 29 - 5
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java

@@ -1,7 +1,8 @@
 package com.genersoft.iot.vmp.vmanager.device;
 
-import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -10,10 +11,14 @@ import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.context.request.async.DeferredResult;
 
 import com.genersoft.iot.vmp.gb28181.bean.Device;
+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.IVideoManagerStorager;
 
 @RestController
@@ -25,16 +30,21 @@ public class DeviceController {
 	@Autowired
 	private IVideoManagerStorager storager;
 	
+	@Autowired
+	private SIPCommander cmder;
+	
+	@Autowired
+	private DeferredResultHolder resultHolder;
+	
 	@GetMapping("/devices/{deviceId}")
-	public ResponseEntity<List<Device>> devices(@PathVariable String deviceId){
+	public ResponseEntity<Device> devices(@PathVariable String deviceId){
 		
 		if (logger.isDebugEnabled()) {
 			logger.debug("查询视频设备API调用,deviceId:" + deviceId);
 		}
 		
-		List<Device> deviceList = new ArrayList<>();
-		deviceList.add(storager.queryVideoDevice(deviceId));
-		return new ResponseEntity<>(deviceList,HttpStatus.OK);
+		Device device = storager.queryVideoDevice(deviceId);
+		return new ResponseEntity<>(device,HttpStatus.OK);
 	}
 	
 	@GetMapping("/devices")
@@ -47,4 +57,18 @@ public class DeviceController {
 		List<Device> deviceList = storager.queryVideoDeviceList(null);
 		return new ResponseEntity<>(deviceList,HttpStatus.OK);
 	}
+	
+	@PostMapping("/devices/{deviceId}/sync")
+	public DeferredResult<ResponseEntity<Device>> devicesSync(@PathVariable String deviceId){
+		
+		if (logger.isDebugEnabled()) {
+			logger.debug("设备信息同步API调用,deviceId:" + deviceId);
+		}
+		
+		Device device = storager.queryVideoDevice(deviceId);
+        cmder.catalogQuery(device);
+        DeferredResult<ResponseEntity<Device>> result = new DeferredResult<ResponseEntity<Device>>();
+        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_CATALOG+deviceId, result);
+        return result;
+	}
 }

+ 7 - 1
src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java

@@ -10,7 +10,9 @@ import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
 
 @RestController
 @RequestMapping("/api")
@@ -21,10 +23,14 @@ public class PlayController {
 	@Autowired
 	private SIPCommander cmder;
 	
+	@Autowired
+	private IVideoManagerStorager storager;
+	
 	@GetMapping("/play/{deviceId}_{channelId}")
 	public ResponseEntity<String> play(@PathVariable String deviceId,@PathVariable String channelId){
 		
-		String ssrc = cmder.playStreamCmd(deviceId, channelId);
+		Device device = storager.queryVideoDevice(deviceId);
+		String ssrc = cmder.playStreamCmd(device, channelId);
 		
 		if (logger.isDebugEnabled()) {
 			logger.debug(String.format("设备预览 API调用,deviceId:%s ,channelId:%s",deviceId, channelId));

+ 7 - 1
src/main/java/com/genersoft/iot/vmp/vmanager/ptz/PtzController.java

@@ -10,7 +10,9 @@ import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
 
 @RestController
 @RequestMapping("/api")
@@ -20,6 +22,9 @@ public class PtzController {
 	
 	@Autowired
 	private SIPCommander cmder;
+	
+	@Autowired
+	private IVideoManagerStorager storager;
 
 	/***
 	 * http://localhost:8080/api/ptz/34020000001320000002_34020000001320000008?leftRight=1&upDown=0&inOut=0&moveSpeed=50&zoomSpeed=0
@@ -38,8 +43,9 @@ public class PtzController {
 		if (logger.isDebugEnabled()) {
 			logger.debug(String.format("设备云台控制 API调用,deviceId:%s ,channelId:%s ,leftRight:%d ,upDown:%d ,inOut:%d ,moveSpeed:%d ,zoomSpeed:%d",deviceId, channelId, leftRight, upDown, inOut, moveSpeed, zoomSpeed));
 		}
+		Device device = storager.queryVideoDevice(deviceId);
 		
-		cmder.ptzCmd(deviceId, channelId, leftRight, upDown, inOut, moveSpeed, zoomSpeed);
+		cmder.ptzCmd(device, channelId, leftRight, upDown, inOut, moveSpeed, zoomSpeed);
 		return new ResponseEntity<String>("success",HttpStatus.OK);
 	}
 }

+ 47 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/record/RecordController.java

@@ -0,0 +1,47 @@
+package com.genersoft.iot.vmp.vmanager.record;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.context.request.async.DeferredResult;
+
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.RecordInfo;
+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.IVideoManagerStorager;
+
+@RestController
+@RequestMapping("/api")
+public class RecordController {
+	
+	private final static Logger logger = LoggerFactory.getLogger(RecordController.class);
+	
+	@Autowired
+	private SIPCommander cmder;
+	
+	@Autowired
+	private IVideoManagerStorager storager;
+	
+	@Autowired
+	private DeferredResultHolder resultHolder;
+	
+	@GetMapping("/recordinfo/{deviceId}")
+	public DeferredResult<ResponseEntity<RecordInfo>> recordinfo(@PathVariable String deviceId, String startTime,  String endTime){
+		
+		if (logger.isDebugEnabled()) {
+			logger.debug(String.format("录像信息 API调用,deviceId:%s ,startTime:%s, startTime:%s",deviceId, startTime, endTime));
+		}
+		
+		Device device = storager.queryVideoDevice(deviceId);
+		cmder.recordInfoQuery(device, startTime, endTime);
+		DeferredResult<ResponseEntity<RecordInfo>> result = new DeferredResult<ResponseEntity<RecordInfo>>();
+		resultHolder.put(DeferredResultHolder.CALLBACK_CMD_CATALOG+deviceId, result);
+        return result;
+	}
+}