songww %!s(int64=5) %!d(string=hai) anos
achega
70091f29f2
Modificáronse 48 ficheiros con 4650 adicións e 0 borrados
  1. 173 0
      pom.xml
  2. 20 0
      src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java
  3. 22 0
      src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
  4. 79 0
      src/main/java/com/genersoft/iot/vmp/conf/RedisConfig.java
  5. 83 0
      src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java
  6. 25 0
      src/main/java/com/genersoft/iot/vmp/conf/VManagerConfig.java
  7. 223 0
      src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
  8. 211 0
      src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java
  9. 126 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
  10. 110 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarm.java
  11. 309 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java
  12. 30 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/Host.java
  13. 34 0
      src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java
  14. 42 0
      src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEvent.java
  15. 66 0
      src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEventListener.java
  16. 44 0
      src/main/java/com/genersoft/iot/vmp/gb28181/event/outline/KeepliveTimeoutListener.java
  17. 41 0
      src/main/java/com/genersoft/iot/vmp/gb28181/event/outline/OutlineEvent.java
  18. 58 0
      src/main/java/com/genersoft/iot/vmp/gb28181/event/outline/OutlineEventListener.java
  19. 110 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java
  20. 183 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  21. 111 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java
  22. 48 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/PtzCmdHelper.java
  23. 387 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  24. 17 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/ISIPRequestProcessor.java
  25. 51 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/AckRequestProcessor.java
  26. 33 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/ByeRequestProcessor.java
  27. 33 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/CancelRequestProcessor.java
  28. 77 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/InviteRequestProcessor.java
  29. 239 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java
  30. 32 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/OtherRequestProcessor.java
  31. 163 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java
  32. 64 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/SubscribeRequestProcessor.java
  33. 17 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/ISIPResponseProcessor.java
  34. 33 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/ByeResponseProcessor.java
  35. 33 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/CancelResponseProcessor.java
  36. 40 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/InviteResponseProcessor.java
  37. 32 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/OtherResponseProcessor.java
  38. 121 0
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java
  39. 77 0
      src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
  40. 36 0
      src/main/java/com/genersoft/iot/vmp/storager/VideoManagerStoragerFactory.java
  41. 116 0
      src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java
  42. 129 0
      src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java
  43. 43 0
      src/main/java/com/genersoft/iot/vmp/utils/redis/FastJsonRedisSerializer.java
  44. 557 0
      src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java
  45. 50 0
      src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java
  46. 41 0
      src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java
  47. 45 0
      src/main/java/com/genersoft/iot/vmp/vmanager/ptz/PtzController.java
  48. 36 0
      src/main/resources/application.yml

+ 173 - 0
pom.xml

