commit 70091f29f2c3c3f974ffc2cc8f140db9f992ca0e Author: songww Date: Thu May 7 21:55:45 2020 +0800 初始提交 diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..88b1f90d --- /dev/null +++ b/pom.xml @@ -0,0 +1,173 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 1.5.10.RELEASE + + + com.genersoft + wvp + web video platform + + + UTF-8 + + 1.5.12 + + 3.4.0 + 3.3.1 + 1.2.4 + 4.1.1 + ${project.build.directory}/generated-snippets + ${project.basedir}/docs/asciidoc + ${project.build.directory}/asciidoc + ${project.build.directory}/asciidoc/html + ${project.build.directory}/asciidoc/pdf + 2.8.0 + 3.0.1 + 2.4 + + + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-undertow + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework + spring-context + + + + + + com.alibaba + druid + 1.0.11 + + + mysql + mysql-connector-java + 5.1.30 + + + + + org.mybatis + mybatis + ${mybatis.version} + + + org.mybatis + mybatis-spring + ${mybatis.spring.version} + + + + + com.github.pagehelper + pagehelper + ${pagehelper.version} + + + + + tk.mybatis + mapper + ${mapper.version} + + + org.apache.commons + commons-lang3 + 3.4 + + + + com.alibaba + fastjson + 1.2.33 + + + + + + io.springfox + springfox-swagger2 + 2.6.1 + + + io.springfox + springfox-swagger-ui + 2.6.1 + + + + + org.springframework.boot + spring-boot-starter-aop + + + + javax.sip + jain-sip-ri + 1.3.0-91 + provided + + + org.dom4j + dom4j + 2.1.1 + + + com.google.code.gson + gson + + + org.springframework.data + spring-data-redis + 1.8.4.RELEASE + + + redis.clients + jedis + 2.9.0 + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + + diff --git a/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java b/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java new file mode 100644 index 00000000..664b0d21 --- /dev/null +++ b/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); + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java b/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java new file mode 100644 index 00000000..723af703 --- /dev/null +++ b/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"; +} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/RedisConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/RedisConfig.java new file mode 100644 index 00000000..914d0c31 --- /dev/null +++ b/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 redisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(redisConnectionFactory); + ParserConfig.getGlobalInstance().setAutoTypeSupport(true); + FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(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"); +// } +} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java new file mode 100644 index 00000000..e6d23a4a --- /dev/null +++ b/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; + } + + +} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/VManagerConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/VManagerConfig.java new file mode 100644 index 00000000..b294e0c4 --- /dev/null +++ b/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; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java b/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java new file mode 100644 index 00000000..4d08fcc6 --- /dev/null +++ b/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){ + + } + } + + /** + *

Title: processTimeout

+ *

Description:

