doc/_content/introduction/compile.md
@@ -69,7 +69,7 @@ ### 5.2 编译前端页面 ```shell script cd wvp-GB28181-pro/web_src/ npm --registry=https://registry.npm.taobao.org install npm --registry=https://registry.npmmirror.com install npm run build ``` 编译如果报错, 一般都是网络问题, 导致的依赖包下载失败 src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
@@ -139,4 +139,15 @@ public static final String WVP_STREAM_GB_ID_PREFIX = "memberNo_"; public static final String WVP_STREAM_GPS_MSG_PREFIX = "WVP_STREAM_GPS_MSG_"; /** * Redis Const * 设备录像信息结果前缀 */ public static final String REDIS_RECORD_INFO_RES_PRE = "GB_RECORD_INFO_RES_"; /** * Redis Const * 设备录像信息结果前缀 */ public static final String REDIS_RECORD_INFO_RES_COUNT_PRE = "GB_RECORD_INFO_RES_COUNT:"; } src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java
@@ -2,6 +2,7 @@ import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.utils.DateUtil; import org.junit.jupiter.api.Order; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -14,6 +15,7 @@ @Configuration("mediaConfig") @Order(0) public class MediaConfig{ private final static Logger logger = LoggerFactory.getLogger(MediaConfig.class); src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java
@@ -5,6 +5,7 @@ import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.junit.jupiter.api.Order; import org.mitre.dsmiley.httpproxy.ProxyServlet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,6 +26,7 @@ */ @SuppressWarnings(value = {"rawtypes", "unchecked"}) @Configuration @Order(1) public class ProxyServletConfig { private final static Logger logger = LoggerFactory.getLogger(ProxyServletConfig.class); src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java
@@ -1,12 +1,14 @@ package com.genersoft.iot.vmp.conf; import org.junit.jupiter.api.Order; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; @Component @ConfigurationProperties(prefix = "sip", ignoreInvalidFields = true) @Order(0) public class SipConfig { private String ip; src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java
@@ -18,7 +18,7 @@ * @author lin */ @Component @Order(value=3) @Order(value=13) public class SipPlatformRunner implements CommandLineRunner { @Autowired src/main/java/com/genersoft/iot/vmp/conf/SpringDocConfig.java
@@ -1,14 +1,11 @@ package com.genersoft.iot.vmp.conf; import io.swagger.v3.oas.models.ExternalDocumentation; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; import io.swagger.v3.oas.models.media.StringSchema; import io.swagger.v3.oas.models.parameters.HeaderParameter; import org.junit.jupiter.api.Order; import org.springdoc.core.GroupedOpenApi; import org.springdoc.core.SpringDocConfigProperties; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -17,6 +14,7 @@ * @author lin */ @Configuration @Order(1) public class SpringDocConfig { @Value("${doc.enabled: true}") src/main/java/com/genersoft/iot/vmp/conf/ThreadPoolTaskConfig.java
@@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.conf; import org.junit.jupiter.api.Order; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; @@ -12,6 +13,7 @@ * @author lin */ @Configuration @Order(1) @EnableAsync(proxyTargetClass = true) public class ThreadPoolTaskConfig { src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
@@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.conf; import org.junit.jupiter.api.Order; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @@ -11,6 +12,7 @@ */ @Component @ConfigurationProperties(prefix = "user-settings", ignoreInvalidFields = true) @Order(0) public class UserSetting { private Boolean savePositionHistory = Boolean.FALSE; src/main/java/com/genersoft/iot/vmp/conf/VersionConfig.java
@@ -1,10 +1,12 @@ package com.genersoft.iot.vmp.conf; import org.junit.jupiter.api.Order; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "version") @Order(0) public class VersionConfig { private String version; src/main/java/com/genersoft/iot/vmp/conf/redis/RedisConfig.java
@@ -8,6 +8,7 @@ import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.listener.PatternTopic; @@ -22,6 +23,7 @@ * */ @Configuration @Order(value=1) public class RedisConfig extends CachingConfigurerSupport { @Autowired src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java
@@ -1,6 +1,7 @@ package com.genersoft.iot.vmp.conf.security; import com.genersoft.iot.vmp.conf.UserSetting; import org.junit.jupiter.api.Order; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -25,6 +26,7 @@ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) @Order(1) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private final static Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class); src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
@@ -19,7 +19,7 @@ import java.util.concurrent.ConcurrentHashMap; @Component @Order(value=1) @Order(value=10) public class SipLayer implements CommandLineRunner { private final static Logger logger = LoggerFactory.getLogger(SipLayer.class); src/main/java/com/genersoft/iot/vmp/gb28181/task/SipRunner.java
@@ -28,7 +28,7 @@ * @author lin */ @Component @Order(value=4) @Order(value=14) public class SipRunner implements CommandLineRunner { @Autowired @@ -69,6 +69,26 @@ // 重置cseq计数 redisCatchStorage.resetAllCSEQ(); // 清理redis // 清理数据库不存在但是redis中存在的数据 List<Device> devicesInDb = deviceService.getAll(); if (devicesInDb.size() == 0) { redisCatchStorage.removeAllDevice(); }else { List<Device> devicesInRedis = redisCatchStorage.getAllDevices(); if (devicesInRedis.size() > 0) { Map<String, Device> deviceMapInDb = new HashMap<>(); devicesInDb.parallelStream().forEach(device -> { deviceMapInDb.put(device.getDeviceId(), device); }); devicesInRedis.parallelStream().forEach(device -> { if (deviceMapInDb.get(device.getDeviceId()) == null) { redisCatchStorage.removeDevice(device.getDeviceId()); } }); } } // 查找国标推流 List<SendRtpItem> sendRtpItems = redisCatchStorage.queryAllSendRTPServer(); if (sendRtpItems.size() > 0) { src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
@@ -215,6 +215,25 @@ }else { catalogXml.append("<Address>" + channel.getAddress() + "</Address>\r\n"); } catalogXml.append("<Block>" + channel.getBlock() + "</Block>\r\n"); catalogXml.append("<SafetyWay>" + channel.getSafetyWay() + "</SafetyWay>\r\n"); catalogXml.append("<CertNum>" + channel.getCertNum() + "</CertNum>\r\n"); catalogXml.append("<Certifiable>" + channel.getCertifiable() + "</Certifiable>\r\n"); catalogXml.append("<ErrCode>" + channel.getErrCode() + "</ErrCode>\r\n"); catalogXml.append("<EndTime>" + channel.getEndTime() + "</EndTime>\r\n"); catalogXml.append("<Secrecy>" + channel.getSecrecy() + "</Secrecy>\r\n"); catalogXml.append("<IPAddress>" + channel.getIpAddress() + "</IPAddress>\r\n"); catalogXml.append("<Port>" + channel.getPort() + "</Port>\r\n"); catalogXml.append("<Password>" + channel.getPort() + "</Password>\r\n"); catalogXml.append("<Status>" + (channel.getStatus() == 1?"ON":"OFF") + "</Status>\r\n"); catalogXml.append("<Longitude>" + (channel.getLongitudeWgs84() != 0? channel.getLongitudeWgs84():channel.getLongitude()) + "</Longitude>\r\n"); catalogXml.append("<Latitude>" + (channel.getLatitudeWgs84() != 0? channel.getLatitudeWgs84():channel.getLatitude()) + "</Latitude>\r\n"); } } catalogXml.append("</Item>\r\n"); src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
@@ -360,7 +360,7 @@ return; } String username = sdp.getOrigin().getUsername(); String addressStr = sdp.getOrigin().getAddress(); String addressStr = sdp.getConnection().getAddress(); logger.info("[上级点播]用户:{}, 通道:{}, 地址:{}:{}, ssrc:{}", username, channelId, addressStr, port, ssrc); Device device = null; @@ -982,7 +982,7 @@ } return; } String addressStr = sdp.getOrigin().getAddress(); String addressStr = sdp.getConnection().getAddress(); logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}, {}", requesterId, addressStr, port, ssrc, mediaTransmissionTCP ? (tcpActive? "TCP主动":"TCP被动") : "UDP"); src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java
@@ -38,6 +38,7 @@ import javax.sip.message.Response; import java.text.ParseException; import java.util.Iterator; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; /** @@ -154,6 +155,17 @@ Element deviceIdElement = rootElement.element("DeviceID"); String channelId = deviceIdElement.getTextTrim().toString(); Device device = redisCatchStorage.getDevice(deviceId); if (device == null) { // 根据通道id查询设备Id List<Device> deviceList = deviceChannelService.getDeviceByChannelId(channelId); if (deviceList.size() > 0) { device = deviceList.get(0); }else { logger.warn("[mobilePosition移动位置Notify] 未找到通道{}所属的设备", channelId); return; } } if (device != null) { if (!ObjectUtils.isEmpty(device.getName())) { mobilePosition.setDeviceName(device.getName()); src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java
@@ -1,6 +1,5 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; import com.genersoft.iot.vmp.conf.ServiceInfo; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper; @@ -95,7 +94,7 @@ // } // } System.out.println(ServiceInfo.getServerPort()); // System.out.println(ServiceInfo.getServerPort()); SIPRequest request = (SIPRequest)evt.getRequest(); Response response = null; boolean passwordCorrect = false; src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java
@@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import gov.nist.javax.sip.message.SIPRequest; @@ -108,6 +109,7 @@ continue; } DeviceChannel deviceChannel = XmlUtil.channelContentHander(itemDevice, device, null); deviceChannel = SipUtils.updateGps(deviceChannel, device.getGeoCoordSys()); deviceChannel.setDeviceId(take.getDevice().getDeviceId()); channelList.add(deviceChannel); src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java
@@ -1,16 +1,17 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.session.RecordDataCatch; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.UJson; import com.genersoft.iot.vmp.utils.redis.RedisUtil; import gov.nist.javax.sip.message.SIPRequest; import org.dom4j.DocumentException; import org.dom4j.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,11 +27,9 @@ import javax.sip.SipException; import javax.sip.message.Response; import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.*; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.stream.Collectors; import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; @@ -49,9 +48,6 @@ private ResponseMessageHandler responseMessageHandler; @Autowired private RecordDataCatch recordDataCatch; @Autowired private DeferredResultHolder deferredResultHolder; @Autowired @@ -61,6 +57,8 @@ @Autowired private ThreadPoolTaskExecutor taskExecutor; private Long recordInfoTtl = 1800L; @Override public void afterPropertiesSet() throws Exception { responseMessageHandler.addHandler(cmdType, this); @@ -68,93 +66,93 @@ @Override public void handForDevice(RequestEvent evt, Device device, Element rootElement) { boolean isEmpty = taskQueue.isEmpty(); try { // 回复200 OK responseAck((SIPRequest) evt.getRequest(), Response.OK); }catch (SipException | InvalidArgumentException | ParseException e) { logger.error("[命令发送失败] 国标级联 国标录像: {}", e.getMessage()); } taskQueue.offer(new HandlerCatchData(evt, device, rootElement)); if (isEmpty) { taskExecutor.execute(()->{ while (!taskQueue.isEmpty()) { try { HandlerCatchData take = taskQueue.poll(); Element rootElementForCharset = getRootElement(take.getEvt(), take.getDevice().getCharset()); if (rootElement == null) { logger.warn("[ 国标录像 ] content cannot be null, {}", evt.getRequest()); continue; } String sn = getText(rootElementForCharset, "SN"); String channelId = getText(rootElementForCharset, "DeviceID"); RecordInfo recordInfo = new RecordInfo(); recordInfo.setChannelId(channelId); recordInfo.setDeviceId(take.getDevice().getDeviceId()); recordInfo.setSn(sn); recordInfo.setName(getText(rootElementForCharset, "Name")); String sumNumStr = getText(rootElementForCharset, "SumNum"); int sumNum = 0; if (!ObjectUtils.isEmpty(sumNumStr)) { sumNum = Integer.parseInt(sumNumStr); } recordInfo.setSumNum(sumNum); Element recordListElement = rootElementForCharset.element("RecordList"); if (recordListElement == null || sumNum == 0) { logger.info("无录像数据"); int count = recordDataCatch.put(take.getDevice().getDeviceId(),channelId, sn, sumNum, new ArrayList<>()); recordInfo.setCount(count); eventPublisher.recordEndEventPush(recordInfo); releaseRequest(take.getDevice().getDeviceId(), sn); } else { Iterator<Element> recordListIterator = recordListElement.elementIterator(); if (recordListIterator != null) { List<RecordItem> recordList = new ArrayList<>(); // 遍历DeviceList while (recordListIterator.hasNext()) { Element itemRecord = recordListIterator.next(); Element recordElement = itemRecord.element("DeviceID"); if (recordElement == null) { logger.info("记录为空,下一个..."); continue; } RecordItem record = new RecordItem(); record.setDeviceId(getText(itemRecord, "DeviceID")); record.setName(getText(itemRecord, "Name")); record.setFilePath(getText(itemRecord, "FilePath")); record.setFileSize(getText(itemRecord, "FileSize")); record.setAddress(getText(itemRecord, "Address")); taskExecutor.execute(()->{ try { String startTimeStr = getText(itemRecord, "StartTime"); record.setStartTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTimeStr)); String endTimeStr = getText(itemRecord, "EndTime"); record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTimeStr)); record.setSecrecy(itemRecord.element("Secrecy") == null ? 0 : Integer.parseInt(getText(itemRecord, "Secrecy"))); record.setType(getText(itemRecord, "Type")); record.setRecorderId(getText(itemRecord, "RecorderID")); recordList.add(record); } recordInfo.setRecordList(recordList); int count = recordDataCatch.put(take.getDevice().getDeviceId(),channelId, sn, sumNum, recordList);recordInfo.setCount(count); logger.info("[国标录像], {}->{}: {}/{}", take.getDevice().getDeviceId(), sn, count, sumNum); // 发送消息,如果是上级查询此录像,则会通过这里通知给上级 eventPublisher.recordEndEventPush(recordInfo); String sn = getText(rootElement, "SN"); String channelId = getText(rootElement, "DeviceID"); RecordInfo recordInfo = new RecordInfo(); recordInfo.setChannelId(channelId); recordInfo.setDeviceId(device.getDeviceId()); recordInfo.setSn(sn); recordInfo.setName(getText(rootElement, "Name")); String sumNumStr = getText(rootElement, "SumNum"); int sumNum = 0; if (!ObjectUtils.isEmpty(sumNumStr)) { sumNum = Integer.parseInt(sumNumStr); } recordInfo.setSumNum(sumNum); Element recordListElement = rootElement.element("RecordList"); if (recordListElement == null || sumNum == 0) { logger.info("无录像数据"); recordInfo.setCount(sumNum); eventPublisher.recordEndEventPush(recordInfo); releaseRequest(device.getDeviceId(), sn,recordInfo); } else { Iterator<Element> recordListIterator = recordListElement.elementIterator(); if (recordListIterator != null) { List<RecordItem> recordList = new ArrayList<>(); // 遍历DeviceList while (recordListIterator.hasNext()) { Element itemRecord = recordListIterator.next(); Element recordElement = itemRecord.element("DeviceID"); if (recordElement == null) { logger.info("记录为空,下一个..."); continue; } if (recordDataCatch.isComplete(take.getDevice().getDeviceId(), sn)){ releaseRequest(take.getDevice().getDeviceId(), sn); } RecordItem record = new RecordItem(); record.setDeviceId(getText(itemRecord, "DeviceID")); record.setName(getText(itemRecord, "Name")); record.setFilePath(getText(itemRecord, "FilePath")); record.setFileSize(getText(itemRecord, "FileSize")); record.setAddress(getText(itemRecord, "Address")); String startTimeStr = getText(itemRecord, "StartTime"); record.setStartTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTimeStr)); String endTimeStr = getText(itemRecord, "EndTime"); record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTimeStr)); record.setSecrecy(itemRecord.element("Secrecy") == null ? 0 : Integer.parseInt(getText(itemRecord, "Secrecy"))); record.setType(getText(itemRecord, "Type")); record.setRecorderId(getText(itemRecord, "RecorderID")); recordList.add(record); } } catch (DocumentException e) { logger.error("xml解析异常: ", e); } catch (Exception e) { logger.warn("[国标录像] 发现未处理的异常, {}\r\n{}",e.getMessage(), evt.getRequest()); Map<String, String> map = recordList.stream() .filter(record -> record.getDeviceId() != null) .collect(Collectors.toMap(record -> record.getStartTime()+ record.getEndTime(), UJson::writeJson)); // 获取任务结果数据 String resKey = VideoManagerConstants.REDIS_RECORD_INFO_RES_PRE + channelId + sn; RedisUtil.hmset(resKey, map, recordInfoTtl); String resCountKey = VideoManagerConstants.REDIS_RECORD_INFO_RES_COUNT_PRE + channelId + sn; long incr = RedisUtil.incr(resCountKey, map.size()); RedisUtil.expire(resCountKey, recordInfoTtl); recordInfo.setRecordList(recordList); recordInfo.setCount(Math.toIntExact(incr)); eventPublisher.recordEndEventPush(recordInfo); if (incr < sumNum) { return; } // 已接收完成 List<RecordItem> resList = RedisUtil.hmget(resKey).values().stream().map(e -> UJson.readJson(e.toString(), RecordItem.class)).collect(Collectors.toList()); if (resList.size() < sumNum) { return; } recordInfo.setRecordList(resList); releaseRequest(device.getDeviceId(), sn,recordInfo); } } }); } } catch (Exception e) { logger.error("[国标录像] 发现未处理的异常, "+e.getMessage(), e); } }); } @Override @@ -162,15 +160,14 @@ } public void releaseRequest(String deviceId, String sn){ public void releaseRequest(String deviceId, String sn,RecordInfo recordInfo){ String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + sn; // 对数据进行排序 Collections.sort(recordDataCatch.getRecordInfo(deviceId, sn).getRecordList()); Collections.sort(recordInfo.getRecordList()); RequestMessage msg = new RequestMessage(); msg.setKey(key); msg.setData(recordDataCatch.getRecordInfo(deviceId, sn)); msg.setData(recordInfo); deferredResultHolder.invokeAllResult(msg); recordDataCatch.remove(deviceId, sn); } } src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java
@@ -80,8 +80,8 @@ */ @Override public void process(ResponseEvent evt ){ logger.debug("接收到消息:" + evt.getResponse()); try { SIPResponse response = (SIPResponse)evt.getResponse(); int statusCode = response.getStatusCode(); // trying不会回复 src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java
@@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.gb28181.utils; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.RemoteAddressInfo; import com.genersoft.iot.vmp.utils.GitUtil; import gov.nist.javax.sip.address.AddressImpl; @@ -168,4 +169,37 @@ return new RemoteAddressInfo(remoteAddress, remotePort); } public static DeviceChannel updateGps(DeviceChannel deviceChannel, String geoCoordSys) { if (deviceChannel.getLongitude()*deviceChannel.getLatitude() > 0) { if (geoCoordSys == null) { geoCoordSys = "WGS84"; } if ("WGS84".equals(geoCoordSys)) { deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude()); deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude()); Double[] position = Coordtransform.WGS84ToGCJ02(deviceChannel.getLongitude(), deviceChannel.getLatitude()); deviceChannel.setLongitudeGcj02(position[0]); deviceChannel.setLatitudeGcj02(position[1]); }else if ("GCJ02".equals(geoCoordSys)) { deviceChannel.setLongitudeGcj02(deviceChannel.getLongitude()); deviceChannel.setLatitudeGcj02(deviceChannel.getLatitude()); Double[] position = Coordtransform.GCJ02ToWGS84(deviceChannel.getLongitude(), deviceChannel.getLatitude()); deviceChannel.setLongitudeWgs84(position[0]); deviceChannel.setLatitudeWgs84(position[1]); }else { deviceChannel.setLongitudeGcj02(0.00); deviceChannel.setLatitudeGcj02(0.00); deviceChannel.setLongitudeWgs84(0.00); deviceChannel.setLatitudeWgs84(0.00); } }else { deviceChannel.setLongitudeGcj02(deviceChannel.getLongitude()); deviceChannel.setLatitudeGcj02(deviceChannel.getLatitude()); deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude()); deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude()); } return deviceChannel; } } src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java
@@ -1,11 +1,9 @@ package com.genersoft.iot.vmp.gb28181.utils; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.TreeType; import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; import com.genersoft.iot.vmp.utils.DateUtil; import org.dom4j.Attribute; @@ -400,6 +398,7 @@ } else { deviceChannel.setLatitude(0.00); } deviceChannel.setGpsTime(DateUtil.getNow()); @@ -414,6 +413,7 @@ } else { deviceChannel.setPTZType(Integer.parseInt(XmlUtil.getText(itemDevice, "PTZType"))); } return deviceChannel; } src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
@@ -25,7 +25,7 @@ import java.util.concurrent.ConcurrentHashMap; @Component @Order(value=2) @Order(value=12) public class ZLMRunner implements CommandLineRunner { private final static Logger logger = LoggerFactory.getLogger(ZLMRunner.class); src/main/java/com/genersoft/iot/vmp/service/IDeviceChannelService.java
@@ -46,4 +46,14 @@ * @return */ List<ChannelReduce> queryAllChannelList(String platformId); /** * 数据位置信息格式处理 */ boolean updateAllGps(Device device); /** * 查询通道所属的设备 */ List<Device> getDeviceByChannelId(String channelId); } src/main/java/com/genersoft/iot/vmp/service/IDeviceService.java
@@ -163,4 +163,8 @@ */ ResourceBaceInfo getOverview(); /** * 获取所有设备 */ List<Device> getAll(); } src/main/java/com/genersoft/iot/vmp/service/impl/DeviceChannelServiceImpl.java
@@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** * @author lin @@ -176,5 +177,36 @@ return channelMapper.queryChannelListInAll(null, null, null, platformId, null); } @Override public boolean updateAllGps(Device device) { List<DeviceChannel> deviceChannels = channelMapper.getChannelsWithoutTransform(device.getDeviceId()); List<DeviceChannel> result = new CopyOnWriteArrayList<>(); if (deviceChannels.size() == 0) { return true; } String now = DateUtil.getNow(); deviceChannels.parallelStream().forEach(deviceChannel -> { deviceChannel.setUpdateTime(now); result.add(updateGps(deviceChannel, device)); }); int limitCount = 300; if (result.size() > limitCount) { for (int i = 0; i < result.size(); i += limitCount) { int toIndex = i + limitCount; if (i + limitCount > result.size()) { toIndex = result.size(); } channelMapper.batchUpdate(result.subList(i, toIndex)); } }else { channelMapper.batchUpdate(result); } return true; } @Override public List<Device> getDeviceByChannelId(String channelId) { return channelMapper.getDeviceByChannelId(channelId); } } src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
@@ -655,4 +655,9 @@ public ResourceBaceInfo getOverview() { return deviceMapper.getOverview(); } @Override public List<Device> getAll() { return deviceMapper.getAll(); } } src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java
@@ -366,7 +366,7 @@ // 存储数据到stream_push表 streamPushMapper.addAll(streamPushItems); List<StreamPushItem> streamPushItemForGbStream = streamPushItems.stream() .filter(streamPushItem-> streamPushItem.getId() != null) .filter(streamPushItem-> streamPushItem.getGbId() != null) .collect(Collectors.toList()); // 存储数据到gb_stream表, id会返回到streamPushItemForGbStream里 if (streamPushItemForGbStream.size() > 0) { src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
@@ -258,4 +258,7 @@ List<SendRtpItem> queryAllSendRTPServer(); List<Device> getAllDevices(); void removeAllDevice(); } src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java
@@ -56,7 +56,7 @@ * @param count 每页数量 * @return */ public PageInfo queryChannelsByDeviceId(String deviceId, String query, Boolean hasSubChannel, Boolean online, Boolean catalogUnderDevice, int page, int count); public PageInfo<DeviceChannel> queryChannelsByDeviceId(String deviceId, String query, Boolean hasSubChannel, Boolean online, Boolean catalogUnderDevice, int page, int count); public List<DeviceChannel> queryChannelsByDeviceIdWithStartAndLimit(String deviceId, String query, Boolean hasSubChannel, Boolean online, int start, int limit,List<String> channelIds); src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java
@@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.storager.dao; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannelInPlatform; import com.genersoft.iot.vmp.vmanager.bean.ResourceBaceInfo; @@ -357,4 +358,21 @@ @Select("select count(1) as total, sum(status) as online from device_channel") ResourceBaceInfo getOverview(); @Select("select channelId" + ", deviceId" + ", latitude" + ", longitude" + ", latitudeWgs84" + ", longitudeWgs84" + ", latitudeGcj02" + ", longitudeGcj02 " + "from device_channel where deviceId = #{deviceId} " + "and latitude != 0 " + "and longitude != 0 " + "and (latitudeGcj02 = 0 or latitudeWgs84 = 0 or longitudeWgs84 = 0 or longitudeGcj02 = 0)") List<DeviceChannel> getChannelsWithoutTransform(String deviceId); @Select("select de.* from device de left join device_channel dc on de.deviceId = dc.deviceId where dc.channelId=#{channelId}") List<Device> getDeviceByChannelId(String channelId); } src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java
@@ -280,4 +280,6 @@ @Select("select count(1) as total, sum(online) as online from device") ResourceBaceInfo getOverview(); @Select("select * from device") List<Device> getAll(); } src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
@@ -665,6 +665,31 @@ } @Override public void removeAllDevice() { String scanKey = VideoManagerConstants.DEVICE_PREFIX + userSetting.getServerId() + "_*"; List<Object> keys = RedisUtil.scan(scanKey); for (Object key : keys) { RedisUtil.del((String) key); } } @Override public List<Device> getAllDevices() { String scanKey = VideoManagerConstants.DEVICE_PREFIX + userSetting.getServerId() + "_*"; List<Device> result = new ArrayList<>(); List<Object> keys = RedisUtil.scan(scanKey); for (Object o : keys) { String key = (String) o; Device device = JsonUtil.redisJsonToObject(key, Device.class); if (Objects.nonNull(device)) { // 只取没有存过得 result.add(JsonUtil.redisJsonToObject(key, Device.class)); } } return result; } @Override public Device getDevice(String deviceId) { String key = VideoManagerConstants.DEVICE_PREFIX + userSetting.getServerId() + "_" + deviceId; return JsonUtil.redisJsonToObject(key, Device.class); src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
@@ -226,8 +226,10 @@ if (allChannelMap.containsKey(deviceChannel.getChannelId())) { deviceChannel.setStreamId(allChannelMap.get(deviceChannel.getChannelId()).getStreamId()); deviceChannel.setHasAudio(allChannelMap.get(deviceChannel.getChannelId()).isHasAudio()); deviceChannel.setUpdateTime(DateUtil.getNow()); updateChannels.add(deviceChannel); }else { deviceChannel.setCreateTime(DateUtil.getNow()); addChannels.add(deviceChannel); } if (!ObjectUtils.isEmpty(deviceChannel.getParentId())) { src/main/java/com/genersoft/iot/vmp/utils/UJson.java
New file @@ -0,0 +1,150 @@ package com.genersoft.iot.vmp.utils; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Iterator; import java.util.Map; import java.util.Objects; /** * @author gaofuwang * @version 1.0 * @date 2022/3/11 10:17 */ public class UJson { private static Logger logger = LoggerFactory.getLogger(UJson.class); public static final ObjectMapper JSON_MAPPER = new ObjectMapper(); static { JSON_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false); } private ObjectNode node; public UJson(){ this.node = JSON_MAPPER.createObjectNode(); } public UJson(String json){ if(StringUtils.isBlank(json)){ this.node = JSON_MAPPER.createObjectNode(); }else{ try { this.node = JSON_MAPPER.readValue(json, ObjectNode.class); }catch (Exception e){ logger.error(e.getMessage(), e); this.node = JSON_MAPPER.createObjectNode(); } } } public UJson(ObjectNode node){ this.node = node; } public String asText(String key){ JsonNode jsonNode = node.get(key); if(Objects.isNull(jsonNode)){ return ""; } return jsonNode.asText(); } public String asText(String key, String defaultVal){ JsonNode jsonNode = node.get(key); if(Objects.isNull(jsonNode)){ return ""; } return jsonNode.asText(defaultVal); } public UJson put(String key, String value){ this.node.put(key, value); return this; } public UJson put(String key, Integer value){ this.node.put(key, value); return this; } public static UJson json(){ return new UJson(); } public static UJson json(String json){ return new UJson(json); } public static <T> T readJson(String json, Class<T> clazz){ if(StringUtils.isBlank(json)){ return null; } try { return JSON_MAPPER.readValue(json, clazz); }catch (Exception e){ logger.error(e.getMessage(), e); return null; } } public static String writeJson(Object object) { try{ return JSON_MAPPER.writeValueAsString(object); }catch (Exception e){ logger.error(e.getMessage(), e); return ""; } } @Override public String toString() { return node.toString(); } public int asInt(String key, int defValue) { JsonNode jsonNode = this.node.get(key); if(Objects.isNull(jsonNode)){ return defValue; } return jsonNode.asInt(defValue); } public UJson getSon(String key) { JsonNode sonNode = this.node.get(key); if(Objects.isNull(sonNode)){ return new UJson(); } return new UJson((ObjectNode) sonNode); } public UJson set(String key, ObjectNode sonNode) { this.node.set(key, sonNode); return this; } public UJson set(String key, UJson sonNode) { this.node.set(key, sonNode.node); return this; } public Iterator<Map.Entry<String, JsonNode>> fields() { return this.node.fields(); } public ObjectNode getNode() { return this.node; } public UJson setAll(UJson json) { this.node.setAll(json.node); return this; } } src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java
@@ -238,7 +238,7 @@ * @param time 时间 * @return true / false */ public static boolean hmset(String key, Map<Object, Object> map, long time) { public static boolean hmset(String key, Map<?, ?> map, long time) { if (redisTemplate == null) { redisTemplate = SpringBeanFactory.getBean("redisTemplate"); } src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamContent.java
@@ -1,43 +1,96 @@ package com.genersoft.iot.vmp.vmanager.bean; import com.genersoft.iot.vmp.common.StreamInfo; import io.swagger.v3.oas.annotations.media.Schema; @Schema(description = "流信息") public class StreamContent { @Schema(description = "应用名") private String app; @Schema(description = "流ID") private String stream; @Schema(description = "IP") private String ip; @Schema(description = "HTTP-FLV流地址") private String flv; @Schema(description = "HTTPS-FLV流地址") private String https_flv; @Schema(description = "Websocket-FLV流地址") private String ws_flv; @Schema(description = "Websockets-FLV流地址") private String wss_flv; @Schema(description = "HTTP-FMP4流地址") private String fmp4; @Schema(description = "HTTPS-FMP4流地址") private String https_fmp4; @Schema(description = "Websocket-FMP4流地址") private String ws_fmp4; @Schema(description = "Websockets-FMP4流地址") private String wss_fmp4; @Schema(description = "HLS流地址") private String hls; @Schema(description = "HTTPS-HLS流地址") private String https_hls; @Schema(description = "Websocket-HLS流地址") private String ws_hls; @Schema(description = "Websockets-HLS流地址") private String wss_hls; @Schema(description = "HTTP-TS流地址") private String ts; @Schema(description = "HTTPS-TS流地址") private String https_ts; @Schema(description = "Websocket-TS流地址") private String ws_ts; @Schema(description = "Websockets-TS流地址") private String wss_ts; @Schema(description = "RTMP流地址") private String rtmp; @Schema(description = "RTMPS流地址") private String rtmps; @Schema(description = "RTSP流地址") private String rtsp; @Schema(description = "RTSPS流地址") private String rtsps; @Schema(description = "RTC流地址") private String rtc; @Schema(description = "RTCS流地址") private String rtcs; @Schema(description = "流媒体ID") private String mediaServerId; @Schema(description = "流编码信息") private Object tracks; @Schema(description = "开始时间") private String startTime; @Schema(description = "结束时间") private String endTime; private double progress; src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/MobilePosition/MobilePositionController.java
@@ -1,39 +1,30 @@ package com.genersoft.iot.vmp.vmanager.gb28181.MobilePosition; import java.text.ParseException; import java.util.List; import java.util.UUID; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.MobilePosition; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.service.IDeviceChannelService; import com.genersoft.iot.vmp.service.IDeviceService; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.github.pagehelper.util.StringUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; 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.CrossOrigin; 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.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import java.text.ParseException; import java.util.List; import java.util.UUID; /** * 位置信息管理 @@ -57,6 +48,9 @@ @Autowired private IDeviceService deviceService; @Autowired private IDeviceChannelService deviceChannelService; /** * 查询历史轨迹 @@ -162,4 +156,24 @@ throw new ControllerException(ErrorCode.ERROR100); } } /** * 数据位置信息格式处理 * @param deviceId 设备ID * @return true = 命令发送成功 */ @Operation(summary = "数据位置信息格式处理") @Parameter(name = "deviceId", description = "设备国标编号", required = true) @GetMapping("/transform/{deviceId}") public void positionTransform(@PathVariable String deviceId) { Device device = deviceService.getDevice(deviceId); if (device == null) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "未找到设备: " + deviceId); } boolean result = deviceChannelService.updateAllGps(device); if (!result) { throw new ControllerException(ErrorCode.ERROR100); } } } src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java
@@ -123,7 +123,7 @@ @Parameter(name = "online", description = "是否在线") @Parameter(name = "channelType", description = "设备/子目录-> false/true") @Parameter(name = "catalogUnderDevice", description = "是否直属与设备的目录") public PageInfo channels(@PathVariable String deviceId, public PageInfo<DeviceChannel> channels(@PathVariable String deviceId, int page, int count, @RequestParam(required = false) String query, @RequestParam(required = false) Boolean online, @@ -223,7 +223,7 @@ @Parameter(name = "online", description = "是否在线") @Parameter(name = "channelType", description = "设备/子目录-> false/true") @GetMapping("/sub_channels/{deviceId}/{channelId}/channels") public PageInfo subChannels(@PathVariable String deviceId, public PageInfo<DeviceChannel> subChannels(@PathVariable String deviceId, @PathVariable String channelId, int page, int count, @@ -237,8 +237,7 @@ return deviceChannelPageResult; } PageInfo pageResult = storager.querySubChannels(deviceId, channelId, query, channelType, online, page, count); return pageResult; return storager.querySubChannels(deviceId, channelId, query, channelType, online, page, count); } /** src/main/resources/logback-spring-local.xml
@@ -4,7 +4,7 @@ <springProperty scop="context" name="spring.application.name" source="spring.application.name" defaultValue=""/> <property name="LOG_HOME" value="logs" /> <substitutionProperty name="log.pattern" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr(%-80.80logger{79}){cyan} %clr(:){faint} %m%n%wEx"/> <substitutionProperty name="log.pattern" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr(%-1.30logger{0}){cyan} %clr(:){faint} %m%n%wEx"/> <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/> <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/> web_src/src/components/CloudRecordDetail.vue
@@ -115,7 +115,7 @@ props: ['recordFile', 'mediaServerId', 'dateFiles', 'mediaServerPath'], data() { return { basePath: `${this.mediaServerPath}/record`, basePath: `${this.mediaServerPath}`, dateFilesObj: [], detailFiles: [], chooseDate: null,