@@ -0,0 +1,173 @@
+<?xml version="1.0"?>
+<project
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+	xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.springframework.boot</groupId>
+		<artifactId>spring-boot-starter-parent</artifactId>
+		<version>1.5.10.RELEASE</version>
+	</parent>
+
+	<groupId>com.genersoft</groupId>
+	<artifactId>wvp</artifactId>
+	<name>web video platform</name>
+	
+	<properties>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
+		<hystrix.version>1.5.12</hystrix.version>
+		<!-- 依赖版本 -->
+		<mapper.version>3.4.0</mapper.version>
+		<mybatis.version>3.3.1</mybatis.version>
+		<mybatis.spring.version>1.2.4</mybatis.spring.version>
+		<pagehelper.version>4.1.1</pagehelper.version>
+		<snippetsDirectory>${project.build.directory}/generated-snippets</snippetsDirectory>
+		<asciidoctor.input.directory>${project.basedir}/docs/asciidoc</asciidoctor.input.directory>
+		<generated.asciidoc.directory>${project.build.directory}/asciidoc</generated.asciidoc.directory>
+		<asciidoctor.html.output.directory>${project.build.directory}/asciidoc/html</asciidoctor.html.output.directory>
+		<asciidoctor.pdf.output.directory>${project.build.directory}/asciidoc/pdf</asciidoctor.pdf.output.directory>
+		<gson.version>2.8.0</gson.version>
+		<jedis.version>3.0.1</jedis.version>
+		<jsonlib.version>2.4</jsonlib.version>
+	</properties>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-jdbc</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-undertow</artifactId>
+		</dependency>
+		<dependency>
+	        <groupId>org.springframework.boot</groupId>
+	        <artifactId>spring-boot-starter-web</artifactId>
+	    </dependency>
+		<dependency>
+	        <groupId>org.springframework</groupId>
+	        <artifactId>spring-context</artifactId>
+	    </dependency>
+	    
+		
+		<!-- druid -->
+		<dependency>
+			<groupId>com.alibaba</groupId>
+			<artifactId>druid</artifactId>
+			<version>1.0.11</version>
+		</dependency>
+		<dependency>
+			<groupId>mysql</groupId>
+			<artifactId>mysql-connector-java</artifactId>
+			<version>5.1.30</version>
+		</dependency>
+		
+		<!--Mybatis -->
+		<dependency>
+			<groupId>org.mybatis</groupId>
+			<artifactId>mybatis</artifactId>
+			<version>${mybatis.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.mybatis</groupId>
+			<artifactId>mybatis-spring</artifactId>
+			<version>${mybatis.spring.version}</version>
+		</dependency>
+		
+		<!--分页插件 -->
+		<dependency>
+			<groupId>com.github.pagehelper</groupId>
+			<artifactId>pagehelper</artifactId>
+			<version>${pagehelper.version}</version>
+		</dependency>
+
+		<!--通用Mapper -->
+		<dependency>
+			<groupId>tk.mybatis</groupId>
+			<artifactId>mapper</artifactId>
+			<version>${mapper.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-lang3</artifactId>
+			<version>3.4</version>
+		</dependency>
+		<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
+		<dependency>
+			<groupId>com.alibaba</groupId>
+			<artifactId>fastjson</artifactId>
+			<version>1.2.33</version>
+		</dependency>
+		
+		<!--Swagger2 -->
+		<!--在线文档 -->
+		<dependency>
+			<groupId>io.springfox</groupId>
+			<artifactId>springfox-swagger2</artifactId>
+			<version>2.6.1</version>
+		</dependency>
+		<dependency>
+			<groupId>io.springfox</groupId>
+			<artifactId>springfox-swagger-ui</artifactId>
+			<version>2.6.1</version>
+		</dependency>
+		
+		<!-- 日志相关 -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-aop</artifactId>
+		</dependency>
+		
+		<dependency>
+			<groupId>javax.sip</groupId>
+			<artifactId>jain-sip-ri</artifactId>
+			<version>1.3.0-91</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.dom4j</groupId>
+			<artifactId>dom4j</artifactId>
+			<version>2.1.1</version>
+		</dependency>
+		<dependency>
+			<groupId>com.google.code.gson</groupId>
+			<artifactId>gson</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.data</groupId>
+			<artifactId>spring-data-redis</artifactId>
+			<version>1.8.4.RELEASE</version>
+		</dependency>
+		<dependency>
+			<groupId>redis.clients</groupId>
+			<artifactId>jedis</artifactId>
+			<version>2.9.0</version>
+		</dependency>
+		
+	</dependencies>
+	
+	<build>
+		<plugins>
+
+			<plugin>
+				<groupId>org.springframework.boot</groupId>
+				<artifactId>spring-boot-maven-plugin</artifactId>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>1.8</source>
+					<target>1.8</target>
+				</configuration>
+			</plugin>
+
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-surefire-plugin</artifactId>
+			</plugin>
+
+		</plugins>
+	</build>
+</project>

+ 20 - 0
src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java

@@ -0,0 +1,20 @@
+package com.genersoft.iot.vmp;
+
+import java.util.logging.LogManager;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+//@EnableEurekaClient
+//@EnableTransactionManagement
+//@EnableFeignClients(basePackages = { "com.genersoft.iot.vmp", "org.integrain" })
+//@ServletComponentScan("com.genersoft.iot.vmp")
+@EnableAutoConfiguration
+public class VManageBootstrap extends LogManager {
+	public static void main(String[] args) {
+		SpringApplication.run(VManageBootstrap.class, args);
+	}
+
+}

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

@@ -0,0 +1,22 @@
+package com.genersoft.iot.vmp.common;
+
+/**    
+ * @Description:TODO(这里用一句话描述这个类的作用)   
+ * @author: songww
+ * @date:   2019年5月30日 下午3:04:04   
+ *   
+ */
+public class VideoManagerConstants {
+	
+	public static final String CACHEKEY_PREFIX = "VMP_deviceId_";
+
+	public static final String KEEPLIVEKEY_PREFIX = "VMP_keeplive_";
+	
+	public static final String EVENT_ONLINE_REGISTER = "1";
+	
+	public static final String EVENT_ONLINE_KEEPLIVE = "2";
+	
+	public static final String EVENT_OUTLINE_UNREGISTER = "1";
+	
+	public static final String EVENT_OUTLINE_TIMEOUT = "2";
+}

+ 79 - 0
src/main/java/com/genersoft/iot/vmp/conf/RedisConfig.java

@@ -0,0 +1,79 @@
+package com.genersoft.iot.vmp.conf;
+
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.listener.RedisMessageListenerContainer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+import com.alibaba.fastjson.parser.ParserConfig;
+import com.genersoft.iot.vmp.utils.redis.FastJsonRedisSerializer;
+
+/**
+ * @Description: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); 
+		FastJsonRedisSerializer<Object> serializer = new FastJsonRedisSerializer<Object>(Object.class);
+		// value值的序列化采用fastJsonRedisSerializer
+		template.setValueSerializer(serializer);
+		template.setHashValueSerializer(serializer);
+		// key的序列化采用StringRedisSerializer
+		template.setKeySerializer(new StringRedisSerializer());
+		template.setHashKeySerializer(new StringRedisSerializer());
+
+		template.setConnectionFactory(redisConnectionFactory);
+		return template;
+	}
+
+	/**
+	 * redis消息监听器容器 可以添加多个监听不同话题的redis监听器,只需要把消息监听器和相应的消息订阅处理器绑定,该消息监听器
+	 * 通过反射技术调用消息订阅处理器的相关方法进行一些业务处理
+	 * 
+	 * @param connectionFactory
+	 * @param listenerAdapter
+	 * @return
+	 */
+	@Bean
+	RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
+
+        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
+        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");
+//    }
+}

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

@@ -0,0 +1,83 @@
+package com.genersoft.iot.vmp.conf;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class SipConfig {
+
+	@Value("${sip.ip}")
+	String sipIp;
+	@Value("${sip.port}")
+	Integer sipPort;
+	@Value("${sip.domain}")
+	String sipDomain;
+	@Value("${sip.password}")
+	String sipPassword;
+	@Value("${media.ip}")
+	String mediaIp;
+	
+	@Value("${media.port}")
+	Integer mediaPort;
+	
+	@Value("${sip.ptz.speed:50}")
+	Integer speed;
+
+	public String getSipIp() {
+		return sipIp;
+	}
+
+	public void setSipIp(String sipIp) {
+		this.sipIp = sipIp;
+	}
+
+	public Integer getSipPort() {
+		return sipPort;
+	}
+
+	public void setSipPort(Integer sipPort) {
+		this.sipPort = sipPort;
+	}
+
+	public String getSipDomain() {
+		return sipDomain;
+	}
+
+	public void setSipDomain(String sipDomain) {
+		this.sipDomain = sipDomain;
+	}
+
+	public String getSipPassword() {
+		return sipPassword;
+	}
+
+	public void setSipPassword(String sipPassword) {
+		this.sipPassword = sipPassword;
+	}
+
+	public String getMediaIp() {
+		return mediaIp;
+	}
+
+	public void setMediaIp(String mediaIp) {
+		this.mediaIp = mediaIp;
+	}
+
+	public Integer getMediaPort() {
+		return mediaPort;
+	}
+
+	public void setMediaPort(Integer mediaPort) {
+		this.mediaPort = mediaPort;
+	}
+
+	public Integer getSpeed() {
+		return speed;
+	}
+
+	public void setSpeed(Integer speed) {
+		this.speed = speed;
+	}
+	
+	
+}

+ 25 - 0
src/main/java/com/genersoft/iot/vmp/conf/VManagerConfig.java

@@ -0,0 +1,25 @@
+package com.genersoft.iot.vmp.conf;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+/**    
+ * @Description:TODO(这里用一句话描述这个类的作用)   
+ * @author: songww
+ * @date:   2020年5月6日 下午2:46:00     
+ */
+@Configuration("vmConfig")
+public class VManagerConfig {
+
+	@Value("${spring.application.database:redis}")
+    private String database;
+
+	public String getDatabase() {
+		return database;
+	}
+
+	public void setDatabase(String database) {
+		this.database = database;
+	}
+	
+}

+ 223 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java

@@ -0,0 +1,223 @@
+package com.genersoft.iot.vmp.gb28181;
+
+import java.util.Properties;
+
+import javax.sip.DialogTerminatedEvent;
+import javax.sip.IOExceptionEvent;
+import javax.sip.ListeningPoint;
+import javax.sip.RequestEvent;
+import javax.sip.ResponseEvent;
+import javax.sip.ServerTransaction;
+import javax.sip.SipFactory;
+import javax.sip.SipListener;
+import javax.sip.SipProvider;
+import javax.sip.SipStack;
+import javax.sip.TimeoutEvent;
+import javax.sip.TransactionAlreadyExistsException;
+import javax.sip.TransactionTerminatedEvent;
+import javax.sip.TransactionUnavailableException;
+import javax.sip.address.AddressFactory;
+import javax.sip.header.HeaderFactory;
+import javax.sip.header.ViaHeader;
+import javax.sip.message.MessageFactory;
+import javax.sip.message.Request;
+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;
+import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorFactory;
+import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
+import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor;
+
+import gov.nist.javax.sip.SipStackImpl;
+
+@Component
+public class SipLayer implements SipListener{
+	
+	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 {
+		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 {
+			startTcpListener();
+			startUdpListener();
+		} catch (Exception e) {
+			logger.error("Sip Server 启动失败! port {"+config.getSipPort()+"}");
+			e.printStackTrace();
+			throw e;
+		}
+		logger.info("Sip Server 启动成功 port {"+config.getSipPort()+"}");
+		return true;
+	}
+	
+	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 {
+		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
+	 * new request arrives.
+	 */
+	@Override
+	public void processRequest(RequestEvent evt) {
+		ISIPRequestProcessor processor = processorFactory.createRequestProcessor(evt);
+		processor.process(evt, this, getServerTransaction(evt));
+	}
+
+	@Override
+	public void processResponse(ResponseEvent evt) {
+		Response response = evt.getResponse();
+		int status = response.getStatusCode();
+		if ((status >= 200) && (status < 300)) { // Success!
+			ISIPResponseProcessor processor = processorFactory.createResponseProcessor(evt);
+			processor.process(evt,this,config);
+		} else {
+			logger.warn("接收到失败的response响应!status:"+status+",message:"+response.getContent().toString());
+		}
+		//trying不会回复
+		if(status == Response.TRYING){
+
+		}
+	}
+
+	/**   
+	 * <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    
+	 */  
+	@Override
+	public void processIOException(IOExceptionEvent exceptionEvent) {
+		// TODO Auto-generated method stub
+		
+	}
+
+	/**   
+	 * <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    
+	 */  
+	@Override
+	public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) {
+		// TODO Auto-generated method stub
+		
+	}
+	
+	private ServerTransaction getServerTransaction(RequestEvent evt) {
+		Request request = evt.getRequest();
+		ServerTransaction serverTransaction = evt.getServerTransaction();
+		// 判断TCP还是UDP
+		boolean isTcp = false;
+		ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
+		String transport = reqViaHeader.getTransport();
+		if (transport.equals("TCP")) {
+			isTcp = true;
+		}
+
+		if (serverTransaction == null) {
+			try {
+			if (isTcp) {
+				serverTransaction = tcpSipProvider.getNewServerTransaction(request);
+			} else {
+				serverTransaction = udpSipProvider.getNewServerTransaction(request);
+			}
+			} catch (TransactionAlreadyExistsException e) {
+				e.printStackTrace();
+			} catch (TransactionUnavailableException e) {
+				e.printStackTrace();
+			}
+		}
+		return serverTransaction;
+	}
+
+
+	public AddressFactory getAddressFactory() {
+		return addressFactory;
+	}
+
+	public HeaderFactory getHeaderFactory() {
+		return headerFactory;
+	}
+
+	public MessageFactory getMessageFactory() {
+		return messageFactory;
+	}
+
+	public SipProvider getTcpSipProvider() {
+		return tcpSipProvider;
+	}
+
+	public SipProvider getUdpSipProvider() {
+		return udpSipProvider;
+	}
+	
+}

+ 211 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java

@@ -0,0 +1,211 @@
+/*
+* Conditions Of Use
+*
+* This software was developed by employees of the National Institute of
+* Standards and Technology (NIST), an agency of the Federal Government.
+* Pursuant to title 15 Untied States Code Section 105, works of NIST
+* employees are not subject to copyright protection in the United States
+* and are considered to be in the public domain.  As a result, a formal
+* license is not needed to use the software.
+*
+* This software is provided by NIST as a service and is expressly
+* provided "AS IS."  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
+* OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
+* AND DATA ACCURACY.  NIST does not warrant or make any representations
+* regarding the use of the software or the results thereof, including but
+* not limited to the correctness, accuracy, reliability or usefulness of
+* the software.
+*
+* Permission to use this software is contingent upon your acceptance
+* of the terms of this agreement
+*
+* .
+*
+*/
+package com.genersoft.iot.vmp.gb28181.auth;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Date;
+import java.util.Random;
+
+import javax.sip.address.URI;
+import javax.sip.header.AuthorizationHeader;
+import javax.sip.header.HeaderFactory;
+import javax.sip.header.WWWAuthenticateHeader;
+import javax.sip.message.Request;
+import javax.sip.message.Response;
+
+import gov.nist.core.InternalErrorHandler;
+
+/**
+ * Implements the HTTP digest authentication method server side functionality.
+ * 
+ * @author M. Ranganathan
+ * @author Marc Bednarek
+ */
+
+public class DigestServerAuthenticationHelper  {
+    
+    private MessageDigest messageDigest;
+    
+    public static final String DEFAULT_ALGORITHM = "MD5";
+    public static final String DEFAULT_SCHEME = "Digest";
+    
+
+
+
+    /** to hex converter */
+    private static final char[] toHex = { '0', '1', '2', '3', '4', '5', '6',
+            '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+    /**
+     * Default constructor.
+     * @throws NoSuchAlgorithmException 
+     */
+    public DigestServerAuthenticationHelper() 
+        throws NoSuchAlgorithmException {
+            messageDigest = MessageDigest.getInstance(DEFAULT_ALGORITHM);
+    }
+
+    public static String toHexString(byte b[]) {
+        int pos = 0;
+        char[] c = new char[b.length * 2];
+        for (int i = 0; i < b.length; i++) {
+            c[pos++] = toHex[(b[i] >> 4) & 0x0F];
+            c[pos++] = toHex[b[i] & 0x0f];
+        }
+        return new String(c);
+    }
+    
+    /**
+     * Generate the challenge string.
+     *
+     * @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();
+        Random rand = new Random();
+        long pad = rand.nextLong();
+        String nonceString = (new Long(time)).toString()
+                + (new Long(pad)).toString();
+        byte mdbytes[] = messageDigest.digest(nonceString.getBytes());
+        // Convert the mdbytes array into a hex string.
+        return toHexString(mdbytes);
+    }
+
+    public Response generateChallenge(HeaderFactory headerFactory, Response response, String realm) {
+        try {
+            WWWAuthenticateHeader proxyAuthenticate = headerFactory
+                    .createWWWAuthenticateHeader(DEFAULT_SCHEME);
+            proxyAuthenticate.setParameter("realm", realm);
+            proxyAuthenticate.setParameter("nonce", generateNonce());
+            proxyAuthenticate.setParameter("opaque", "");
+            proxyAuthenticate.setParameter("stale", "FALSE");
+            proxyAuthenticate.setParameter("algorithm", DEFAULT_ALGORITHM);
+            response.setHeader(proxyAuthenticate);
+        } catch (Exception ex) {
+            InternalErrorHandler.handleException(ex);
+        }
+        return response;
+    }
+    /**
+     * Authenticate the inbound request.
+     *
+     * @param request - the request to authenticate.
+     * @param hashedPassword -- the MD5 hashed string of username:realm:plaintext password.
+     * 
+     * @return true if authentication succeded and false otherwise.
+     */
+    public boolean doAuthenticateHashedPassword(Request request, String hashedPassword) {
+    	AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
+        if ( authHeader == null ) return false;
+        String realm = authHeader.getRealm();
+        String username = authHeader.getUsername();
+      
+        if ( username == null || realm == null ) {
+            return false;
+        }
+       
+        String nonce = authHeader.getNonce();
+        URI uri = authHeader.getURI();
+        if (uri == null) {
+            return false;
+        }
+        
+
+      
+        String A2 = request.getMethod().toUpperCase() + ":" + uri.toString();
+        String HA1 = hashedPassword;
+
+       
+        byte[] mdbytes = messageDigest.digest(A2.getBytes());
+        String HA2 = toHexString(mdbytes);
+      
+        String cnonce = authHeader.getCNonce();
+        String KD = HA1 + ":" + nonce;
+        if (cnonce != null) {
+            KD += ":" + cnonce;
+        }
+        KD += ":" + HA2;
+        mdbytes = messageDigest.digest(KD.getBytes());
+        String mdString = toHexString(mdbytes);
+        String response = authHeader.getResponse();
+       
+
+        return mdString.equals(response);
+    }
+
+    /**
+     * Authenticate the inbound request given plain text password.
+     *
+     * @param request - the request to authenticate.
+     * @param pass -- the plain text password.
+     * 
+     * @return true if authentication succeded and false otherwise.
+     */
+    public boolean doAuthenticatePlainTextPassword(Request request, String pass) {
+    	AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
+        if ( authHeader == null ) return false;
+        String realm = authHeader.getRealm();
+        String username = authHeader.getUsername();
+      
+   
+        if ( username == null || realm == null ) {
+            return false;
+        }
+        
+
+        String nonce = authHeader.getNonce();
+        URI uri = authHeader.getURI();
+        if (uri == null) {
+           return false;
+        }
+        
+
+        String A1 = username + ":" + realm + ":" + pass;
+        String A2 = request.getMethod().toUpperCase() + ":" + uri.toString();
+        byte mdbytes[] = messageDigest.digest(A1.getBytes());
+        String HA1 = toHexString(mdbytes);
+
+       
+        mdbytes = messageDigest.digest(A2.getBytes());
+        String HA2 = toHexString(mdbytes);
+      
+        String cnonce = authHeader.getCNonce();
+        String KD = HA1 + ":" + nonce;
+        if (cnonce != null) {
+            KD += ":" + cnonce;
+        }
+        KD += ":" + HA2;
+        mdbytes = messageDigest.digest(KD.getBytes());
+        String mdString = toHexString(mdbytes);
+        String response = authHeader.getResponse();
+        return mdString.equals(response);
+        
+    }
+
+}

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

@@ -0,0 +1,126 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+import java.util.Map;
+
+
+public class Device {
+
+	/**
+	 * 设备Id
+	 */
+	private String deviceId;
+
+	/**
+	 * 设备名
+	 */
+	private String name;
+	
+	/**
+	 * 生产厂商
+	 */
+	private String manufacturer;
+	
+	/**
+	 * 型号
+	 */
+	private String model;
+	
+	/**
+	 * 固件版本
+	 */
+	private String firmware;
+
+	/**
+	 * 传输协议
+	 * UDP/TCP
+	 */
+	private String transport;
+
+	/**
+	 * wan地址
+	 */
+	private Host host;
+	
+	/**
+	 * 在线
+	 */
+	private int online;
+
+	/**
+	 * 通道列表
+	 */
+	private Map<String,DeviceChannel> channelMap;
+
+
+	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 getTransport() {
+		return transport;
+	}
+
+	public void setTransport(String transport) {
+		this.transport = transport;
+	}
+
+	public Host getHost() {
+		return host;
+	}
+
+	public void setHost(Host host) {
+		this.host = host;
+	}
+
+	public Map<String, DeviceChannel> getChannelMap() {
+		return channelMap;
+	}
+
+	public void setChannelMap(Map<String, DeviceChannel> channelMap) {
+		this.channelMap = channelMap;
+	}
+
+	public String getManufacturer() {
+		return manufacturer;
+	}
+
+	public void setManufacturer(String manufacturer) {
+		this.manufacturer = manufacturer;
+	}
+
+	public String getModel() {
+		return model;
+	}
+
+	public void setModel(String model) {
+		this.model = model;
+	}
+
+	public String getFirmware() {
+		return firmware;
+	}
+
+	public void setFirmware(String firmware) {
+		this.firmware = firmware;
+	}
+
+	public int getOnline() {
+		return online;
+	}
+
+	public void setOnline(int online) {
+		this.online = online;
+	}
+}

+ 110 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarm.java

@@ -0,0 +1,110 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+public class DeviceAlarm {
+
+	/**
+	 * 设备Id
+	 */
+	private String deviceId;
+
+	/**
+	 * 报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级 警情-
+	 */
+	private String alarmPriorit;
+
+	/**
+	 * 报警方式 , 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警,
+	 * 7其他报警;可以为直接组合如12为电话报警或 设备报警-
+	 */
+	private String alarmMethod;
+
+	/**
+	 * 报警时间
+	 */
+	private String alarmTime;
+
+	/**
+	 * 报警内容描述
+	 */
+	private String alarmDescription;
+
+	/**
+	 * 经度
+	 */
+	private double longitude;
+
+	/**
+	 * 纬度
+	 */
+	private double latitude;
+
+	/**
+	 * 报警类型
+	 */
+	private String alarmType;
+
+	public String getDeviceId() {
+		return deviceId;
+	}
+
+	public void setDeviceId(String deviceId) {
+		this.deviceId = deviceId;
+	}
+
+	public String getAlarmPriorit() {
+		return alarmPriorit;
+	}
+
+	public void setAlarmPriorit(String alarmPriorit) {
+		this.alarmPriorit = alarmPriorit;
+	}
+
+	public String getAlarmMethod() {
+		return alarmMethod;
+	}
+
+	public void setAlarmMethod(String alarmMethod) {
+		this.alarmMethod = alarmMethod;
+	}
+
+	public String getAlarmTime() {
+		return alarmTime;
+	}
+
+	public void setAlarmTime(String alarmTime) {
+		this.alarmTime = alarmTime;
+	}
+
+	public String getAlarmDescription() {
+		return alarmDescription;
+	}
+
+	public void setAlarmDescription(String alarmDescription) {
+		this.alarmDescription = alarmDescription;
+	}
+
+	public double getLongitude() {
+		return longitude;
+	}
+
+	public void setLongitude(double longitude) {
+		this.longitude = longitude;
+	}
+
+	public double getLatitude() {
+		return latitude;
+	}
+
+	public void setLatitude(double latitude) {
+		this.latitude = latitude;
+	}
+
+	public String getAlarmType() {
+		return alarmType;
+	}
+
+	public void setAlarmType(String alarmType) {
+		this.alarmType = alarmType;
+	}
+
+}

+ 309 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java

@@ -0,0 +1,309 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+public class DeviceChannel {
+
+	/**
+	 * 通道id
+	 */
+	private String channelId;
+	
+	/**
+	 * 通道名
+	 */
+	private String name;
+	
+	/**
+	 * 生产厂商
+	 */
+	private String manufacture;
+	
+	/**
+	 * 型号
+	 */
+	private String model;
+	
+	/**
+	 * 设备归属
+	 */
+	private String owner;
+	
+	/**
+	 * 行政区域
+	 */
+	private String civilCode;
+	
+	/**
+	 * 警区
+	 */
+	private String block;
+
+	/**
+	 * 安装地址
+	 */
+	private String address;
+	
+	/**
+	 * 是否有子设备 1有, 0没有
+	 */
+	private int parental;
+	
+	/**
+	 * 父级id
+	 */
+	private String parentId;
+	
+	/**
+	 * 信令安全模式  缺省为0; 0:不采用; 2: S/MIME签名方式; 3: S/ MIME加密签名同时采用方式; 4:数字摘要方式
+	 */
+	private int safetyWay;
+	
+	/**
+	 * 注册方式 缺省为1;1:符合IETFRFC3261标准的认证注册模 式; 2:基于口令的双向认证注册模式; 3:基于数字证书的双向认证注册模式
+	 */
+	private int registerWay;
+	
+	/**
+	 * 证书序列号
+	 */
+	private String certNum;
+	
+	/**
+	 * 证书有效标识 缺省为0;证书有效标识:0:无效1: 有效
+	 */
+	private int certifiable;
+	
+	/**
+	 * 证书无效原因码
+	 */
+	private int errCode;
+	
+	/**
+	 * 证书终止有效期
+	 */
+	private String endTime;
+	
+	/**
+	 * 保密属性 缺省为0; 0:不涉密, 1:涉密
+	 */
+	private String secrecy;
+	
+	/**
+	 * IP地址
+	 */
+	private String ipAddress;
+	
+	/**
+	 * 端口号
+	 */
+	private int port;
+	
+	/**
+	 * 密码
+	 */
+	private String password;	 
+	
+	/**
+	 * 在线/离线
+	 * 1在线,0离线
+	 * 默认在线
+	 * 信令:
+	 * <Status>ON</Status>
+	 * <Status>OFF</Status>
+	 * 遇到过NVR下的IPC下发信令可以推流, 但是 Status 响应 OFF
+	 */
+	private int status;
+
+	/**
+	 * 经度
+	 */
+	private double longitude;
+	
+	/**
+	 * 纬度
+	 */
+	private double latitude;
+
+	public String getChannelId() {
+		return channelId;
+	}
+
+	public void setChannelId(String channelId) {
+		this.channelId = channelId;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public int getStatus() {
+		return status;
+	}
+
+	public void setStatus(int status) {
+		this.status = status;
+	}
+
+	public String getManufacture() {
+		return manufacture;
+	}
+
+	public void setManufacture(String manufacture) {
+		this.manufacture = manufacture;
+	}
+
+	public String getModel() {
+		return model;
+	}
+
+	public void setModel(String model) {
+		this.model = model;
+	}
+
+	public String getOwner() {
+		return owner;
+	}
+
+	public void setOwner(String owner) {
+		this.owner = owner;
+	}
+
+	public String getCivilCode() {
+		return civilCode;
+	}
+
+	public void setCivilCode(String civilCode) {
+		this.civilCode = civilCode;
+	}
+
+	public String getBlock() {
+		return block;
+	}
+
+	public void setBlock(String block) {
+		this.block = block;
+	}
+
+	public String getAddress() {
+		return address;
+	}
+
+	public void setAddress(String address) {
+		this.address = address;
+	}
+
+	public int getParental() {
+		return parental;
+	}
+
+	public void setParental(int parental) {
+		this.parental = parental;
+	}
+
+	public String getParentId() {
+		return parentId;
+	}
+
+	public void setParentId(String parentId) {
+		this.parentId = parentId;
+	}
+
+	public int getSafetyWay() {
+		return safetyWay;
+	}
+
+	public void setSafetyWay(int safetyWay) {
+		this.safetyWay = safetyWay;
+	}
+
+	public int getRegisterWay() {
+		return registerWay;
+	}
+
+	public void setRegisterWay(int registerWay) {
+		this.registerWay = registerWay;
+	}
+
+	public String getCertNum() {
+		return certNum;
+	}
+
+	public void setCertNum(String certNum) {
+		this.certNum = certNum;
+	}
+
+	public int getCertifiable() {
+		return certifiable;
+	}
+
+	public void setCertifiable(int certifiable) {
+		this.certifiable = certifiable;
+	}
+
+	public int getErrCode() {
+		return errCode;
+	}
+
+	public void setErrCode(int errCode) {
+		this.errCode = errCode;
+	}
+
+	public String getEndTime() {
+		return endTime;
+	}
+
+	public void setEndTime(String endTime) {
+		this.endTime = endTime;
+	}
+
+	public String getSecrecy() {
+		return secrecy;
+	}
+
+	public void setSecrecy(String secrecy) {
+		this.secrecy = secrecy;
+	}
+
+	public String getIpAddress() {
+		return ipAddress;
+	}
+
+	public void setIpAddress(String ipAddress) {
+		this.ipAddress = ipAddress;
+	}
+
+	public int getPort() {
+		return port;
+	}
+
+	public void setPort(int port) {
+		this.port = port;
+	}
+
+	public String getPassword() {
+		return password;
+	}
+
+	public void setPassword(String password) {
+		this.password = password;
+	}
+
+	public double getLongitude() {
+		return longitude;
+	}
+
+	public void setLongitude(double longitude) {
+		this.longitude = longitude;
+	}
+
+	public double getLatitude() {
+		return latitude;
+	}
+
+	public void setLatitude(double latitude) {
+		this.latitude = latitude;
+	}
+}

+ 30 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Host.java

@@ -0,0 +1,30 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+public class Host {
+	
+	private String ip;
+	private int port;
+	private String address;
+	
+	public String getIp() {
+		return ip;
+	}
+	public void setIp(String ip) {
+		this.ip = ip;
+	}
+	public int getPort() {
+		return port;
+	}
+	public void setPort(int port) {
+		this.port = port;
+	}
+	public String getAddress() {
+		return address;
+	}
+	public void setAddress(String address) {
+		this.address = address;
+	}
+	
+	
+
+}

+ 34 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java

@@ -0,0 +1,34 @@
+package com.genersoft.iot.vmp.gb28181.event;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Component;
+
+import com.genersoft.iot.vmp.gb28181.event.online.OnlineEvent;
+import com.genersoft.iot.vmp.gb28181.event.outline.OutlineEvent;
+
+/**    
+ * @Description:Event事件通知推送器,支持推送在线事件、离线事件
+ * @author: songww
+ * @date:   2020年5月6日 上午11:30:50     
+ */
+@Component
+public class EventPublisher {
+
+	@Autowired
+    private ApplicationEventPublisher applicationEventPublisher;
+	
+	public void onlineEventPublish(String deviceId, String from) {
+		OnlineEvent onEvent = new OnlineEvent(this);
+		onEvent.setDeviceId(deviceId);
+		onEvent.setFrom(from);
+        applicationEventPublisher.publishEvent(onEvent);
+    }
+	
+	public void outlineEventPublish(String deviceId, String from){
+		OutlineEvent outEvent = new OutlineEvent(this);
+		outEvent.setDeviceId(deviceId);
+		outEvent.setFrom(from);
+        applicationEventPublisher.publishEvent(outEvent);
+    }
+}

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

@@ -0,0 +1,42 @@
+package com.genersoft.iot.vmp.gb28181.event.online;
+
+import org.springframework.context.ApplicationEvent;
+
+/**    
+ * @Description:TODO(这里用一句话描述这个类的作用)   
+ * @author: songww
+ * @date:   2020年5月6日 上午11:32:56     
+ */
+public class OnlineEvent extends ApplicationEvent {
+
+	/**   
+	 * @Title:  OnlineEvent   
+	 * @Description:    TODO(这里用一句话描述这个方法的作用)   
+	 * @param:  @param source  
+	 * @throws   
+	 */  
+	public OnlineEvent(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;
+	}
+	
+}

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

@@ -0,0 +1,66 @@
+package com.genersoft.iot.vmp.gb28181.event.online;
+
+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.IVideoManagerStorager;
+import com.genersoft.iot.vmp.utils.redis.RedisUtil;
+
+/**
+ * @Description: 在线事件监听器,监听到离线后,修改设备离在线状态。 设备在线有两个来源:
+ *               1、设备主动注销,发送注销指令,{@link com.genersoft.iot.vmp.gb28181.transmit.request.impl.RegisterRequestProcessor}
+ *               2、设备未知原因离线,心跳超时,{@link com.genersoft.iot.vmp.gb28181.transmit.request.impl.MessageRequestProcessor}
+ * @author: songww
+ * @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 IVideoManagerStorager storager;
+	
+	@Autowired
+    private RedisUtil redis;
+
+	@Override
+	public void onApplicationEvent(OnlineEvent event) {
+		
+		if (logger.isDebugEnabled()) {
+			logger.debug("设备离线事件触发,deviceId:" + event.getDeviceId() + ",from:" + event.getFrom());
+		}
+		
+		String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + event.getDeviceId();
+		boolean needUpdateStorager = false;
+
+		switch (event.getFrom()) {
+		// 注册时触发的在线事件,先在redis中增加超时超时监听
+		case VideoManagerConstants.EVENT_ONLINE_REGISTER:
+			// TODO 超时时间暂时写死为180秒
+			redis.set(key, event.getDeviceId(), 180);
+			needUpdateStorager = true;
+			break;
+		// 设备主动发送心跳触发的离线事件
+		case VideoManagerConstants.EVENT_ONLINE_KEEPLIVE:
+			boolean exist = redis.hasKey(key);
+			// 先判断是否还存在,当设备先心跳超时后又发送心跳时,redis没有监听,需要增加
+			if (!exist) {
+				needUpdateStorager = true;
+				redis.set(key, event.getDeviceId(), 180);
+			} else {
+				redis.expire(key, 180);
+			}
+			break;
+		}
+		
+		if (needUpdateStorager) {
+			// 处理离线监听
+			storager.online(event.getDeviceId());
+		}
+	}
+}

+ 44 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/event/outline/KeepliveTimeoutListener.java

@@ -0,0 +1,44 @@
+package com.genersoft.iot.vmp.gb28181.event.outline;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.connection.Message;
+import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
+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: songww
+ * @date:   2020年5月6日 上午11:35:46     
+ */
+@Component
+public class KeepliveTimeoutListener extends KeyExpirationEventMessageListener {
+
+	@Autowired
+	private EventPublisher publisher;
+
+	public KeepliveTimeoutListener(RedisMessageListenerContainer listenerContainer) {
+		super(listenerContainer);
+	}
+
+	/**
+     * 监听失效的key,key格式为keeplive_deviceId
+     * @param message
+     * @param pattern
+     */
+    @Override
+    public void onMessage(Message message, byte[] pattern) {
+        //  获取失效的key
+        String expiredKey = message.toString();
+        if(!expiredKey.startsWith(VideoManagerConstants.KEEPLIVEKEY_PREFIX)){
+        	System.out.println("收到redis过期监听,但开头不是"+VideoManagerConstants.KEEPLIVEKEY_PREFIX+",忽略");
+        	return;
+        }
+        
+        String deviceId = expiredKey.substring(VideoManagerConstants.KEEPLIVEKEY_PREFIX.length(),expiredKey.length());
+        publisher.outlineEventPublish(deviceId, VideoManagerConstants.EVENT_OUTLINE_TIMEOUT);
+    }
+}

+ 41 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/event/outline/OutlineEvent.java

@@ -0,0 +1,41 @@
+package com.genersoft.iot.vmp.gb28181.event.outline;
+
+import org.springframework.context.ApplicationEvent;
+
+/**    
+ * @Description:TODO(这里用一句话描述这个类的作用)   
+ * @author: songww
+ * @date:   2020年5月6日 上午11:33:13     
+ */
+public class OutlineEvent extends ApplicationEvent {
+	
+	/**   
+	 * @Title:  OutlineEvent   
+	 * @Description:    TODO(这里用一句话描述这个方法的作用)   
+	 * @param:  @param source  
+	 * @throws   
+	 */  
+	public OutlineEvent(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;
+	}
+}

+ 58 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/event/outline/OutlineEventListener.java

@@ -0,0 +1,58 @@
+package com.genersoft.iot.vmp.gb28181.event.outline;
+
+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.IVideoManagerStorager;
+import com.genersoft.iot.vmp.utils.redis.RedisUtil;
+
+/**
+ * @Description: 离线事件监听器,监听到离线后,修改设备离在线状态。 设备离线有两个来源:
+ *               1、设备主动注销,发送注销指令,{@link com.genersoft.iot.vmp.gb28181.transmit.request.impl.RegisterRequestProcessor}
+ *               2、设备未知原因离线,心跳超时,{@link com.genersoft.iot.vmp.gb28181.event.outline.OutlineEventListener}
+ * @author: songww
+ * @date: 2020年5月6日 下午1:51:23
+ */
+@Component
+public class OutlineEventListener implements ApplicationListener<OutlineEvent> {
+
+	private final static Logger logger = LoggerFactory.getLogger(OutlineEventListener.class);
+	
+	@Autowired
+	private IVideoManagerStorager storager;
+	
+	@Autowired
+    private RedisUtil redis;
+
+	@Override
+	public void onApplicationEvent(OutlineEvent event) {
+		
+		if (logger.isDebugEnabled()) {
+			logger.debug("设备离线事件触发,deviceId:" + event.getDeviceId() + ",from:" + event.getFrom());
+		}
+
+		String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + 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);
+			}
+		}
+
+		// 处理离线监听
+		storager.outline(event.getDeviceId());
+	}
+}