+ * @param timeoutEvent + */ + @Override + public void processTimeout(TimeoutEvent timeoutEvent) { + // TODO Auto-generated method stub + + } + + /** + *

Title: processIOException

+ *

Description:

+ * @param exceptionEvent + */ + @Override + public void processIOException(IOExceptionEvent exceptionEvent) { + // TODO Auto-generated method stub + + } + + /** + *

Title: processTransactionTerminated

+ *

Description:

+ * @param transactionTerminatedEvent + */ + @Override + public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) { + // TODO Auto-generated method stub + + } + + /** + *

Title: processDialogTerminated

+ *

Description:

+ * @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; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java b/src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java new file mode 100644 index 00000000..637ee9a7 --- /dev/null +++ b/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); + + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java new file mode 100644 index 00000000..74f86a21 --- /dev/null +++ b/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 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 getChannelMap() { + return channelMap; + } + + public void setChannelMap(Map 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; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarm.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarm.java new file mode 100644 index 00000000..8ba43c26 --- /dev/null +++ b/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; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java new file mode 100644 index 00000000..1bccad83 --- /dev/null +++ b/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离线 + * 默认在线 + * 信令: + * ON + * OFF + * 遇到过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; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Host.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Host.java new file mode 100644 index 00000000..4afb6346 --- /dev/null +++ b/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; + } + + + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java new file mode 100644 index 00000000..de0dbc45 --- /dev/null +++ b/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); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEvent.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEvent.java new file mode 100644 index 00000000..afc6751e --- /dev/null +++ b/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; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEventListener.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEventListener.java new file mode 100644 index 00000000..4f8d6ab6 --- /dev/null +++ b/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 { + + 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()); + } + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/outline/KeepliveTimeoutListener.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/outline/KeepliveTimeoutListener.java new file mode 100644 index 00000000..7f427e7d --- /dev/null +++ b/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); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/outline/OutlineEvent.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/outline/OutlineEvent.java new file mode 100644 index 00000000..7e729363 --- /dev/null +++ b/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; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/outline/OutlineEventListener.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/outline/OutlineEventListener.java new file mode 100644 index 00000000..a87b42e8 --- /dev/null +++ b/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 { + + 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()); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java new file mode 100644 index 00000000..b809696c --- /dev/null +++ b/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; + } + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java new file mode 100644 index 00000000..2fe88ae3 --- /dev/null +++ b/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); +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java new file mode 100644 index 00000000..55aa9fea --- /dev/null +++ b/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 viaHeaders = new ArrayList(); + 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 viaHeaders = new ArrayList(); + 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; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/PtzCmdHelper.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/PtzCmdHelper.java new file mode 100644 index 00000000..f3b411b4 --- /dev/null +++ b/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); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java new file mode 100644 index 00000000..09f09346 --- /dev/null +++ b/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(""); + ptzXml.append(""); + ptzXml.append("DeviceControl"); + ptzXml.append("" + (int)((Math.random()*9+1)*100000) + ""); + ptzXml.append("" + channelId + ""); + ptzXml.append("" + PtzCmdHelper.create(leftRight, upDown, inOut, moveSpeed, zoomSpeed) + ""); + ptzXml.append(""); + ptzXml.append(""); + ptzXml.append(""); + + 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(""); + catalogXml.append(""); + catalogXml.append("DeviceInfo"); + catalogXml.append("" + (int)((Math.random()*9+1)*100000) + ""); + catalogXml.append("" + device.getDeviceId() + ""); + catalogXml.append(""); + + 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(""); + catalogXml.append(""); + catalogXml.append("Catalog"); + catalogXml.append("" + (int)((Math.random()*9+1)*100000) + ""); + catalogXml.append("" + device.getDeviceId() + ""); + catalogXml.append(""); + + 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); + } + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/ISIPRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/ISIPRequestProcessor.java new file mode 100644 index 00000000..a1757f21 --- /dev/null +++ b/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); + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/AckRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/AckRequestProcessor.java new file mode 100644 index 00000000..f26d566e --- /dev/null +++ b/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(); + } + + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/ByeRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/ByeRequestProcessor.java new file mode 100644 index 00000000..20dd82f4 --- /dev/null +++ b/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 + + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/CancelRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/CancelRequestProcessor.java new file mode 100644 index 00000000..c5008290 --- /dev/null +++ b/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 + + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/InviteRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/InviteRequestProcessor.java new file mode 100644 index 00000000..17b592a9 --- /dev/null +++ b/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(); +// } + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java new file mode 100644 index 00000000..9a5551b5 --- /dev/null +++ b/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("Keepalive")) { + processMessageKeepAlive(evt); + } else if (new String(request.getRawContent()).contains("Catalog")) { + processMessageCatalogList(evt); + } else if (new String(request.getRawContent()).contains("DeviceInfo")) { + processMessageDeviceInfo(evt); + } else if (new String(request.getRawContent()).contains("Alarm")) { + 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 deviceListIterator = deviceListElement.elementIterator(); + if (deviceListIterator != null) { + Device device = storager.queryVideoDevice(deviceId); + if (device == null) { + return; + } + Map channelMap = device.getChannelMap(); + if (channelMap == null) { + channelMap = new HashMap(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(); + } + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/OtherRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/OtherRequestProcessor.java new file mode 100644 index 00000000..227240d8 --- /dev/null +++ b/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 { + + /** + *

Title: process

+ *

Description:

+ * @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()); + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java new file mode 100644 index 00000000..1c7a957f --- /dev/null +++ b/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(); + } + + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/SubscribeRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/SubscribeRequestProcessor.java new file mode 100644 index 00000000..47761d09 --- /dev/null +++ b/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(); + } + + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/ISIPResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/ISIPResponseProcessor.java new file mode 100644 index 00000000..9ff99597 --- /dev/null +++ b/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); + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/ByeResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/ByeResponseProcessor.java new file mode 100644 index 00000000..2efa139c --- /dev/null +++ b/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 + + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/CancelResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/CancelResponseProcessor.java new file mode 100644 index 00000000..1ce6f433 --- /dev/null +++ b/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 + + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/InviteResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/InviteResponseProcessor.java new file mode 100644 index 00000000..4204ce7d --- /dev/null +++ b/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(); + } + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/OtherResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/OtherResponseProcessor.java new file mode 100644 index 00000000..a0a6fa47 --- /dev/null +++ b/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 { + + /** + *

Title: process

+ *

Description:

+ * @param evt + * @param layer + * @param config + */ + @Override + public void process(ResponseEvent evt, SipLayer layer, SipConfig config) { + // TODO Auto-generated method stub + + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java b/src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java new file mode 100644 index 00000000..e0d776c2 --- /dev/null +++ b/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> + */ + public static List> listNodes(Element node, String nodeName) + { + if (null == node) + { + return null; + } + // 初始化返回 + List> listMap = new ArrayList>(); + // 首先获取当前节点的所有属性节点 + List list = node.attributes(); + + Map map = null; + // 遍历属性节点 + for (Attribute attribute : list) + { + if (nodeName.equals(node.getName())) + { + if (null == map) + { + map = new HashMap(); + listMap.add(map); + } + // 取到的节点属性放到map中 + map.put(attribute.getName(), attribute.getValue()); + } + + } + // 遍历当前节点下的所有节点 ,nodeName 要解析的节点名称 + // 使用递归 + Iterator iterator = node.elementIterator(); + while (iterator.hasNext()) + { + Element e = iterator.next(); + listMap.addAll(listNodes(e, nodeName)); + } + return listMap; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java b/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java new file mode 100644 index 00000000..d9e40e58 --- /dev/null +++ b/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 设备对象数组 + */ + public List 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); +} diff --git a/src/main/java/com/genersoft/iot/vmp/storager/VideoManagerStoragerFactory.java b/src/main/java/com/genersoft/iot/vmp/storager/VideoManagerStoragerFactory.java new file mode 100644 index 00000000..282c3e57 --- /dev/null +++ b/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; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java b/src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java new file mode 100644 index 00000000..8adc73c6 --- /dev/null +++ b/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 设备对象数组 + */ + @Override + public List 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; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java b/src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java new file mode 100644 index 00000000..cad5e1fa --- /dev/null +++ b/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 设备对象数组 + */ + @Override + public List queryVideoDeviceList(String[] deviceIds) { + List devices = new ArrayList<>(); + if (deviceIds == null || deviceIds.length == 0) { + List 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); + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/utils/redis/FastJsonRedisSerializer.java b/src/main/java/com/genersoft/iot/vmp/utils/redis/FastJsonRedisSerializer.java new file mode 100644 index 00000000..590296ab --- /dev/null +++ b/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 implements RedisSerializer { + + public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + private Class clazz; + + public FastJsonRedisSerializer(Class 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); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java b/src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java new file mode 100644 index 00000000..0427f8b7 --- /dev/null +++ b/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 集合 + 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 hmget(String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * HashSet + * @param key 键 + * @param map 值 + * @return true / false + */ + public boolean hmset(String key, Map 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 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 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 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 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 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 keys(String key) { + try { + Set set = redisTemplate.keys(key); + return new ArrayList<>(set); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java new file mode 100644 index 00000000..993ddf79 --- /dev/null +++ b/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> devices(@PathVariable String deviceId){ + + if (logger.isDebugEnabled()) { + logger.debug("查询视频设备API调用,deviceId:" + deviceId); + } + + List deviceList = new ArrayList<>(); + deviceList.add(storager.queryVideoDevice(deviceId)); + return new ResponseEntity<>(deviceList,HttpStatus.OK); + } + + @GetMapping("/devices") + public ResponseEntity> devices(){ + + if (logger.isDebugEnabled()) { + logger.debug("查询所有视频设备API调用"); + } + + List deviceList = storager.queryVideoDeviceList(null); + return new ResponseEntity<>(deviceList,HttpStatus.OK); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java new file mode 100644 index 00000000..d8b7305a --- /dev/null +++ b/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 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(ssrc,HttpStatus.OK); + } else { + logger.warn("设备预览API调用失败!"); + return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/ptz/PtzController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/ptz/PtzController.java new file mode 100644 index 00000000..842d2a89 --- /dev/null +++ b/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 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("success",HttpStatus.OK); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 00000000..4d40d270 --- /dev/null +++ b/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 \ No newline at end of file