+ 110 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java

@@ -0,0 +1,110 @@
+package com.genersoft.iot.vmp.gb28181.transmit;
+
+import javax.sip.RequestEvent;
+import javax.sip.ResponseEvent;
+import javax.sip.header.CSeqHeader;
+import javax.sip.message.Request;
+import javax.sip.message.Response;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
+import com.genersoft.iot.vmp.gb28181.transmit.request.impl.AckRequestProcessor;
+import com.genersoft.iot.vmp.gb28181.transmit.request.impl.ByeRequestProcessor;
+import com.genersoft.iot.vmp.gb28181.transmit.request.impl.CancelRequestProcessor;
+import com.genersoft.iot.vmp.gb28181.transmit.request.impl.InviteRequestProcessor;
+import com.genersoft.iot.vmp.gb28181.transmit.request.impl.MessageRequestProcessor;
+import com.genersoft.iot.vmp.gb28181.transmit.request.impl.OtherRequestProcessor;
+import com.genersoft.iot.vmp.gb28181.transmit.request.impl.RegisterRequestProcessor;
+import com.genersoft.iot.vmp.gb28181.transmit.request.impl.SubscribeRequestProcessor;
+import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor;
+import com.genersoft.iot.vmp.gb28181.transmit.response.impl.ByeResponseProcessor;
+import com.genersoft.iot.vmp.gb28181.transmit.response.impl.CancelResponseProcessor;
+import com.genersoft.iot.vmp.gb28181.transmit.response.impl.InviteResponseProcessor;
+import com.genersoft.iot.vmp.gb28181.transmit.response.impl.OtherResponseProcessor;
+
+/**    
+ * @Description:TODO(这里用一句话描述这个类的作用)   
+ * @author: songww
+ * @date:   2020年5月3日 下午4:24:37     
+ */
+@Component
+public class SIPProcessorFactory {
+	
+	@Autowired
+	private InviteRequestProcessor inviteRequestProcessor;
+	
+	@Autowired
+	private RegisterRequestProcessor registerRequestProcessor;
+	
+	@Autowired
+	private SubscribeRequestProcessor subscribeRequestProcessor;
+	
+	@Autowired
+	private AckRequestProcessor ackRequestProcessor;
+	
+	@Autowired
+	private ByeRequestProcessor byeRequestProcessor;
+	
+	@Autowired
+	private CancelRequestProcessor cancelRequestProcessor;
+	
+	@Autowired
+	private MessageRequestProcessor messageRequestProcessor;
+	
+	@Autowired
+	private OtherRequestProcessor otherRequestProcessor;
+	
+	@Autowired
+	private InviteResponseProcessor inviteResponseProcessor;
+	
+	@Autowired
+	private ByeResponseProcessor byeResponseProcessor;
+	
+	@Autowired
+	private CancelResponseProcessor cancelResponseProcessor;
+	
+	@Autowired
+	private OtherResponseProcessor otherResponseProcessor;
+	
+	
+	public ISIPRequestProcessor createRequestProcessor(RequestEvent evt) {
+		Request request = evt.getRequest();
+		String method = request.getMethod();
+		
+		if (Request.INVITE.equals(method)) {
+			return inviteRequestProcessor;
+		} else if (Request.REGISTER.equals(method)) {
+			return registerRequestProcessor;
+		} else if (Request.SUBSCRIBE.equals(method)) {
+			return subscribeRequestProcessor;
+		} else if (Request.ACK.equals(method)) {
+			return ackRequestProcessor;
+		} else if (Request.BYE.equals(method)) {
+			return byeRequestProcessor;
+		} else if (Request.CANCEL.equals(method)) {
+			return cancelRequestProcessor;
+		} else if (Request.MESSAGE.equals(method)) {
+			return messageRequestProcessor;
+		} else {
+			return otherRequestProcessor;
+		}
+	}
+	
+	public ISIPResponseProcessor createResponseProcessor(ResponseEvent evt) {
+		Response response = evt.getResponse();
+		CSeqHeader cseqHeader = (CSeqHeader) response.getHeader(CSeqHeader.NAME);
+		String method = cseqHeader.getMethod();
+		if(Request.INVITE.equals(method)){
+			return inviteResponseProcessor;
+		} else if (Request.BYE.equals(method)) {
+			return byeResponseProcessor;
+		} else if (Request.CANCEL.equals(method)) {
+			return cancelResponseProcessor;
+		} else {
+			return otherResponseProcessor;
+		}
+	}
+	
+}

+ 183 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java

@@ -0,0 +1,183 @@
+package com.genersoft.iot.vmp.gb28181.transmit.cmd;
+
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+
+/**    
+ * @Description:设备能力接口,用于定义设备的控制、查询能力   
+ * @author: songww
+ * @date:   2020年5月3日 下午9:16:34     
+ */
+public interface ISIPCommander {
+
+	/**
+	 * 云台方向放控制,使用配置文件中的默认镜头移动速度
+	 * 
+	 * @param deviceId  控制设备
+	 * @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);
+	
+	/**
+	 * 云台方向放控制
+	 * 
+	 * @param deviceId  控制设备
+	 * @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);
+	
+	/**
+	 * 云台缩放控制,使用配置文件中的默认镜头缩放速度
+	 * 
+	 * @param deviceId  控制设备
+	 * @param channelId  预览通道
+     * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
+	 */
+	public boolean ptzZoomCmd(String deviceId,String channelId,int inOut);
+	
+	/**
+	 * 云台缩放控制
+	 * 
+	 * @param deviceId  控制设备
+	 * @param channelId  预览通道
+     * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
+     * @param zoomSpeed  镜头缩放速度
+	 */
+	public boolean ptzZoomCmd(String deviceId,String channelId,int inOut, int moveSpeed);
+	
+	/**
+	 * 云台控制,支持方向与缩放控制
+	 * 
+	 * @param deviceId  控制设备
+	 * @param channelId  预览通道
+	 * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
+     * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
+     * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
+     * @param moveSpeed  镜头移动速度
+     * @param zoomSpeed  镜头缩放速度
+	 */
+	public boolean ptzCmd(String deviceId,String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed);
+	
+	/**
+	 * 请求预览视频流
+	 * 
+	 * @param deviceId  视频设备
+	 * @param channelId  预览通道
+	 */
+	public String playStreamCmd(String deviceId,String channelId);
+	
+	/**
+	 * 语音广播
+	 * 
+	 * @param deviceId  视频设备
+	 * @param channelId  预览通道
+	 */
+	public String audioBroadcastCmd(String deviceId,String channelId);
+	
+	/**
+	 * 音视频录像控制
+	 * 
+	 * @param deviceId  视频设备
+	 * @param channelId  预览通道
+	 */
+	public String recordCmd(String deviceId,String channelId);
+	
+	/**
+	 * 报警布防/撤防命令
+	 * 
+	 * @param deviceId  视频设备
+	 */
+	public String guardCmd(String deviceId);
+	
+	/**
+	 * 报警复位命令
+	 * 
+	 * @param deviceId  视频设备
+	 */
+	public String alarmCmd(String deviceId);
+	
+	/**
+	 * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
+	 * 
+	 * @param deviceId  视频设备
+	 * @param channelId  预览通道
+	 */
+	public String iFameCmd(String deviceId,String channelId);
+	
+	/**
+	 * 看守位控制命令
+	 * 
+	 * @param deviceId  视频设备
+	 */
+	public String homePositionCmd(String deviceId);
+	
+	/**
+	 * 设备配置命令
+	 * 
+	 * @param deviceId  视频设备
+	 */
+	public String deviceConfigCmd(String deviceId);
+	
+	
+	/**
+	 * 查询设备状态
+	 * 
+	 * @param device 视频设备
+	 */
+	public boolean deviceStatusQuery(Device device);
+	
+	/**
+	 * 查询设备信息
+	 * 
+	 * @param device 视频设备
+	 * @return 
+	 */
+	public boolean deviceInfoQuery(Device device);
+	
+	/**
+	 * 查询目录列表
+	 * 
+	 * @param device 视频设备
+	 */
+	public boolean catalogQuery(Device device);
+	
+	/**
+	 * 查询录像信息
+	 * 
+	 * @param device 视频设备
+	 */
+	public boolean recordInfoQuery(Device device);
+	
+	/**
+	 * 查询报警信息
+	 * 
+	 * @param device 视频设备
+	 */
+	public boolean alarmInfoQuery(Device device);
+	
+	/**
+	 * 查询设备配置
+	 * 
+	 * @param device 视频设备
+	 */
+	public boolean configQuery(Device device);
+	
+	/**
+	 * 查询设备预置位置
+	 * 
+	 * @param device 视频设备
+	 */
+	public boolean presetQuery(Device device);
+	
+	/**
+	 * 查询移动设备位置数据
+	 * 
+	 * @param device 视频设备
+	 */
+	public boolean mobilePostitionQuery(Device device);
+}

+ 111 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java

@@ -0,0 +1,111 @@
+package com.genersoft.iot.vmp.gb28181.transmit.cmd;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.address.Address;
+import javax.sip.address.SipURI;
+import javax.sip.header.CSeqHeader;
+import javax.sip.header.CallIdHeader;
+import javax.sip.header.ContentTypeHeader;
+import javax.sip.header.FromHeader;
+import javax.sip.header.MaxForwardsHeader;
+import javax.sip.header.ToHeader;
+import javax.sip.header.ViaHeader;
+import javax.sip.message.Request;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.gb28181.SipLayer;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.Host;
+
+/**
+ * @Description:TODO(这里用一句话描述这个类的作用)
+ * @author: songww
+ * @date: 2020年5月6日 上午9:29:02
+ */
+@Component
+public class SIPRequestHeaderProvider {
+
+	@Autowired
+	private SipLayer layer;
+
+	@Autowired
+	private SipConfig config;
+	
+	public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag) throws ParseException, InvalidArgumentException {
+		Request request = null;
+		Host host = device.getHost();
+		// sipuri
+		SipURI requestURI = layer.getAddressFactory().createSipURI(device.getDeviceId(), host.getAddress());
+		// via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(config.getSipIp(), config.getSipPort(),
+				device.getTransport(), viaTag);
+		viaHeaders.add(viaHeader);
+		// from
+		SipURI fromSipURI = layer.getAddressFactory().createSipURI(device.getDeviceId(),
+				config.getSipIp() + ":" + config.getSipPort());
+		Address fromAddress = layer.getAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = layer.getHeaderFactory().createFromHeader(fromAddress, fromTag);
+		// to
+		SipURI toSipURI = layer.getAddressFactory().createSipURI(device.getDeviceId(), host.getAddress());
+		Address toAddress = layer.getAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = layer.getHeaderFactory().createToHeader(toAddress, toTag);
+		// callid
+		CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? layer.getTcpSipProvider().getNewCallId()
+				: layer.getUdpSipProvider().getNewCallId();
+		// Forwards
+		MaxForwardsHeader maxForwards = layer.getHeaderFactory().createMaxForwardsHeader(70);
+		// ceq
+		CSeqHeader cSeqHeader = layer.getHeaderFactory().createCSeqHeader(1L, Request.MESSAGE);
+
+		request = layer.getMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
+				toHeader, viaHeaders, maxForwards);
+		ContentTypeHeader contentTypeHeader = layer.getHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
+		request.setContent(content, contentTypeHeader);
+		return request;
+	}
+	
+	public Request createInviteRequest(Device device, String content, String viaTag, String fromTag, String toTag) throws ParseException, InvalidArgumentException {
+		Request request = null;
+		Host host = device.getHost();
+		//请求行
+		SipURI requestLine = layer.getAddressFactory().createSipURI(device.getDeviceId(), host.getAddress());
+		//via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(config.getSipIp(), config.getSipPort(), device.getTransport(), viaTag);
+		viaHeader.setRPort();
+		viaHeaders.add(viaHeader);
+		//from
+		SipURI fromSipURI = layer.getAddressFactory().createSipURI(device.getDeviceId(),config.getSipIp()+":"+config.getSipPort());
+		Address fromAddress = layer.getAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = layer.getHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
+		//to
+		SipURI toSipURI = layer.getAddressFactory().createSipURI(device.getDeviceId(),host.getAddress()); 
+		Address toAddress = layer.getAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = layer.getHeaderFactory().createToHeader(toAddress,null);
+
+		//callid
+		CallIdHeader callIdHeader = null;
+		if(device.getTransport().equals("TCP")) {
+			callIdHeader = layer.getTcpSipProvider().getNewCallId();
+		}
+		if(device.getTransport().equals("UDP")) {
+			callIdHeader = layer.getUdpSipProvider().getNewCallId();
+		}
+		
+		//Forwards
+		MaxForwardsHeader maxForwards = layer.getHeaderFactory().createMaxForwardsHeader(70);
+		//ceq
+		CSeqHeader cSeqHeader = layer.getHeaderFactory().createCSeqHeader(1L, Request.INVITE);
+		request = layer.getMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+		ContentTypeHeader contentTypeHeader = layer.getHeaderFactory().createContentTypeHeader("Application", "SDP");
+		request.setContent(content, contentTypeHeader);
+		return request;
+	}
+}

+ 48 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/PtzCmdHelper.java

@@ -0,0 +1,48 @@
+package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
+
+public class PtzCmdHelper {
+   /**
+    *
+    * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
+    * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
+    * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
+    * @param moveSpeed  镜头移动速度 默认 0XFF (0-255)
+    * @param zoomSpeed  镜头缩放速度 默认 0X1 (0-255)
+    * @return
+    */
+	//云台控制发送了消息,相机会一直执行,直到其他命令或者发送了停止命令,切记要考虑这个机制
+    public static String create(int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed) {
+        int cmdCode = 0;
+        if (leftRight == 2) cmdCode|=0x01;      // 右移
+        else if(leftRight == 1) cmdCode|=0x02;  // 左移
+        if (upDown == 2) cmdCode|=0x04;         // 下移
+        else if(upDown == 1) cmdCode|=0x08;     // 上移
+        if (inOut == 2) cmdCode |= 0x10;        // 放大
+        else if(inOut == 1) cmdCode |= 0x20;    // 缩小
+        char[] szCmd = new char[16];
+        String strTmp;
+        szCmd[0] = 'A'; //字节1 A5
+        szCmd[1] = '5';
+        szCmd[2] = '0'; //字节2 0F
+        szCmd[3] = 'F';
+        szCmd[4] = '0'; //字节3 地址的低8位
+        szCmd[5] = '1';
+        strTmp = String.format("%02X", cmdCode);
+        szCmd[6]  = strTmp.charAt(0); //字节4 控制码
+        szCmd[7]  = strTmp.charAt(1);
+        strTmp = String.format("%02X", moveSpeed);
+        szCmd[8]  = strTmp.charAt(0); //字节5 水平控制速度
+        szCmd[9]  = strTmp.charAt(1);
+        szCmd[10] = strTmp.charAt(0); //字节6 垂直控制速度
+        szCmd[11] = strTmp.charAt(1);
+        strTmp = String.format("%X", zoomSpeed);
+        szCmd[12] = strTmp.charAt(0); //字节7高4位 缩放控制速度
+        szCmd[13] = '0';              //字节7低4位 地址的高4位
+        //计算校验码
+        int nCheck = (0XA5 + 0X0F + 0X01 + cmdCode + moveSpeed + moveSpeed + (zoomSpeed << 4 & 0XF0)) % 0X100;
+        strTmp = String.format("%02X", nCheck);
+        szCmd[14] = strTmp.charAt(0); //字节8 校验码
+        szCmd[15] = strTmp.charAt(1);
+        return String.valueOf(szCmd);
+    }
+}

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

@@ -0,0 +1,387 @@
+package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
+
+import java.text.ParseException;
+import java.util.Random;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import javax.sip.message.Request;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.genersoft.iot.vmp.conf.SipConfig;
+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;
+
+/**    
+ * @Description:设备能力接口,用于定义设备的控制、查询能力   
+ * @author: songww
+ * @date:   2020年5月3日 下午9:22:48     
+ */
+@Component
+public class SIPCommander implements ISIPCommander {
+	
+	@Autowired
+	private SipConfig config;
+	
+	@Autowired
+	private SIPRequestHeaderProvider headerProvider;
+	
+	@Autowired
+	private SipLayer sipLayer;
+	
+	@Autowired
+	private IVideoManagerStorager storager;
+	 
+	/**
+	 * 云台方向放控制,使用配置文件中的默认镜头移动速度
+	 * 
+	 * @param deviceId  控制设备
+	 * @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);
+	}
+
+	/**
+	 * 云台方向放控制
+	 * 
+	 * @param deviceId  控制设备
+	 * @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);
+	}
+
+	/**
+	 * 云台缩放控制,使用配置文件中的默认镜头缩放速度
+	 * 
+	 * @param deviceId  控制设备
+	 * @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());
+	}
+
+	/**
+	 * 云台缩放控制
+	 * 
+	 * @param deviceId  控制设备
+	 * @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);
+	}
+  
+	/**
+	 * 云台控制,支持方向与缩放控制
+	 * 
+	 * @param deviceId  控制设备
+	 * @param channelId  预览通道
+	 * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
+     * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
+     * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
+     * @param moveSpeed  镜头移动速度
+     * @param zoomSpeed  镜头缩放速度
+	 */
+	@Override
+	public boolean ptzCmd(String deviceId, 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>");
+			ptzXml.append("<CmdType>DeviceControl</CmdType>");
+			ptzXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>");
+			ptzXml.append("<DeviceID>" + channelId + "</DeviceID>");
+			ptzXml.append("<PTZCmd>" + PtzCmdHelper.create(leftRight, upDown, inOut, moveSpeed, zoomSpeed) + "</PTZCmd>");
+			ptzXml.append("<Info>");
+			ptzXml.append("</Info>");
+			ptzXml.append("</Control>");
+			
+			Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), "ViaPtzBranch", "FromPtzTag", "ToPtzTag");
+			
+			transmitRequest(device.getTransport(), request);
+			
+			return true;
+		} catch (SipException | ParseException | InvalidArgumentException e) {
+			e.printStackTrace();
+		} 
+		return false;
+	}
+
+	/**
+	 * 请求预览视频流
+	 * 
+	 * @param deviceId  视频设备
+	 * @param channelId  预览通道
+	 */  
+	@Override
+	public String playStreamCmd(String deviceId, String channelId) {
+		try {
+			
+			Device device = storager.queryVideoDevice(deviceId);
+			
+			//生成ssrc标识数据流 10位数字
+			String ssrc = "";
+			Random random = new Random();
+			// ZLMediaServer最大识别7FFFFFFF即2147483647,所以随机数不能超过这个数
+			ssrc = String.valueOf(random.nextInt(2147483647));
+			//
+			StringBuffer content = new StringBuffer(200);
+	        content.append("v=0\r\n");
+	        content.append("o="+channelId+" 0 0 IN IP4 "+config.getSipIp()+"\r\n");
+	        content.append("s=Play\r\n");
+	        content.append("c=IN IP4 "+config.getMediaIp()+"\r\n");
+	        content.append("t=0 0\r\n");
+	        if(device.getTransport().equals("TCP")) {
+	        	content.append("m=video "+config.getMediaPort()+" TCP/RTP/AVP 96 98 97\r\n");
+			}
+	        if(device.getTransport().equals("UDP")) {
+	        	content.append("m=video "+config.getMediaPort()+" RTP/AVP 96 98 97\r\n");
+			}
+	        content.append("a=sendrecv\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");
+	        if(device.getTransport().equals("TCP")){
+	             content.append("a=setup:passive\r\n");
+	             content.append("a=connection:new\r\n");
+	        }
+	        content.append("y="+ssrc+"\r\n");//ssrc
+	        
+	        Request request = headerProvider.createInviteRequest(device, content.toString(), null, "live", null);
+	
+	        transmitRequest(device.getTransport(), request);
+			return ssrc;
+		} catch ( SipException | ParseException | InvalidArgumentException e) {
+			e.printStackTrace();
+			return null;
+		} 
+	}
+
+	/**
+	 * 语音广播
+	 * 
+	 * @param deviceId  视频设备
+	 * @param channelId  预览通道
+	 */
+	@Override
+	public String audioBroadcastCmd(String deviceId, String channelId) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	/**
+	 * 音视频录像控制
+	 * 
+	 * @param deviceId  视频设备
+	 * @param channelId  预览通道
+	 */  
+	@Override
+	public String recordCmd(String deviceId, String channelId) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	/**
+	 * 报警布防/撤防命令
+	 * 
+	 * @param deviceId  视频设备
+	 */  
+	@Override
+	public String guardCmd(String deviceId) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	/**
+	 * 报警复位命令
+	 * 
+	 * @param deviceId  视频设备
+	 */  
+	@Override
+	public String alarmCmd(String deviceId) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	/**
+	 * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
+	 * 
+	 * @param deviceId  视频设备
+	 * @param channelId  预览通道
+	 */ 
+	@Override
+	public String iFameCmd(String deviceId, String channelId) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	/**
+	 * 看守位控制命令
+	 * 
+	 * @param deviceId  视频设备
+	 */  
+	@Override
+	public String homePositionCmd(String deviceId) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	/**
+	 * 设备配置命令
+	 * 
+	 * @param deviceId  视频设备
+	 */  
+	@Override
+	public String deviceConfigCmd(String deviceId) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	/**
+	 * 查询设备状态
+	 * 
+	 * @param device 视频设备
+	 */  
+	@Override
+	public boolean deviceStatusQuery(Device device) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+	/**
+	 * 查询设备信息
+	 * 
+	 * @param device 视频设备
+	 */  
+	@Override
+	public boolean deviceInfoQuery(Device device) {
+		try {
+			StringBuffer catalogXml = new StringBuffer(200);
+			catalogXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>");
+			catalogXml.append("<Query>");
+			catalogXml.append("<CmdType>DeviceInfo</CmdType>");
+			catalogXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>");
+			catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>");
+			catalogXml.append("</Query>");
+			
+			Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaDeviceInfoBranch", "FromDeviceInfoTag", "ToDeviceInfoTag");
+			
+			transmitRequest(device.getTransport(), request);
+		} catch (SipException | ParseException | InvalidArgumentException e) {
+			e.printStackTrace();
+			return false;
+		}
+		return true;
+	}
+
+	/**
+	 * 查询目录列表
+	 * 
+	 * @param device 视频设备
+	 */ 
+	@Override
+	public boolean catalogQuery(Device device) {
+		try {
+			StringBuffer catalogXml = new StringBuffer(200);
+			catalogXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>");
+			catalogXml.append("<Query>");
+			catalogXml.append("<CmdType>Catalog</CmdType>");
+			catalogXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>");
+			catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>");
+			catalogXml.append("</Query>");
+			
+			Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaCatalogBranch", "FromCatalogTag", "ToCatalogTag");
+			
+			transmitRequest(device.getTransport(), request);
+
+		} catch (SipException | ParseException | InvalidArgumentException e) {
+			e.printStackTrace();
+			return false;
+		}
+		return true;
+	}
+
+	/**
+	 * 查询录像信息
+	 * 
+	 * @param device 视频设备
+	 */  
+	@Override
+	public boolean recordInfoQuery(Device device) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+	/**
+	 * 查询报警信息
+	 * 
+	 * @param device 视频设备
+	 */  
+	@Override
+	public boolean alarmInfoQuery(Device device) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+	/**
+	 * 查询设备配置
+	 * 
+	 * @param device 视频设备
+	 */  
+	@Override
+	public boolean configQuery(Device device) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+	/**
+	 * 查询设备预置位置
+	 * 
+	 * @param device 视频设备
+	 */  
+	@Override
+	public boolean presetQuery(Device device) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+	/**
+	 * 查询移动设备位置数据
+	 * 
+	 * @param device 视频设备
+	 */  
+	@Override
+	public boolean mobilePostitionQuery(Device device) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+	
+	private void transmitRequest(String transport, Request request) throws SipException {
+		if(transport.equals("TCP")) {
+			sipLayer.getTcpSipProvider().sendRequest(request);
+		} else if(transport.equals("UDP")) {
+			sipLayer.getUdpSipProvider().sendRequest(request);
+		}
+	}
+}

+ 17 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/ISIPRequestProcessor.java

@@ -0,0 +1,17 @@
+package com.genersoft.iot.vmp.gb28181.transmit.request;
+
+import javax.sip.RequestEvent;
+import javax.sip.ServerTransaction;
+
+import com.genersoft.iot.vmp.gb28181.SipLayer;
+
+/**    
+ * @Description:处理接收IPCamera发来的SIP协议请求消息
+ * @author: songww
+ * @date:   2020年5月3日 下午4:42:22     
+ */
+public interface ISIPRequestProcessor {
+
+	public void process(RequestEvent evt, SipLayer layer, ServerTransaction transaction);
+
+}

+ 51 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/AckRequestProcessor.java

@@ -0,0 +1,51 @@
+package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
+
+import javax.sip.Dialog;
+import javax.sip.InvalidArgumentException;
+import javax.sip.RequestEvent;
+import javax.sip.ServerTransaction;
+import javax.sip.SipException;
+import javax.sip.message.Request;
+
+import org.springframework.stereotype.Component;
+
+import com.genersoft.iot.vmp.gb28181.SipLayer;
+import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
+
+import gov.nist.javax.sip.header.CSeq;
+
+/**    
+ * @Description:ACK请求处理器  
+ * @author: songww
+ * @date:   2020年5月3日 下午5:31:45     
+ */
+@Component
+public class AckRequestProcessor implements ISIPRequestProcessor {
+	
+	/**   
+	 * 处理  ACK请求
+	 * 
+	 * @param evt
+	 * @param layer
+	 * @param transaction
+	 * @param config    
+	 */  
+	@Override
+	public void process(RequestEvent evt, SipLayer layer, ServerTransaction transaction) {
+		Request request = evt.getRequest();
+		Dialog dialog = evt.getDialog();
+		try {
+			Request ackRequest = null;
+			CSeq csReq = (CSeq) request.getHeader(CSeq.NAME);
+			ackRequest = dialog.createAck(csReq.getSeqNumber());
+			dialog.sendAck(ackRequest);
+			System.out.println("send ack to callee:" + ackRequest.toString());
+		} catch (SipException e) {
+			e.printStackTrace();
+		} catch (InvalidArgumentException e) {
+			e.printStackTrace();
+		}
+		
+	}
+
+}

+ 33 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/ByeRequestProcessor.java

@@ -0,0 +1,33 @@
+package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
+
+import javax.sip.RequestEvent;
+import javax.sip.ServerTransaction;
+
+import org.springframework.stereotype.Component;
+
+import com.genersoft.iot.vmp.gb28181.SipLayer;
+import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
+
+/**    
+ * @Description: BYE请求处理器
+ * @author: songww
+ * @date:   2020年5月3日 下午5:32:05     
+ */
+@Component
+public class ByeRequestProcessor implements ISIPRequestProcessor {
+
+	/**   
+	 * 处理BYE请求
+	 * 
+	 * @param evt
+	 * @param layer
+	 * @param transaction
+	 * @param config    
+	 */  
+	@Override
+	public void process(RequestEvent evt, SipLayer layer, ServerTransaction transaction) {
+		// TODO Auto-generated method stub
+		
+	}
+
+}

+ 33 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/CancelRequestProcessor.java

@@ -0,0 +1,33 @@
+package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
+
+import javax.sip.RequestEvent;
+import javax.sip.ServerTransaction;
+
+import org.springframework.stereotype.Component;
+
+import com.genersoft.iot.vmp.gb28181.SipLayer;
+import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
+
+/**    
+ * @Description:CANCEL请求处理器
+ * @author: songww
+ * @date:   2020年5月3日 下午5:32:23     
+ */
+@Component
+public class CancelRequestProcessor implements ISIPRequestProcessor {
+
+	/**   
+	 * 处理CANCEL请求
+	 *  
+	 * @param evt
+	 * @param layer
+	 * @param transaction
+	 * @param config    
+	 */  
+	@Override
+	public void process(RequestEvent evt, SipLayer layer, ServerTransaction transaction) {
+		// TODO Auto-generated method stub
+		
+	}
+
+}

+ 77 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/InviteRequestProcessor.java

@@ -0,0 +1,77 @@
+package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
+
+import javax.sip.RequestEvent;
+import javax.sip.ServerTransaction;
+
+import org.springframework.stereotype.Component;
+
+import com.genersoft.iot.vmp.gb28181.SipLayer;
+import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
+
+/**    
+ * @Description:处理INVITE请求
+ * @author: songww
+ * @date:   2020年5月3日 下午4:43:52     
+ */
+@Component
+public class InviteRequestProcessor implements ISIPRequestProcessor {
+
+	/**
+	 * 处理invite请求
+	 * 
+	 * @param request
+	 *            请求消息
+	 */ 
+	@Override
+	public void process(RequestEvent evt, SipLayer layer, ServerTransaction transaction) {
+		// TODO Auto-generated method stub
+//		Request request = requestEvent.getRequest();
+//
+//		try {
+//			// 发送100 Trying
+//			ServerTransaction serverTransaction = getServerTransaction(requestEvent);
+//			// 查询目标地址
+//			URI reqUri = request.getRequestURI();
+//			URI contactURI = currUser.get(reqUri);
+//
+//			System.out.println("processInvite rqStr=" + reqUri + " contact=" + contactURI);
+//
+//			// 根据Request uri来路由,后续的响应消息通过VIA来路由
+//			Request cliReq = messageFactory.createRequest(request.toString());
+//			cliReq.setRequestURI(contactURI);
+//
+//			HeaderFactory headerFactory = SipFactory.getInstance().createHeaderFactory();
+//			Via callerVia = (Via) request.getHeader(Via.NAME);
+//			Via via = (Via) headerFactory.createViaHeader(SIPMain.ip, SIPMain.port, "UDP",
+//					callerVia.getBranch() + "sipphone");
+//
+//			// FIXME 需要测试是否能够通过设置VIA头域来修改VIA头域值
+//			cliReq.removeHeader(Via.NAME);
+//			cliReq.addHeader(via);
+//
+//			// 更新contact的地址
+//			ContactHeader contactHeader = headerFactory.createContactHeader();
+//			Address address = SipFactory.getInstance().createAddressFactory()
+//					.createAddress("sip:sipsoft@" + SIPMain.ip + ":" + SIPMain.port);
+//			contactHeader.setAddress(address);
+//			contactHeader.setExpires(3600);
+//			cliReq.setHeader(contactHeader);
+//
+//			clientTransactionId = sipProvider.getNewClientTransaction(cliReq);
+//			clientTransactionId.sendRequest();
+//
+//			System.out.println("processInvite clientTransactionId=" + clientTransactionId.toString());
+//
+//			System.out.println("send invite to callee: " + cliReq);
+//		} catch (TransactionUnavailableException e1) {
+//			e1.printStackTrace();
+//		} catch (SipException e) {
+//			e.printStackTrace();
+//		} catch (ParseException e) {
+//			e.printStackTrace();
+//		} catch (Exception e) {
+//			e.printStackTrace();
+//		}
+	}
+
+}

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

@@ -0,0 +1,239 @@
+package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
+
+import java.io.ByteArrayInputStream;
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.RequestEvent;
+import javax.sip.ServerTransaction;
+import javax.sip.SipException;
+import javax.sip.message.Request;
+import javax.sip.message.Response;
+
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+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.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.gb28181.utils.XmlUtil;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+
+/**    
+ * @Description:MESSAGE请求处理器
+ * @author: songww
+ * @date:   2020年5月3日 下午5:32:41     
+ */
+@Component
+public class MessageRequestProcessor implements ISIPRequestProcessor {
+
+	private ServerTransaction transaction;
+	
+	private SipLayer layer;
+	
+	@Autowired
+	private SIPCommander cmder;
+	
+	@Autowired
+	private IVideoManagerStorager storager;
+	
+	@Autowired
+	private EventPublisher publisher;
+	
+	/**   
+	 * 处理MESSAGE请求
+	 *  
+	 * @param evt
+	 * @param layer
+	 * @param transaction  
+	 */  
+	@Override
+	public void process(RequestEvent evt, SipLayer layer, ServerTransaction transaction) {
+		
+		this.layer = layer;
+		this.transaction = transaction;
+		
+		Request request = evt.getRequest();
+		
+		if (new String(request.getRawContent()).contains("<CmdType>Keepalive</CmdType>")) {
+			processMessageKeepAlive(evt);
+		} else if (new String(request.getRawContent()).contains("<CmdType>Catalog</CmdType>")) {
+			processMessageCatalogList(evt);
+		} else if (new String(request.getRawContent()).contains("<CmdType>DeviceInfo</CmdType>")) {
+			processMessageDeviceInfo(evt);
+		} else if (new String(request.getRawContent()).contains("<CmdType>Alarm</CmdType>")) {
+			processMessageAlarm(evt);
+		}
+		
+	}
+	
+	/***
+	 * 收到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 deviceIdElement = rootElement.element("DeviceID");
+			String deviceId = deviceIdElement.getText().toString();
+			Element deviceListElement = rootElement.element("DeviceList");
+			if (deviceListElement == null) {
+				return;
+			}
+			Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
+			if (deviceListIterator != null) {
+				Device device = storager.queryVideoDevice(deviceId);
+				if (device == null) {
+					return;
+				}
+				Map<String, DeviceChannel> channelMap = device.getChannelMap();
+				if (channelMap == null) {
+					channelMap = new HashMap<String, DeviceChannel>(5);
+					device.setChannelMap(channelMap);
+				}
+				// 遍历DeviceList
+				while (deviceListIterator.hasNext()) {
+					Element itemDevice = deviceListIterator.next();
+					Element channelDeviceElement = itemDevice.element("DeviceID");
+					if (channelDeviceElement == null) {
+						continue;
+					}
+					String channelDeviceId = channelDeviceElement.getText().toString();
+					Element channdelNameElement = itemDevice.element("Name");
+					String channelName = channdelNameElement != null ? channdelNameElement.getText().toString() : "";
+					Element statusElement = itemDevice.element("Status");
+					String status = statusElement != null ? statusElement.getText().toString() : "ON";
+					DeviceChannel deviceChannel = channelMap.containsKey(channelDeviceId) ? channelMap.get(channelDeviceId) : new DeviceChannel();
+					deviceChannel.setName(channelName);
+					deviceChannel.setChannelId(channelDeviceId);
+					if(status.equals("ON")) {
+						deviceChannel.setStatus(1);
+					}
+					if(status.equals("OFF")) {
+						deviceChannel.setStatus(0);
+					}
+
+					deviceChannel.setManufacture(XmlUtil.getText(itemDevice,"Manufacturer"));
+					deviceChannel.setModel(XmlUtil.getText(itemDevice,"Model"));
+					deviceChannel.setOwner(XmlUtil.getText(itemDevice,"Owner"));
+					deviceChannel.setCivilCode(XmlUtil.getText(itemDevice,"CivilCode"));
+					deviceChannel.setBlock(XmlUtil.getText(itemDevice,"Block"));
+					deviceChannel.setAddress(XmlUtil.getText(itemDevice,"Address"));
+					deviceChannel.setParental(itemDevice.element("Parental") == null? 0:Integer.parseInt(XmlUtil.getText(itemDevice,"Parental")));
+					deviceChannel.setParentId(XmlUtil.getText(itemDevice,"ParentId"));
+					deviceChannel.setSafetyWay(itemDevice.element("SafetyWay") == null? 0:Integer.parseInt(XmlUtil.getText(itemDevice,"SafetyWay")));
+					deviceChannel.setRegisterWay(itemDevice.element("RegisterWay") == null? 1:Integer.parseInt(XmlUtil.getText(itemDevice,"RegisterWay")));
+					deviceChannel.setCertNum(XmlUtil.getText(itemDevice,"CertNum"));
+					deviceChannel.setCertifiable(itemDevice.element("Certifiable") == null? 0:Integer.parseInt(XmlUtil.getText(itemDevice,"Certifiable")));
+					deviceChannel.setErrCode(itemDevice.element("ErrCode") == null? 0:Integer.parseInt(XmlUtil.getText(itemDevice,"ErrCode")));
+					deviceChannel.setEndTime(XmlUtil.getText(itemDevice,"EndTime"));
+					deviceChannel.setSecrecy(XmlUtil.getText(itemDevice,"Secrecy"));
+					deviceChannel.setIpAddress(XmlUtil.getText(itemDevice,"IPAddress"));
+					deviceChannel.setPort(itemDevice.element("Port") == null? 0:Integer.parseInt(XmlUtil.getText(itemDevice,"Port")));
+					deviceChannel.setPassword(XmlUtil.getText(itemDevice,"Password"));
+					deviceChannel.setLongitude(itemDevice.element("Longitude") == null? 0.00:Double.parseDouble(XmlUtil.getText(itemDevice,"Longitude")));
+					deviceChannel.setLatitude(itemDevice.element("Latitude") == null? 0.00:Double.parseDouble(XmlUtil.getText(itemDevice,"Latitude")));
+					channelMap.put(channelDeviceId, deviceChannel);
+				}
+				// 更新
+				storager.update(device);
+			}
+		} catch (DocumentException e) {
+			e.printStackTrace();
+		}
+	}
+	
+	/***
+	 * 收到deviceInfo设备信息请求 处理
+	 * @param evt
+	 */
+	private void processMessageDeviceInfo(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 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) {
+			e.printStackTrace();
+		}
+	}
+	
+	/***
+	 * 收到alarm设备报警信息 处理
+	 * @param 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 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) {
+			e.printStackTrace();
+		}
+	}
+	
+	/***
+	 * 收到keepalive请求 处理
+	 * @param evt
+	 */
+	private void processMessageKeepAlive(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();
+			Element deviceIdElement = rootElement.element("DeviceID");
+			transaction.sendResponse(response);
+			publisher.onlineEventPublish(deviceIdElement.getText(), VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
+		} catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
+			e.printStackTrace();
+		}
+	}
+
+}

+ 32 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/OtherRequestProcessor.java

@@ -0,0 +1,32 @@
+package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
+
+import javax.sip.RequestEvent;
+import javax.sip.ServerTransaction;
+
+import org.springframework.stereotype.Component;
+
+import com.genersoft.iot.vmp.gb28181.SipLayer;
+import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
+
+/**    
+ * @Description:暂不支持的消息请求处理器
+ * @author: songww
+ * @date:   2020年5月3日 下午5:32:59     
+ */
+@Component
+public class OtherRequestProcessor implements ISIPRequestProcessor {
+
+	/**   
+	 * <p>Title: process</p>   
+	 * <p>Description: </p>   
+	 * @param evt
+	 * @param layer
+	 * @param transaction
+	 * @param config    
+	 */  
+	@Override
+	public void process(RequestEvent evt, SipLayer layer, ServerTransaction transaction) {
+		System.out.println("no support the method! Method:" + evt.getRequest().getMethod());
+	}
+
+}

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

@@ -0,0 +1,163 @@
+package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
+
+import java.security.NoSuchAlgorithmException;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Locale;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.RequestEvent;
+import javax.sip.ServerTransaction;
+import javax.sip.SipException;
+import javax.sip.header.AuthorizationHeader;
+import javax.sip.header.ContactHeader;
+import javax.sip.header.ExpiresHeader;
+import javax.sip.header.FromHeader;
+import javax.sip.header.ViaHeader;
+import javax.sip.message.Request;
+import javax.sip.message.Response;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+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.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;
+
+import gov.nist.javax.sip.address.AddressImpl;
+import gov.nist.javax.sip.address.SipUri;
+import gov.nist.javax.sip.header.Expires;
+
+/**    
+ * @Description:收到注册请求 处理 
+ * @author: songww
+ * @date:   2020年5月3日 下午4:47:25     
+ */
+@Component
+public class RegisterRequestProcessor implements ISIPRequestProcessor {
+
+	@Autowired
+	private SipConfig config;
+	
+	@Autowired
+	private SIPCommander cmder;
+	
+	@Autowired
+	private IVideoManagerStorager storager;
+	
+	@Autowired
+	private EventPublisher publisher;
+	
+	/***
+	 * 收到注册请求 处理
+	 * 
+	 * @param request
+	 *            请求消息
+	 */ 
+	@Override
+	public void process(RequestEvent evt, SipLayer layer, ServerTransaction transaction) {
+		try {
+			System.out.println("收到注册请求,开始处理");
+			Request request = evt.getRequest();
+
+			Response response = null; 
+			boolean passwordCorrect = false;
+			// 注册标志  0:未携带授权头或者密码错误  1:注册成功   2:注销成功
+			int registerFlag = 0;
+			Device device = null;
+			AuthorizationHeader authorhead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME); 
+			// 校验密码是否正确
+			if (authorhead != null) {
+				passwordCorrect = new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request,
+						config.getSipPassword());
+			}
+
+			// 未携带授权头或者密码错误 回复401
+			if (authorhead == null || !passwordCorrect) {
+				
+				if (authorhead == null) {
+					System.out.println("未携带授权头 回复401");
+				} else if (!passwordCorrect) {
+					System.out.println("密码错误 回复401");
+				}
+				response = layer.getMessageFactory().createResponse(Response.UNAUTHORIZED, request);
+				new DigestServerAuthenticationHelper().generateChallenge(layer.getHeaderFactory(), response, config.getSipDomain());
+			}
+			// 携带授权头并且密码正确
+			else if (passwordCorrect) {
+				response = layer.getMessageFactory().createResponse(Response.OK, request);
+				// 添加date头
+				response.addHeader(layer.getHeaderFactory().createDateHeader(Calendar.getInstance(Locale.ENGLISH)));
+				ExpiresHeader expiresHeader = (ExpiresHeader) request.getHeader(Expires.NAME);
+				// 添加Contact头
+				response.addHeader(request.getHeader(ContactHeader.NAME));
+				// 添加Expires头
+				response.addHeader(request.getExpires());
+				
+				// 1.获取到通信地址等信息,保存到Redis
+				FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME);
+				ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
+				String received = viaHeader.getReceived();
+				int rPort = viaHeader.getRPort();
+				// 本地模拟设备 received 为空 rPort 为 -1
+				// 解析本地地址替代
+				if (StringUtils.isEmpty(received) || rPort == -1) {
+					received = viaHeader.getHost();
+					rPort = viaHeader.getPort();
+				}
+				//
+				Host host = new Host();
+				host.setIp(received);
+				host.setPort(rPort);
+				host.setAddress(received.concat(":").concat(String.valueOf(rPort)));
+				AddressImpl address = (AddressImpl) fromHeader.getAddress();
+				SipUri uri = (SipUri) address.getURI();
+				String deviceId = uri.getUser();
+				device = new Device();
+				device.setDeviceId(deviceId);
+				device.setHost(host);
+				// 注销成功
+				if (expiresHeader != null && expiresHeader.getExpires() == 0) {
+					registerFlag = 2;
+				}
+				// 注册成功
+				else {
+					registerFlag = 1;
+					// 判断TCP还是UDP
+					boolean isTcp = false;
+					ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
+					String transport = reqViaHeader.getTransport();
+					if (transport.equals("TCP")) {
+						isTcp = true;
+					}
+					device.setTransport(isTcp ? "TCP" : "UDP");
+				}
+			}
+			transaction.sendResponse(response);
+			// 注册成功
+			// 保存到redis
+			// 下发catelog查询目录
+			if (registerFlag == 1 && device != null) {
+				System.out.println("注册成功! deviceId:" + device.getDeviceId());
+				storager.update(device);
+				publisher.onlineEventPublish(device.getDeviceId(), VideoManagerConstants.EVENT_ONLINE_REGISTER);
+				cmder.deviceInfoQuery(device);
+			} else if (registerFlag == 2) {
+				System.out.println("注销成功! deviceId:" + device.getDeviceId());
+				publisher.outlineEventPublish(device.getDeviceId(), VideoManagerConstants.EVENT_OUTLINE_UNREGISTER);
+			}
+		} catch (SipException | InvalidArgumentException | NoSuchAlgorithmException | ParseException e) {
+			e.printStackTrace();
+		}
+		
+	}
+
+}

+ 64 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/SubscribeRequestProcessor.java

@@ -0,0 +1,64 @@
+package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
+
+import java.text.ParseException;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.RequestEvent;
+import javax.sip.ServerTransaction;
+import javax.sip.SipException;
+import javax.sip.header.ExpiresHeader;
+import javax.sip.message.Request;
+import javax.sip.message.Response;
+
+import org.springframework.stereotype.Component;
+
+import com.genersoft.iot.vmp.gb28181.SipLayer;
+import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
+
+/**    
+ * @Description:SUBSCRIBE请求处理器
+ * @author: songww
+ * @date:   2020年5月3日 下午5:31:20     
+ */
+@Component
+public class SubscribeRequestProcessor implements ISIPRequestProcessor {
+
+	/**   
+	 * 处理SUBSCRIBE请求  
+	 * 
+	 * @param evt
+	 * @param layer
+	 * @param transaction
+	 * @param config    
+	 */
+	@Override
+	public void process(RequestEvent evt, SipLayer layer, ServerTransaction transaction) {
+		Request request = evt.getRequest();
+
+		try {
+			Response response = null;
+			response = layer.getMessageFactory().createResponse(200, request);
+			if (response != null) {
+				ExpiresHeader expireHeader = layer.getHeaderFactory().createExpiresHeader(30);
+				response.setExpires(expireHeader);
+			}
+			System.out.println("response : " + response.toString());
+
+			if (transaction != null) {
+				transaction.sendResponse(response);
+				transaction.terminate();
+			} else {
+				System.out.println("processRequest serverTransactionId is null.");
+			}
+
+		} catch (ParseException e) {
+			e.printStackTrace();
+		} catch (SipException e) {
+			e.printStackTrace();
+		} catch (InvalidArgumentException e) {
+			e.printStackTrace();
+		}
+		
+	}
+
+}

+ 17 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/ISIPResponseProcessor.java

@@ -0,0 +1,17 @@
+package com.genersoft.iot.vmp.gb28181.transmit.response;
+
+import javax.sip.ResponseEvent;
+
+import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.gb28181.SipLayer;
+
+/**    
+ * @Description:处理接收IPCamera发来的SIP协议响应消息
+ * @author: songww
+ * @date:   2020年5月3日 下午4:42:22     
+ */
+public interface ISIPResponseProcessor {
+
+	public void process(ResponseEvent evt, SipLayer layer, SipConfig config);
+
+}

+ 33 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/ByeResponseProcessor.java

@@ -0,0 +1,33 @@
+package com.genersoft.iot.vmp.gb28181.transmit.response.impl;
+
+import javax.sip.ResponseEvent;
+
+import org.springframework.stereotype.Component;
+
+import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.gb28181.SipLayer;
+import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor;
+
+/**    
+ * @Description: BYE请求响应器
+ * @author: songww
+ * @date:   2020年5月3日 下午5:32:05     
+ */
+@Component
+public class ByeResponseProcessor implements ISIPResponseProcessor {
+
+	/**   
+	 * 处理BYE响应
+	 * 
+	 * @param evt
+	 * @param layer
+	 * @param transaction
+	 * @param config    
+	 */  
+	@Override
+	public void process(ResponseEvent evt, SipLayer layer, SipConfig config) {
+		// TODO Auto-generated method stub
+		
+	}
+
+}

+ 33 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/CancelResponseProcessor.java

@@ -0,0 +1,33 @@
+package com.genersoft.iot.vmp.gb28181.transmit.response.impl;
+
+import javax.sip.ResponseEvent;
+
+import org.springframework.stereotype.Component;
+
+import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.gb28181.SipLayer;
+import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor;
+
+/**    
+ * @Description:CANCEL响应处理器
+ * @author: songww
+ * @date:   2020年5月3日 下午5:32:23     
+ */
+@Component
+public class CancelResponseProcessor implements ISIPResponseProcessor {
+
+	/**   
+	 * 处理CANCEL响应
+	 *  
+	 * @param evt
+	 * @param layer
+	 * @param transaction
+	 * @param config    
+	 */  
+	@Override
+	public void process(ResponseEvent evt, SipLayer layer, SipConfig config) {
+		// TODO Auto-generated method stub
+		
+	}
+
+}

+ 40 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/InviteResponseProcessor.java

@@ -0,0 +1,40 @@
+package com.genersoft.iot.vmp.gb28181.transmit.response.impl;
+
+import javax.sip.Dialog;
+import javax.sip.InvalidArgumentException;
+import javax.sip.ResponseEvent;
+import javax.sip.SipException;
+import javax.sip.message.Request;
+
+import org.springframework.stereotype.Component;
+
+import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.gb28181.SipLayer;
+import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor;
+
+/**    
+ * @Description:处理INVITE响应
+ * @author: songww
+ * @date:   2020年5月3日 下午4:43:52     
+ */
+@Component
+public class InviteResponseProcessor implements ISIPResponseProcessor {
+
+	/**
+	 * 处理invite响应
+	 * 
+	 * @param request
+	 *            响应消息
+	 */ 
+	@Override
+	public void process(ResponseEvent evt, SipLayer layer, SipConfig config) {
+		try {
+			Dialog dialog = evt.getDialog();
+			Request reqAck =dialog.createAck(1L);
+			dialog.sendAck(reqAck);
+		} catch (InvalidArgumentException | SipException e) {
+			e.printStackTrace();
+		}
+	}
+
+}

+ 32 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/OtherResponseProcessor.java

@@ -0,0 +1,32 @@
+package com.genersoft.iot.vmp.gb28181.transmit.response.impl;
+
+import javax.sip.ResponseEvent;
+
+import org.springframework.stereotype.Component;
+
+import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.gb28181.SipLayer;
+import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor;
+
+/**    
+ * @Description:暂不支持的消息响应处理器
+ * @author: songww
+ * @date:   2020年5月3日 下午5:32:59     
+ */
+@Component
+public class OtherResponseProcessor implements ISIPResponseProcessor {
+
+	/**   
+	 * <p>Title: process</p>   
+	 * <p>Description: </p>   
+	 * @param evt
+	 * @param layer
+	 * @param config    
+	 */  
+	@Override
+	public void process(ResponseEvent evt, SipLayer layer, SipConfig config) {
+		// TODO Auto-generated method stub
+		
+	}
+
+}

+ 121 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java

@@ -0,0 +1,121 @@
+package com.genersoft.iot.vmp.gb28181.utils;
+
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.dom4j.Attribute;
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 基于dom4j的工具包
+ * 
+ * 
+ */
+public class XmlUtil
+{
+    /**
+     * 日志服务
+     */
+    private static Logger LOG = LoggerFactory.getLogger(XmlUtil.class);
+
+    /**
+     * 解析XML为Document对象
+     * 
+     * @param xml
+     *            被解析的XMl
+     * @return Document
+     */
+    public static Element parseXml(String xml)
+    {
+        Document document = null;
+        //
+        StringReader sr = new StringReader(xml);
+        SAXReader saxReader = new SAXReader();
+        try
+        {
+            document = saxReader.read(sr);
+        }
+        catch (DocumentException e)
+        {
+            LOG.error("解析失败", e);
+        }
+        return null == document ? null : document.getRootElement();
+    }
+
+    /**
+     * 获取element对象的text的值
+     * 
+     * @param em
+     *            节点的对象
+     * @param tag
+     *            节点的tag
+     * @return 节点
+     */
+    public static String getText(Element em, String tag)
+    {
+        if (null == em)
+        {
+            return null;
+        }
+        Element e = em.element(tag);
+        //
+        return null == e ? null : e.getText();
+    }
+
+    /**
+     * 递归解析xml节点,适用于 多节点数据
+     * 
+     * @param node
+     *            node
+     * @param nodeName
+     *            nodeName
+     * @return List<Map<String, Object>>
+     */
+    public static List<Map<String, Object>> listNodes(Element node, String nodeName)
+    {
+        if (null == node)
+        {
+            return null;
+        }
+        // 初始化返回
+        List<Map<String, Object>> listMap = new ArrayList<Map<String, Object>>();
+        // 首先获取当前节点的所有属性节点
+        List<Attribute> list = node.attributes();
+
+        Map<String, Object> map = null;
+        // 遍历属性节点
+        for (Attribute attribute : list)
+        {
+            if (nodeName.equals(node.getName()))
+            {
+                if (null == map)
+                {
+                    map = new HashMap<String, Object>();
+                    listMap.add(map);
+                }
+                // 取到的节点属性放到map中
+                map.put(attribute.getName(), attribute.getValue());
+            }
+
+        }
+        // 遍历当前节点下的所有节点 ,nodeName 要解析的节点名称
+        // 使用递归
+        Iterator<Element> iterator = node.elementIterator();
+        while (iterator.hasNext())
+        {
+            Element e = iterator.next();
+            listMap.addAll(listNodes(e, nodeName));
+        }
+        return listMap;
+    }
+
+}

+ 77 - 0
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java

@@ -0,0 +1,77 @@
+package com.genersoft.iot.vmp.storager;
+
+import java.util.List;
+
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+
+/**    
+ * @Description:视频设备数据存储接口
+ * @author: songww
+ * @date:   2020年5月6日 下午2:14:31     
+ */
+public interface IVideoManagerStorager {
+	
+	/**   
+	 * 根据设备ID判断设备是否存在
+	 * 
+	 * @param deviceId 设备ID
+	 * @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 update(Device device);
+	
+	/**   
+	 * 获取设备
+	 * 
+	 * @param deviceId 设备ID
+	 * @return DShadow 设备对象
+	 */
+	public Device queryVideoDevice(String deviceId);
+	
+	/**   
+	 * 获取多个设备
+	 * 
+	 * @param deviceIds 设备ID数组
+	 * @return List<Device> 设备对象数组
+	 */
+	public List<Device> queryVideoDeviceList(String[] deviceIds);
+	
+	/**   
+	 * 删除设备
+	 * 
+	 * @param deviceId 设备ID
+	 * @return true:删除成功  false:删除失败
+	 */
+	public boolean delete(String deviceId);
+	
+	/**   
+	 * 更新设备在线
+	 * 
+	 * @param deviceId 设备ID
+	 * @return true:更新成功  false:更新失败
+	 */
+	public boolean online(String deviceId);
+	
+	/**   
+	 * 更新设备离线
+	 * 
+	 * @param deviceId 设备ID
+	 * @return true:更新成功  false:更新失败
+	 */
+	public boolean outline(String deviceId);
+}

+ 36 - 0
src/main/java/com/genersoft/iot/vmp/storager/VideoManagerStoragerFactory.java

@@ -0,0 +1,36 @@
+package com.genersoft.iot.vmp.storager;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+
+import com.genersoft.iot.vmp.conf.VManagerConfig;
+
+/**    
+ * @Description:视频设备数据存储工厂,根据存储策略,返回对应的存储器
+ * @author: songww
+ * @date:   2020年5月6日 下午2:15:16     
+ */
+@Component
+public class VideoManagerStoragerFactory {
+	
+	@Autowired
+	private VManagerConfig vmConfig;
+
+	@Autowired
+	private IVideoManagerStorager jdbcStorager;
+	
+	@Autowired
+	private IVideoManagerStorager redisStorager;
+	
+	@Bean("storager")
+	public IVideoManagerStorager getStorager() {
+		if ("redis".equals(vmConfig.getDatabase().toLowerCase())) {
+			return redisStorager;
+		} else  if ("jdbc".equals(vmConfig.getDatabase().toLowerCase())) {
+			return jdbcStorager;
+		}
+		return redisStorager;
+	}
+	
+}

+ 116 - 0
src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java

@@ -0,0 +1,116 @@
+package com.genersoft.iot.vmp.storager.jdbc;
+
+import java.util.List;
+
+import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Service;
+
+import com.genersoft.iot.vmp.common.VideoManagerConstants;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+
+/**    
+ * @Description:视频设备数据存储-jdbc实现  
+ * @author: songww
+ * @date:   2020年5月6日 下午2:28:12     
+ */
+@Component("jdbcStorager")
+public class VideoManagerJdbcStoragerImpl implements IVideoManagerStorager {
+
+	/**   
+	 * 根据设备ID判断设备是否存在
+	 * 
+	 * @param deviceId 设备ID
+	 * @return true:存在  false:不存在
+	 */ 
+	@Override
+	public boolean exists(String deviceId) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+	/**   
+	 * 视频设备创建
+	 * 
+	 * @param device 设备对象
+	 * @return true:创建成功  false:创建失败
+	 */ 
+	@Override
+	public boolean create(Device device) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+	
+	/**   
+	 * 视频设备更新
+	 * 
+	 * @param device 设备对象
+	 * @return true:更新成功  false:更新失败
+	 */  
+	@Override
+	public boolean update(Device device) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+	/**   
+	 * 获取设备
+	 * 
+	 * @param deviceId 设备ID
+	 * @return Device 设备对象
+	 */  
+	@Override
+	public Device queryVideoDevice(String deviceId) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	/**   
+	 * 获取多个设备
+	 * 
+	 * @param deviceIds 设备ID数组
+	 * @return List<Device> 设备对象数组
+	 */  
+	@Override
+	public List<Device> queryVideoDeviceList(String[] deviceIds) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	/**   
+	 * 删除设备
+	 * 
+	 * @param deviceId 设备ID
+	 * @return true:删除成功  false:删除失败
+	 */  
+	@Override
+	public boolean delete(String deviceId) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+	/**   
+	 * 更新设备在线
+	 * 
+	 * @param deviceId 设备ID
+	 * @return true:更新成功  false:更新失败
+	 */ 
+	@Override
+	public boolean online(String deviceId) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+	/**   
+	 * 更新设备离线
+	 * 
+	 * @param deviceId 设备ID
+	 * @return true:更新成功  false:更新失败
+	 */ 
+	@Override
+	public boolean outline(String deviceId) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+}

+ 129 - 0
src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java

@@ -0,0 +1,129 @@
+package com.genersoft.iot.vmp.storager.redis;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.genersoft.iot.vmp.common.VideoManagerConstants;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+import com.genersoft.iot.vmp.utils.redis.RedisUtil;
+
+/**    
+ * @Description:视频设备数据存储-redis实现  
+ * @author: songww
+ * @date:   2020年5月6日 下午2:31:42     
+ */
+@Component("redisStorager")
+public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager {
+
+	@Autowired
+    private RedisUtil redis;
+	
+	/**   
+	 * 根据设备ID判断设备是否存在
+	 * 
+	 * @param deviceId 设备ID
+	 * @return true:存在  false:不存在
+	 */ 
+	@Override
+	public boolean exists(String deviceId) {
+		return redis.hasKey(VideoManagerConstants.CACHEKEY_PREFIX+deviceId);
+	}
+
+	/**   
+	 * 视频设备创建
+	 * 
+	 * @param device 设备对象
+	 * @return true:创建成功  false:创建失败
+	 */ 
+	@Override
+	public boolean create(Device device) {
+		return redis.set(VideoManagerConstants.CACHEKEY_PREFIX+device.getDeviceId(), device);
+	}
+	
+	/**   
+	 * 视频设备更新
+	 * 
+	 * @param device 设备对象
+	 * @return true:更新成功  false:更新失败
+	 */  
+	@Override
+	public boolean update(Device device) {
+		return redis.set(VideoManagerConstants.CACHEKEY_PREFIX+device.getDeviceId(), device);
+	}
+
+	/**   
+	 * 获取设备
+	 * 
+	 * @param deviceId 设备ID
+	 * @return Device 设备对象
+	 */  
+	@Override
+	public Device queryVideoDevice(String deviceId) {
+		return (Device)redis.get(VideoManagerConstants.CACHEKEY_PREFIX+deviceId);
+	}
+
+	/**   
+	 * 获取多个设备
+	 * 
+	 * @param deviceIds 设备ID数组
+	 * @return List<Device> 设备对象数组
+	 */  
+	@Override
+	public List<Device> queryVideoDeviceList(String[] deviceIds) {
+		List<Device> devices = new ArrayList<>();
+		if (deviceIds == null || deviceIds.length == 0) {
+			List<Object> deviceIdList = redis.keys(VideoManagerConstants.CACHEKEY_PREFIX+"*");
+			for (int i = 0; i < deviceIdList.size(); i++) {
+				devices.add((Device)redis.get((String)deviceIdList.get(i)));
+			}
+		} else {
+			for (int i = 0; i < deviceIds.length; i++) {
+				devices.add((Device)redis.get(VideoManagerConstants.CACHEKEY_PREFIX+deviceIds[i]));
+			}
+		}
+		return devices;
+	}
+
+	/**   
+	 * 删除设备
+	 * 
+	 * @param deviceId 设备ID
+	 * @return true:删除成功  false:删除失败
+	 */  
+	@Override
+	public boolean delete(String deviceId) {
+		redis.del(VideoManagerConstants.CACHEKEY_PREFIX+deviceId);
+		return true;  
+	}
+
+	/**   
+	 * 更新设备在线
+	 * 
+	 * @param deviceId 设备ID
+	 * @return true:更新成功  false:更新失败
+	 */ 
+	@Override
+	public boolean online(String deviceId) {
+		Device device = (Device)redis.get(VideoManagerConstants.CACHEKEY_PREFIX+deviceId);
+		device.setOnline(1);
+		return redis.set(VideoManagerConstants.CACHEKEY_PREFIX+device.getDeviceId(), device);
+	}
+
+	/**   
+	 * 更新设备离线
+	 * 
+	 * @param deviceId 设备ID
+	 * @return true:更新成功  false:更新失败
+	 */ 
+	@Override
+	public boolean outline(String deviceId) {
+		Device device = (Device)redis.get(VideoManagerConstants.CACHEKEY_PREFIX+deviceId);
+		device.setOnline(0);
+		return redis.set(VideoManagerConstants.CACHEKEY_PREFIX+device.getDeviceId(), device);
+	}
+	
+}

+ 43 - 0
src/main/java/com/genersoft/iot/vmp/utils/redis/FastJsonRedisSerializer.java

@@ -0,0 +1,43 @@
+package com.genersoft.iot.vmp.utils.redis;
+
+import java.nio.charset.Charset;
+
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+
+/**    
+ * @Description:使用fastjson实现redis的序列化   
+ * @author: songww
+ * @date:   2020年5月6日 下午8:40:11     
+ */
+public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
+
+	public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+	 
+    private Class<T> clazz;
+ 
+    public FastJsonRedisSerializer(Class<T> clazz) {
+        super();
+        this.clazz = clazz;
+    }
+ 
+    @Override
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
+            return new byte[0];
+        }
+        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+    }
+ 
+    @Override
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length <= 0) {
+            return null;
+        }
+        String str = new String(bytes, DEFAULT_CHARSET);
+        return (T) JSON.parseObject(str, clazz);
+    }
+}

+ 557 - 0
src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java

@@ -0,0 +1,557 @@
+package com.genersoft.iot.vmp.utils.redis;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+/**    
+ * @Description:Redis工具类
+ * @author: songww
+ * @date:   2020年5月6日 下午8:27:29     
+ */
+@Component
+public class RedisUtil {
+
+	@Autowired
+    private RedisTemplate redisTemplate;
+	
+	/**
+     * 指定缓存失效时间
+     * @param key 键
+     * @param time 时间(秒)
+     * @return true / false
+     */
+    public boolean expire(String key, long time) {
+        try {
+            if (time > 0) {
+                redisTemplate.expire(key, time, TimeUnit.SECONDS);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 根据 key 获取过期时间
+     * @param key 键
+     * @return
+     */
+    public long getExpire(String key) {
+        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 判断 key 是否存在
+     * @param key 键
+     * @return true / false
+     */
+    public boolean hasKey(String key) {
+        try {
+            return redisTemplate.hasKey(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 删除缓存
+     * @SuppressWarnings("unchecked") 忽略类型转换警告
+     * @param key 键(一个或者多个)
+     */
+    public void del(String... key) {
+        if (key != null && key.length > 0) {
+            if (key.length == 1) {
+                redisTemplate.delete(key[0]);
+            } else {
+//                传入一个 Collection<String> 集合
+                redisTemplate.delete(CollectionUtils.arrayToList(key));
+            }
+        }
+    }
+
+//    ============================== String ==============================
+
+    /**
+     * 普通缓存获取
+     * @param key 键
+     * @return 值
+     */
+    public Object get(String key) {
+        return key == null ? null : redisTemplate.opsForValue().get(key);
+    }
+
+    /**
+     * 普通缓存放入
+     * @param key 键
+     * @param value 值
+     * @return true / false
+     */
+    public boolean set(String key, Object value) {
+        try {
+            redisTemplate.opsForValue().set(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 普通缓存放入并设置时间
+     * @param key 键
+     * @param value 值
+     * @param time 时间(秒),如果 time < 0 则设置无限时间
+     * @return true / false
+     */
+    public boolean set(String key, Object value, long time) {
+        try {
+            if (time > 0) {
+                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
+            } else {
+                set(key, value);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 递增
+     * @param key 键
+     * @param delta 递增大小
+     * @return
+     */
+    public long incr(String key, long delta) {
+        if (delta < 0) {
+            throw new RuntimeException("递增因子必须大于 0");
+        }
+        return redisTemplate.opsForValue().increment(key, delta);
+    }
+
+    /**
+     * 递减
+     * @param key 键
+     * @param delta 递减大小
+     * @return
+     */
+    public long decr(String key, long delta) {
+        if (delta < 0) {
+            throw new RuntimeException("递减因子必须大于 0");
+        }
+        return redisTemplate.opsForValue().increment(key, delta);
+    }
+
+//    ============================== Map ==============================
+
+    /**
+     * HashGet
+     * @param key 键(no null)
+     * @param item 项(no null)
+     * @return 值
+     */
+    public Object hget(String key, String item) {
+        return redisTemplate.opsForHash().get(key, item);
+    }
+
+    /**
+     * 获取 key 对应的 map
+     * @param key 键(no null)
+     * @return 对应的多个键值
+     */
+    public Map<Object, Object> hmget(String key) {
+        return redisTemplate.opsForHash().entries(key);
+    }
+
+    /**
+     * HashSet
+     * @param key 键
+     * @param map 值
+     * @return true / false
+     */
+    public boolean hmset(String key, Map<Object, Object> map) {
+        try {
+            redisTemplate.opsForHash().putAll(key, map);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * HashSet 并设置时间
+     * @param key 键
+     * @param map 值
+     * @param time 时间
+     * @return true / false
+     */
+    public boolean hmset(String key, Map<Object, Object> map, long time) {
+        try {
+            redisTemplate.opsForHash().putAll(key, map);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 向一张 Hash表 中放入数据,如不存在则创建
+     * @param key 键
+     * @param item 项
+     * @param value 值
+     * @return true / false
+     */
+    public boolean hset(String key, String item, Object value) {
+        try {
+            redisTemplate.opsForHash().put(key, item, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 向一张 Hash表 中放入数据,并设置时间,如不存在则创建
+     * @param key 键
+     * @param item 项
+     * @param value 值
+     * @param time 时间(如果原来的 Hash表 设置了时间,这里会覆盖)
+     * @return true / false
+     */
+    public boolean hset(String key, String item, Object value, long time) {
+        try {
+            redisTemplate.opsForHash().put(key, item, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 删除 Hash表 中的值
+     * @param key 键
+     * @param item 项(可以多个,no null)
+     */
+    public void hdel(String key, Object... item) {
+        redisTemplate.opsForHash().delete(key, item);
+    }
+
+    /**
+     * 判断 Hash表 中是否有该键的值
+     * @param key 键(no null)
+     * @param item 值(no null)
+     * @return true / false
+     */
+    public boolean hHasKey(String key, String item) {
+        return redisTemplate.opsForHash().hasKey(key, item);
+    }
+
+    /**
+     * Hash递增,如果不存在则创建一个,并把新增的值返回
+     * @param key 键
+     * @param item 项
+     * @param by 递增大小 > 0
+     * @return
+     */
+    public Double hincr(String key, String item, Double by) {
+        return redisTemplate.opsForHash().increment(key, item, by);
+    }
+
+    /**
+     * Hash递减
+     * @param key 键
+     * @param item 项
+     * @param by 递减大小
+     * @return
+     */
+    public Double hdecr(String key, String item, Double by) {
+        return redisTemplate.opsForHash().increment(key, item, -by);
+    }
+
+//    ============================== Set ==============================
+
+    /**
+     * 根据 key 获取 set 中的所有值
+     * @param key 键
+     * @return 值
+     */
+    public Set<Object> sGet(String key) {
+        try {
+            return redisTemplate.opsForSet().members(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 从键为 key 的 set 中,根据 value 查询是否存在
+     * @param key 键
+     * @param value 值
+     * @return true / false
+     */
+    public boolean sHasKey(String key, Object value) {
+        try {
+            return redisTemplate.opsForSet().isMember(key, value);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将数据放入 set缓存
+     * @param key 键值
+     * @param values 值(可以多个)
+     * @return 成功个数
+     */
+    public long sSet(String key, Object... values) {
+        try {
+            return redisTemplate.opsForSet().add(key, values);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 将数据放入 set缓存,并设置时间
+     * @param key 键
+     * @param time 时间
+     * @param values 值(可以多个)
+     * @return 成功放入个数
+     */
+    public long sSet(String key, long time, Object... values) {
+        try {
+            long count = redisTemplate.opsForSet().add(key, values);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return count;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 获取 set缓存的长度
+     * @param key 键
+     * @return 长度
+     */
+    public long sGetSetSize(String key) {
+        try {
+            return redisTemplate.opsForSet().size(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 移除 set缓存中,值为 value 的
+     * @param key 键
+     * @param values 值
+     * @return 成功移除个数
+     */
+    public long setRemove(String key, Object... values) {
+        try {
+            return redisTemplate.opsForSet().remove(key, values);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+//    ============================== List ==============================
+
+    /**
+     * 获取 list缓存的内容
+     * @param key 键
+     * @param start 开始
+     * @param end 结束(0 到 -1 代表所有值)
+     * @return
+     */
+    public List<Object> lGet(String key, long start, long end) {
+        try {
+            return redisTemplate.opsForList().range(key, start, end);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 获取 list缓存的长度
+     * @param key 键
+     * @return 长度
+     */
+    public long lGetListSize(String key) {
+        try {
+            return redisTemplate.opsForList().size(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 根据索引 index 获取键为 key 的 list 中的元素
+     * @param key 键
+     * @param index 索引
+     *              当 index >= 0 时 {0:表头, 1:第二个元素}
+     *              当 index < 0 时 {-1:表尾, -2:倒数第二个元素}
+     * @return 值
+     */
+    public Object lGetIndex(String key, long index) {
+        try {
+            return redisTemplate.opsForList().index(key, index);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 将值 value 插入键为 key 的 list 中,如果 list 不存在则创建空 list
+     * @param key 键
+     * @param value 值
+     * @return true / false
+     */
+    public boolean lSet(String key, Object value) {
+        try {
+            redisTemplate.opsForList().rightPush(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将值 value 插入键为 key 的 list 中,并设置时间
+     * @param key 键
+     * @param value 值
+     * @param time 时间
+     * @return true / false
+     */
+    public boolean lSet(String key, Object value, long time) {
+        try {
+            redisTemplate.opsForList().rightPush(key, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将 values 插入键为 key 的 list 中
+     * @param key 键
+     * @param values 值
+     * @return true / false
+     */
+    public boolean lSetList(String key, List<Object> values) {
+        try {
+            redisTemplate.opsForList().rightPushAll(key, values);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将 values 插入键为 key 的 list 中,并设置时间
+     * @param key 键
+     * @param values 值
+     * @param time 时间
+     * @return true / false
+     */
+    public boolean lSetList(String key, List<Object> values, long time) {
+        try {
+            redisTemplate.opsForList().rightPushAll(key, values);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 根据索引 index 修改键为 key 的值
+     * @param key 键
+     * @param index 索引
+     * @param value 值
+     * @return true / false
+     */
+    public boolean lUpdateIndex(String key, long index, Object value) {
+        try {
+            redisTemplate.opsForList().set(key, index, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 在键为 key 的 list 中删除值为 value 的元素
+     * @param key 键
+     * @param count 如果 count == 0 则删除 list 中所有值为 value 的元素
+     *              如果 count > 0 则删除 list 中最左边那个值为 value 的元素
+     *              如果 count < 0 则删除 list 中最右边那个值为 value 的元素
+     * @param value
+     * @return
+     */
+    public long lRemove(String key, long count, Object value) {
+        try {
+            return redisTemplate.opsForList().remove(key, count, value);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+    
+    /**
+     * 模糊查询
+     * @param key 键
+     * @return true / false
+     */
+    public List<Object> keys(String key) {
+        try {
+        	Set<String> set = redisTemplate.keys(key);
+            return new ArrayList<>(set);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+}

+ 50 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java

@@ -0,0 +1,50 @@
+package com.genersoft.iot.vmp.vmanager.device;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.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 com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
+
+@RestController
+@RequestMapping("/api")
+public class DeviceController {
+	
+	private final static Logger logger = LoggerFactory.getLogger(DeviceController.class);
+	
+	@Autowired
+	private IVideoManagerStorager storager;
+	
+	@GetMapping("/devices/{deviceId}")
+	public ResponseEntity<List<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);
+	}
+	
+	@GetMapping("/devices")
+	public ResponseEntity<List<Device>> devices(){
+		
+		if (logger.isDebugEnabled()) {
+			logger.debug("查询所有视频设备API调用");
+		}
+		
+		List<Device> deviceList = storager.queryVideoDeviceList(null);
+		return new ResponseEntity<>(deviceList,HttpStatus.OK);
+	}
+}

+ 41 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java

@@ -0,0 +1,41 @@
+package com.genersoft.iot.vmp.vmanager.play;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.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 com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
+
+@RestController
+@RequestMapping("/api")
+public class PlayController {
+	
+	private final static Logger logger = LoggerFactory.getLogger(PlayController.class);
+	
+	@Autowired
+	private SIPCommander cmder;
+	
+	@GetMapping("/play/{deviceId}_{channelId}")
+	public ResponseEntity<String> play(@PathVariable String deviceId,@PathVariable String channelId){
+		
+		String ssrc = cmder.playStreamCmd(deviceId, channelId);
+		
+		if (logger.isDebugEnabled()) {
+			logger.debug(String.format("设备预览 API调用,deviceId:%s ,channelId:%s",deviceId, channelId));
+			logger.debug("设备预览 API调用,ssrc:"+ssrc+",ZLMedia streamId:"+Integer.toHexString(Integer.parseInt(ssrc)));
+		}
+		
+		if(ssrc!=null) {
+			return new ResponseEntity<String>(ssrc,HttpStatus.OK);
+		} else {
+			logger.warn("设备预览API调用失败!");
+			return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
+		}
+	}
+}

+ 45 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/ptz/PtzController.java

@@ -0,0 +1,45 @@
+package com.genersoft.iot.vmp.vmanager.ptz;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.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 com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
+
+@RestController
+@RequestMapping("/api")
+public class PtzController {
+	
+	private final static Logger logger = LoggerFactory.getLogger(PtzController.class);
+	
+	@Autowired
+	private SIPCommander cmder;
+
+	/***
+	 * http://localhost:8080/api/ptz/34020000001320000002_34020000001320000008?leftRight=1&upDown=0&inOut=0&moveSpeed=50&zoomSpeed=0
+	 * @param deviceId
+	 * @param channelId
+	 * @param leftRight
+	 * @param upDown
+	 * @param inOut
+	 * @param moveSpeed
+	 * @param zoomSpeed
+	 * @return
+	 */
+	@GetMapping("/ptz/{deviceId}_{channelId}")
+	public ResponseEntity<String> ptz(@PathVariable String deviceId,@PathVariable String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed){
+		
+		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));
+		}
+		
+		cmder.ptzCmd(deviceId, channelId, leftRight, upDown, inOut, moveSpeed, zoomSpeed);
+		return new ResponseEntity<String>("success",HttpStatus.OK);
+	}
+}

+ 36 - 0
src/main/resources/application.yml

@@ -0,0 +1,36 @@
+spring:
+    application:
+        name: wvp
+        # 数据存储方式,支持redis、jdbc
+        database: redis
+    redis: 
+        # Redis服务器IP
+        host: 127.0.0.1
+        #端口号
+        port: 6379
+        datebase: 0
+        #访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接
+        password:
+        #超时时间
+        timeout: 10000
+    datasource: 
+        name: wcp
+        url: jdbc:mysql://127.0.0.1:3306/wcp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
+        username: root
+        password: 123456
+        type: com.alibaba.druid.pool.DruidDataSource
+        driver-class-name: com.mysql.jdbc.Driver
+server:
+    port: 8080
+sip:
+    # 本地服务地址
+    ip: 192.168.0.3
+    server_id: 34020000002000000001
+    port: 5060
+    domain: 34020000
+    # 暂时使用统一密码,后续改为一机一密
+    password: admin
+media:
+    # ZLMediaServer IP
+    ip: 192.168.0.4
+    port: 10000