diff --git a/README.md b/README.md index 64ec4090a..f95d7a03e 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git - [X] 支持推流鉴权 - [X] 支持接口鉴权 - [X] 云端录像,推流/代理/国标视频均可以录制在云端服务器,支持预览和下载 +- [X] 支持打包可执行jar和war # 遇到问题如何解决 diff --git a/doc/_content/introduction/compile.md b/doc/_content/introduction/compile.md index b208685b5..cd3064036 100644 --- a/doc/_content/introduction/compile.md +++ b/doc/_content/introduction/compile.md @@ -77,13 +77,18 @@ npm run build **编译完成一般是这个样子,中间没有报红的错误信息** ![编译成功](_media/img.png) -### 5.3 打包项目, 生成可执行jar +### 5.3 生成可执行jar ```bash cd wvp-GB28181-pro mvn package ``` +### 5.4 生成war +```bash +cd wvp-GB28181-pro +mvn package -P war +``` 编译如果报错, 一般都是网络问题, 导致的依赖包下载失败 -编译完成后在target目录下出现wvp-pro-***.jar。 +编译完成后在target目录下出现wvp-pro-***.jar/wvp-pro-***.war。 接下来[配置服务](./_content/introduction/config.md) diff --git a/doc/_content/introduction/config.md b/doc/_content/introduction/config.md index feb7487c0..ba8d564ea 100644 --- a/doc/_content/introduction/config.md +++ b/doc/_content/introduction/config.md @@ -31,6 +31,7 @@ java -jar wvp-pro-*.jar ## 2 配置WVP-PRO ### 2.1 Mysql数据库配置 首先你需要创建一个名为wvp(也可使用其他名字)的数据库,并使用sql/mysql.sql导入数据库,初始化数据库结构。 +(这里注意,取决于版本,新版的sql文件夹下有update.sql,补丁包,一定要注意运行导入) 在application-dev.yml中配置(使用1.2方式的是在jar包的同级目录的application.yml)配置数据库连接,包括数据库连接信息,密码。 ### 2.2 Redis数据库配置 配置wvp中的redis连接信息,建议wvp自己单独使用一个db。 @@ -116,4 +117,4 @@ user-settings: 如果配置信息无误,你可以启动zlm,再启动wvp来测试了,启动成功的话,你可以在wvp的日志下看到zlm已连接的提示。 -接下来[部署到服务器](./_content/introduction/deployment.md), 如何你只是本地运行直接再本地运行即可。 \ No newline at end of file +接下来[部署到服务器](./_content/introduction/deployment.md), 如何你只是本地运行直接再本地运行即可。 diff --git a/doc/_content/introduction/deployment.md b/doc/_content/introduction/deployment.md index 9635fe49b..3f13867bc 100644 --- a/doc/_content/introduction/deployment.md +++ b/doc/_content/introduction/deployment.md @@ -27,7 +27,9 @@ ```shell nohup java -jar wvp-pro-*.jar & ``` - +war包: +下载Tomcat后将war包放入webapps中,启动Tomcat以解压war包,停止Tomcat后,删除ROOT目录以及war包,将解压后的war包目录重命名为ROOT, +然后启动Tomcat。 **启动ZLM** ```shell nohup ./MediaServer -d -m 3 & diff --git a/pom.xml b/pom.xml index efc311a19..cbcbb1118 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,7 @@ 2.6.7 web video platform 国标28181视频平台 + ${project.packaging} @@ -56,6 +57,42 @@ ${project.build.directory}/asciidoc/pdf + + + jar + + true + + + jar + + + + war + + war + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-jetty + + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + + + org.springframework.boot @@ -242,8 +279,8 @@ spring-boot-starter-test - + diff --git a/sql/update.sql b/sql/update.sql deleted file mode 100644 index 2e5d5691b..000000000 --- a/sql/update.sql +++ /dev/null @@ -1,3 +0,0 @@ --- 2.6.6->2.6.7 -alter table device - add keepaliveIntervalTime int default null; \ No newline at end of file diff --git a/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java b/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java index af3340dd8..eab220793 100644 --- a/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java +++ b/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java @@ -1,20 +1,24 @@ package com.genersoft.iot.vmp; -import java.util.logging.LogManager; - import com.genersoft.iot.vmp.conf.druid.EnableDruidSupport; -import com.genersoft.iot.vmp.storager.impl.RedisCatchStorageImpl; import com.genersoft.iot.vmp.utils.GitUtil; import com.genersoft.iot.vmp.utils.SpringBeanFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.ServletComponentScan; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.scheduling.annotation.EnableScheduling; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.SessionCookieConfig; +import javax.servlet.SessionTrackingMode; +import java.util.Collections; + /** * 启动类 */ @@ -22,7 +26,7 @@ import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling @EnableDruidSupport -public class VManageBootstrap extends LogManager { +public class VManageBootstrap extends SpringBootServletInitializer { private final static Logger logger = LoggerFactory.getLogger(VManageBootstrap.class); @@ -41,6 +45,21 @@ public class VManageBootstrap extends LogManager { context.close(); VManageBootstrap.context = SpringApplication.run(VManageBootstrap.class, args); } - + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(VManageBootstrap.class); + } + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + super.onStartup(servletContext); + + servletContext.setSessionTrackingModes( + Collections.singleton(SessionTrackingMode.COOKIE) + ); + SessionCookieConfig sessionCookieConfig = servletContext.getSessionCookieConfig(); + sessionCookieConfig.setHttpOnly(true); + + } } diff --git a/src/main/java/com/genersoft/iot/vmp/common/enums/DeviceControlType.java b/src/main/java/com/genersoft/iot/vmp/common/enums/DeviceControlType.java new file mode 100644 index 000000000..02202d89c --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/common/enums/DeviceControlType.java @@ -0,0 +1,77 @@ +package com.genersoft.iot.vmp.common.enums; + +import org.dom4j.Element; +import org.springframework.util.ObjectUtils; + + +/** + * @author gaofuwang + * @date 2023/01/18/ 10:09:00 + * @since 1.0 + */ +public enum DeviceControlType { + + /** + * 云台控制 + * 上下左右,预置位,扫描,辅助功能,巡航 + */ + PTZ("PTZCmd","云台控制"), + /** + * 远程启动 + */ + TELE_BOOT("TeleBoot","远程启动"), + /** + * 录像控制 + */ + RECORD("RecordCmd","录像控制"), + /** + * 布防撤防 + */ + GUARD("GuardCmd","布防撤防"), + /** + * 告警控制 + */ + ALARM("AlarmCmd","告警控制"), + /** + * 强制关键帧 + */ + I_FRAME("IFameCmd","强制关键帧"), + /** + * 拉框放大 + */ + DRAG_ZOOM_IN("DragZoomIn","拉框放大"), + /** + * 拉框缩小 + */ + DRAG_ZOOM_OUT("DragZoomOut","拉框缩小"), + /** + * 看守位 + */ + HOME_POSITION("HomePosition","看守位"); + + private final String val; + + private final String desc; + + DeviceControlType(String val, String desc) { + this.val = val; + this.desc = desc; + } + + public String getVal() { + return val; + } + + public String getDesc() { + return desc; + } + + public static DeviceControlType typeOf(Element rootElement) { + for (DeviceControlType item : DeviceControlType.values()) { + if (!ObjectUtils.isEmpty(rootElement.element(item.val)) || !ObjectUtils.isEmpty(rootElement.elements(item.val))) { + return item; + } + } + return null; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java b/src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java index d1e61c0a3..7133e14eb 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java @@ -10,6 +10,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import org.springframework.web.filter.OncePerRequestFilter; @@ -23,6 +24,7 @@ import java.io.IOException; * @author lin */ @WebFilter(filterName = "ApiAccessFilter", urlPatterns = "/api/*", asyncSupported=true) +@Component public class ApiAccessFilter extends OncePerRequestFilter { private final static Logger logger = LoggerFactory.getLogger(ApiAccessFilter.class); @@ -48,7 +50,7 @@ public class ApiAccessFilter extends OncePerRequestFilter { filterChain.doFilter(servletRequest, servletResponse); - if (uriName != null && userSetting.getLogInDatebase()) { + if (uriName != null && userSetting != null && userSetting.getLogInDatebase() != null && userSetting.getLogInDatebase()) { LogDto logDto = new LogDto(); logDto.setName(uriName); diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java index d0abfbf7c..c4086b9ec 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java @@ -2,7 +2,6 @@ package com.genersoft.iot.vmp.conf; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.service.IMediaServerService; -import org.apache.catalina.connector.ClientAbortException; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; @@ -15,11 +14,9 @@ import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.ConnectException; @@ -77,9 +74,7 @@ public class ProxyServletConfig { } catch (IOException ioException) { if (ioException instanceof ConnectException) { logger.error("zlm 连接失败"); - } else if (ioException instanceof ClientAbortException) { - logger.error("zlm: 用户已中断连接,代理终止"); - } else { + } else { logger.error("zlm 代理失败: ", e); } } catch (RuntimeException exception){ @@ -195,9 +190,7 @@ public class ProxyServletConfig { } catch (IOException ioException) { if (ioException instanceof ConnectException) { logger.error("录像服务 连接失败"); - } else if (ioException instanceof ClientAbortException) { - logger.error("录像服务:用户已中断连接,代理终止"); - } else { + }else { logger.error("录像服务 代理失败: ", e); } } catch (RuntimeException exception){ diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ServiceInfo.java b/src/main/java/com/genersoft/iot/vmp/conf/ServiceInfo.java new file mode 100644 index 000000000..55fbcf48f --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/conf/ServiceInfo.java @@ -0,0 +1,30 @@ +package com.genersoft.iot.vmp.conf; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.web.context.WebServerInitializedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +@Component +public class ServiceInfo implements ApplicationListener { + + private final Logger logger = LoggerFactory.getLogger(ServiceInfo.class); + + private static int serverPort; + + public static int getServerPort() { + return serverPort; + } + + @Override + public void onApplicationEvent(WebServerInitializedEvent event) { + // 项目启动获取启动的端口号 + ServiceInfo.serverPort = event.getWebServer().getPort(); + logger.info("项目启动获取启动的端口号: " + ServiceInfo.serverPort); + } + + public void setServerPort(int serverPort) { + ServiceInfo.serverPort = serverPort; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/UrlTokenHandler.java b/src/main/java/com/genersoft/iot/vmp/conf/security/UrlTokenHandler.java deleted file mode 100644 index e63aca4a5..000000000 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/UrlTokenHandler.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.genersoft.iot.vmp.conf.security; - -import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; - -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.SessionCookieConfig; -import javax.servlet.SessionTrackingMode; -import java.util.Collections; - -public class UrlTokenHandler extends SpringBootServletInitializer { - - @Override - public void onStartup(ServletContext servletContext) throws ServletException { - super.onStartup(servletContext); - - servletContext.setSessionTrackingModes( - Collections.singleton(SessionTrackingMode.COOKIE) - ); - SessionCookieConfig sessionCookieConfig = servletContext.getSessionCookieConfig(); - sessionCookieConfig.setHttpOnly(true); - - } -} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/AlarmChannelMessage.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/AlarmChannelMessage.java index 96d25c4eb..7f50f4d37 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/AlarmChannelMessage.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/AlarmChannelMessage.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.gb28181.bean; + /** * 通过redis分发报警消息 */ @@ -8,12 +9,14 @@ public class AlarmChannelMessage { * 国标编号 */ private String gbId; - /** * 报警编号 */ private int alarmSn; - + /** + * 告警类型 + */ + private int alarmType; /** * 报警描述 @@ -36,6 +39,14 @@ public class AlarmChannelMessage { this.alarmSn = alarmSn; } + public int getAlarmType() { + return alarmType; + } + + public void setAlarmType(int alarmType) { + this.alarmType = alarmType; + } + public String getAlarmDescription() { return alarmDescription; } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarmMethod.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarmMethod.java index ff8761eef..d1fb6db61 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarmMethod.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarmMethod.java @@ -37,4 +37,18 @@ public enum DeviceAlarmMethod { public int getVal() { return val; } + + /** + * 查询是否匹配类型 + * @param code + * @return + */ + public static DeviceAlarmMethod typeOf(int code) { + for (DeviceAlarmMethod item : DeviceAlarmMethod.values()) { + if (code==item.getVal()) { + return item; + } + } + return null; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DragZoomRequest.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DragZoomRequest.java new file mode 100644 index 000000000..86fdb4d29 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DragZoomRequest.java @@ -0,0 +1,143 @@ +package com.genersoft.iot.vmp.gb28181.bean; + +import com.genersoft.iot.vmp.gb28181.utils.MessageElement; + +/** + * 设备信息查询响应 + * + * @author Y.G + * @version 1.0 + * @date 2022/6/28 14:55 + */ +public class DragZoomRequest { + /** + * 序列号 + */ + @MessageElement("SN") + private String sn; + + @MessageElement("DeviceID") + private String deviceId; + + @MessageElement(value = "DragZoomIn") + private DragZoom dragZoomIn; + + @MessageElement(value = "DragZoomOut") + private DragZoom dragZoomOut; + + /** + * 基本参数 + */ + public static class DragZoom { + /** + * 播放窗口长度像素值 + */ + @MessageElement("Length") + protected Integer length; + /** + * 播放窗口宽度像素值 + */ + @MessageElement("Width") + protected Integer width; + /** + * 拉框中心的横轴坐标像素值 + */ + @MessageElement("MidPointX") + protected Integer midPointX; + /** + * 拉框中心的纵轴坐标像素值 + */ + @MessageElement("MidPointY") + protected Integer midPointY; + /** + * 拉框长度像素值 + */ + @MessageElement("LengthX") + protected Integer lengthX; + /** + * 拉框宽度像素值 + */ + @MessageElement("LengthY") + protected Integer lengthY; + + public Integer getLength() { + return length; + } + + public void setLength(Integer length) { + this.length = length; + } + + public Integer getWidth() { + return width; + } + + public void setWidth(Integer width) { + this.width = width; + } + + public Integer getMidPointX() { + return midPointX; + } + + public void setMidPointX(Integer midPointX) { + this.midPointX = midPointX; + } + + public Integer getMidPointY() { + return midPointY; + } + + public void setMidPointY(Integer midPointY) { + this.midPointY = midPointY; + } + + public Integer getLengthX() { + return lengthX; + } + + public void setLengthX(Integer lengthX) { + this.lengthX = lengthX; + } + + public Integer getLengthY() { + return lengthY; + } + + public void setLengthY(Integer lengthY) { + this.lengthY = lengthY; + } + } + + public String getSn() { + return sn; + } + + public void setSn(String sn) { + this.sn = sn; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public DragZoom getDragZoomIn() { + return dragZoomIn; + } + + public void setDragZoomIn(DragZoom dragZoomIn) { + this.dragZoomIn = dragZoomIn; + } + + public DragZoom getDragZoomOut() { + return dragZoomOut; + } + + public void setDragZoomOut(DragZoom dragZoomOut) { + this.dragZoomOut = dragZoomOut; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/HomePositionRequest.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/HomePositionRequest.java new file mode 100644 index 000000000..2c20713bb --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/HomePositionRequest.java @@ -0,0 +1,94 @@ +package com.genersoft.iot.vmp.gb28181.bean; + +import com.genersoft.iot.vmp.gb28181.utils.MessageElement; + +/** + * 设备信息查询响应 + * + * @author Y.G + * @version 1.0 + * @date 2022/6/28 14:55 + */ +public class HomePositionRequest { + /** + * 序列号 + */ + @MessageElement("SN") + private String sn; + + @MessageElement("DeviceID") + private String deviceId; + + @MessageElement(value = "HomePosition") + private HomePosition homePosition; + + + /** + * 基本参数 + */ + public static class HomePosition { + /** + * 播放窗口长度像素值 + */ + @MessageElement("Enabled") + protected String enabled; + /** + * 播放窗口宽度像素值 + */ + @MessageElement("ResetTime") + protected String resetTime; + /** + * 拉框中心的横轴坐标像素值 + */ + @MessageElement("PresetIndex") + protected String presetIndex; + + public String getEnabled() { + return enabled; + } + + public void setEnabled(String enabled) { + this.enabled = enabled; + } + + public String getResetTime() { + return resetTime; + } + + public void setResetTime(String resetTime) { + this.resetTime = resetTime; + } + + public String getPresetIndex() { + return presetIndex; + } + + public void setPresetIndex(String presetIndex) { + this.presetIndex = presetIndex; + } + } + + public String getSn() { + return sn; + } + + public void setSn(String sn) { + this.sn = sn; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public HomePosition getHomePosition() { + return homePosition; + } + + public void setHomePosition(HomePosition homePosition) { + this.homePosition = homePosition; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java index 2121db7a5..41aebf5d9 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.gb28181.bean; + import java.time.Instant; import java.util.List; @@ -20,6 +21,8 @@ public class RecordInfo { private int sumNum; + private int count; + private Instant lastTime; private List recordList; @@ -79,4 +82,12 @@ public class RecordInfo { public void setLastTime(Instant lastTime) { this.lastTime = lastTime; } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEventListener.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEventListener.java index 92a435170..cb4682304 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEventListener.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEventListener.java @@ -1,8 +1,10 @@ package com.genersoft.iot.vmp.gb28181.event.record; import com.genersoft.iot.vmp.gb28181.bean.RecordInfo; +import com.genersoft.iot.vmp.utils.redis.RedisUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; @@ -20,25 +22,46 @@ public class RecordEndEventListener implements ApplicationListener handlerMap = new ConcurrentHashMap<>(); public interface RecordEndEventHandler{ void handler(RecordInfo recordInfo); } - private Map handlerMap = new ConcurrentHashMap<>(); - @Override public void onApplicationEvent(RecordEndEvent event) { - logger.info("录像查询完成事件触发,deviceId:{}, channelId: {}, 录像数量{}条", event.getRecordInfo().getDeviceId(), - event.getRecordInfo().getChannelId(), event.getRecordInfo().getSumNum() ); + String deviceId = event.getRecordInfo().getDeviceId(); + String channelId = event.getRecordInfo().getChannelId(); + int count = event.getRecordInfo().getCount(); + int sumNum = event.getRecordInfo().getSumNum(); + logger.info("录像查询完成事件触发,deviceId:{}, channelId: {}, 录像数量{}/{}条", event.getRecordInfo().getDeviceId(), + event.getRecordInfo().getChannelId(), count,sumNum); if (handlerMap.size() > 0) { - for (RecordEndEventHandler recordEndEventHandler : handlerMap.values()) { - recordEndEventHandler.handler(event.getRecordInfo()); + RecordEndEventHandler handler = handlerMap.get(deviceId + channelId); + if (handler !=null){ + handler.handler(event.getRecordInfo()); + if (count ==sumNum){ + handlerMap.remove(deviceId + channelId); + } } } - handlerMap.clear(); } + /** + * 添加 + * @param device + * @param channelId + * @param recordEndEventHandler + */ public void addEndEventHandler(String device, String channelId, RecordEndEventHandler recordEndEventHandler) { handlerMap.put(device + channelId, recordEndEventHandler); } + /** + * 添加 + * @param device + * @param channelId + */ + public void delEndEventHandler(String device, String channelId) { + handlerMap.remove(device + channelId); + } + } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java b/src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java index 12e0ba534..38cdf7c45 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java @@ -109,12 +109,18 @@ public class CatalogDataCatch { for (String deviceId : keys) { CatalogData catalogData = data.get(deviceId); - if ( catalogData.getLastTime().isBefore(instantBefore5S)) { // 超过五秒收不到消息任务超时, 只更新这一部分数据 + if ( catalogData.getLastTime().isBefore(instantBefore5S)) { + // 超过五秒收不到消息任务超时, 只更新这一部分数据, 收到数据与声明的总数一致,则重置通道数据,数据不全则只对收到的数据做更新操作 if (catalogData.getStatus().equals(CatalogData.CatalogDataStatus.runIng)) { - storager.resetChannels(catalogData.getDevice().getDeviceId(), catalogData.getChannelList()); + if (catalogData.getTotal() == catalogData.getChannelList().size()) { + storager.resetChannels(catalogData.getDevice().getDeviceId(), catalogData.getChannelList()); + }else { + storager.updateChannels(catalogData.getDevice().getDeviceId(), catalogData.getChannelList()); + } + String errorMsg = "更新成功,共" + catalogData.getTotal() + "条,已更新" + catalogData.getChannelList().size() + "条"; + catalogData.setErrorMsg(errorMsg); if (catalogData.getTotal() != catalogData.getChannelList().size()) { - String errorMsg = "更新成功,共" + catalogData.getTotal() + "条,已更新" + catalogData.getChannelList().size() + "条"; - catalogData.setErrorMsg(errorMsg); + } }else if (catalogData.getStatus().equals(CatalogData.CatalogDataStatus.ready)) { String errorMsg = "同步失败,等待回复超时"; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/session/RecordDataCatch.java b/src/main/java/com/genersoft/iot/vmp/gb28181/session/RecordDataCatch.java index 1d2b34b0a..3f24dbee4 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/session/RecordDataCatch.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/session/RecordDataCatch.java @@ -1,6 +1,7 @@ package com.genersoft.iot.vmp.gb28181.session; import com.genersoft.iot.vmp.gb28181.bean.*; +import com.genersoft.iot.vmp.gb28181.event.record.RecordEndEventListener; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; @@ -23,14 +24,17 @@ public class RecordDataCatch { @Autowired private DeferredResultHolder deferredResultHolder; + @Autowired + private RecordEndEventListener recordEndEventListener; - public int put(String deviceId, String sn, int sumNum, List recordItems) { + public int put(String deviceId,String channelId, String sn, int sumNum, List recordItems) { String key = deviceId + sn; RecordInfo recordInfo = data.get(key); if (recordInfo == null) { recordInfo = new RecordInfo(); recordInfo.setDeviceId(deviceId); + recordInfo.setChannelId(channelId); recordInfo.setSn(sn.trim()); recordInfo.setSumNum(sumNum); recordInfo.setRecordList(Collections.synchronizedList(new ArrayList<>())); @@ -67,6 +71,7 @@ public class RecordDataCatch { msg.setKey(msgKey); msg.setData(recordInfo); deferredResultHolder.invokeAllResult(msg); + recordEndEventListener.delEndEventHandler(recordInfo.getDeviceId(),recordInfo.getChannelId()); data.remove(key); } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java b/src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java index ea2760d55..1bd9850f4 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java @@ -4,6 +4,7 @@ import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; +import com.genersoft.iot.vmp.utils.JsonUtil; import com.genersoft.iot.vmp.utils.redis.RedisUtil; import gov.nist.javax.sip.message.SIPResponse; import org.springframework.beans.factory.annotation.Autowired; @@ -134,7 +135,7 @@ public class VideoStreamSessionManager { List result= new ArrayList<>(); for (int i = 0; i < ssrcTransactionKeys.size(); i++) { String key = (String)ssrcTransactionKeys.get(i); - SsrcTransaction ssrcTransaction = (SsrcTransaction)RedisUtil.get(key); + SsrcTransaction ssrcTransaction = JsonUtil.redisJsonToObject(key, SsrcTransaction.class); result.add(ssrcTransaction); } return result; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java index 0aff21d21..742b8bbbc 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java @@ -47,61 +47,65 @@ public class SIPSender { } public void transmitRequest(String ip, Message message, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, ParseException { - ViaHeader viaHeader = (ViaHeader)message.getHeader(ViaHeader.NAME); - String transport = "UDP"; - if (viaHeader == null) { - logger.warn("[消息头缺失]: ViaHeader, 使用默认的UDP方式处理数据"); - }else { - transport = viaHeader.getTransport(); - } - if (message.getHeader(UserAgentHeader.NAME) == null) { - try { - message.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil)); - } catch (ParseException e) { - logger.error("添加UserAgentHeader失败", e); + try { + ViaHeader viaHeader = (ViaHeader)message.getHeader(ViaHeader.NAME); + String transport = "UDP"; + if (viaHeader == null) { + logger.warn("[消息头缺失]: ViaHeader, 使用默认的UDP方式处理数据"); + }else { + transport = viaHeader.getTransport(); } - } - - CallIdHeader callIdHeader = (CallIdHeader) message.getHeader(CallIdHeader.NAME); - // 添加错误订阅 - if (errorEvent != null) { - sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> { - errorEvent.response(eventResult); - sipSubscribe.removeErrorSubscribe(eventResult.callId); - sipSubscribe.removeOkSubscribe(eventResult.callId); - })); - } - // 添加订阅 - if (okEvent != null) { - sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult -> { - okEvent.response(eventResult); - sipSubscribe.removeOkSubscribe(eventResult.callId); - sipSubscribe.removeErrorSubscribe(eventResult.callId); - }); - } - if ("TCP".equals(transport)) { - SipProviderImpl tcpSipProvider = sipLayer.getTcpSipProvider(ip); - if (tcpSipProvider == null) { - logger.error("[发送信息失败] 未找到tcp://{}的监听信息", ip); - return; - } - if (message instanceof Request) { - tcpSipProvider.sendRequest((Request)message); - }else if (message instanceof Response) { - tcpSipProvider.sendResponse((Response)message); + if (message.getHeader(UserAgentHeader.NAME) == null) { + try { + message.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil)); + } catch (ParseException e) { + logger.error("添加UserAgentHeader失败", e); + } } - } else if ("UDP".equals(transport)) { - SipProviderImpl sipProvider = sipLayer.getUdpSipProvider(ip); - if (sipProvider == null) { - logger.error("[发送信息失败] 未找到udp://{}的监听信息", ip); - return; + CallIdHeader callIdHeader = (CallIdHeader) message.getHeader(CallIdHeader.NAME); + // 添加错误订阅 + if (errorEvent != null) { + sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> { + errorEvent.response(eventResult); + sipSubscribe.removeErrorSubscribe(eventResult.callId); + sipSubscribe.removeOkSubscribe(eventResult.callId); + })); } - if (message instanceof Request) { - sipProvider.sendRequest((Request)message); - }else if (message instanceof Response) { - sipProvider.sendResponse((Response)message); + // 添加订阅 + if (okEvent != null) { + sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult -> { + okEvent.response(eventResult); + sipSubscribe.removeOkSubscribe(eventResult.callId); + sipSubscribe.removeErrorSubscribe(eventResult.callId); + }); } + if ("TCP".equals(transport)) { + SipProviderImpl tcpSipProvider = sipLayer.getTcpSipProvider(ip); + if (tcpSipProvider == null) { + logger.error("[发送信息失败] 未找到tcp://{}的监听信息", ip); + return; + } + if (message instanceof Request) { + tcpSipProvider.sendRequest((Request)message); + }else if (message instanceof Response) { + tcpSipProvider.sendResponse((Response)message); + } + + } else if ("UDP".equals(transport)) { + SipProviderImpl sipProvider = sipLayer.getUdpSipProvider(ip); + if (sipProvider == null) { + logger.error("[发送信息失败] 未找到udp://{}的监听信息", ip); + return; + } + if (message instanceof Request) { + sipProvider.sendRequest((Request)message); + }else if (message instanceof Response) { + sipProvider.sendResponse((Response)message); + } + } + } finally { + logger.info("[SEND]:SUCCESS:{}", message); } } 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 index f01132417..b6e5d61d3 100644 --- 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 @@ -182,7 +182,7 @@ public interface ISIPCommander { * @param channelId 预览通道 * @param recordCmdStr 录像命令:Record / StopRecord */ - void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; + void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; /** * 远程启动控制命令 @@ -196,7 +196,7 @@ public interface ISIPCommander { * * @param device 视频设备 */ - void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; + void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; /** * 报警复位命令 @@ -205,7 +205,7 @@ public interface ISIPCommander { * @param alarmMethod 报警方式(可选) * @param alarmType 报警类型(可选) */ - void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; + void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; /** * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧 @@ -214,17 +214,19 @@ public interface ISIPCommander { * @param channelId 预览通道 */ void iFrameCmd(Device device, String channelId) throws InvalidArgumentException, SipException, ParseException; - + /** * 看守位控制命令 - * - * @param device 视频设备 - * @param enabled 看守位使能:1 = 开启,0 = 关闭 - * @param resetTime 自动归位时间间隔,开启看守位时使用,单位:秒(s) - * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255 + * + * @param device 视频设备 + * @param channelId 通道id,非通道则是设备本身 + * @param frontCmd 上级平台的指令,如果存在则直接下发 + * @param enabled 看守位使能:1 = 开启,0 = 关闭 + * @param resetTime 自动归位时间间隔,开启看守位时使用,单位:秒(s) + * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255 */ - void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; - + void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; + /** * 设备配置命令 * diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java index 0d6da6453..0425356ad 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java @@ -51,11 +51,11 @@ public interface ISIPCommanderForPlatform { /** * 向上级回复DeviceInfo查询信息 * @param parentPlatform 平台信息 - * @param sn - * @param fromTag + * @param sn SN + * @param fromTag FROM头的tag信息 * @return */ - void deviceInfoResponse(ParentPlatform parentPlatform, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException; + void deviceInfoResponse(ParentPlatform parentPlatform,Device device, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException; /** * 向上级回复DeviceStatus查询信息 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 index 729f9b871..80c032ca7 100644 --- 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 @@ -32,6 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; import javax.sip.InvalidArgumentException; import javax.sip.ResponseEvent; @@ -711,7 +712,7 @@ public class SIPCommander implements ISIPCommander { * @param recordCmdStr 录像命令:Record / StopRecord */ @Override - public void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { + public void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException { StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); cmdXml.append("\r\n"); @@ -729,7 +730,7 @@ public class SIPCommander implements ISIPCommander { Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); - sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent); + sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent); } /** @@ -763,7 +764,7 @@ public class SIPCommander implements ISIPCommander { * @param guardCmdStr "SetGuard"/"ResetGuard" */ @Override - public void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { + public void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException { StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); @@ -778,7 +779,7 @@ public class SIPCommander implements ISIPCommander { Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); - sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent); + sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent); } /** @@ -787,7 +788,7 @@ public class SIPCommander implements ISIPCommander { * @param device 视频设备 */ @Override - public void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { + public void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException { StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); @@ -814,7 +815,7 @@ public class SIPCommander implements ISIPCommander { Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); - sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent); + sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent); } /** @@ -850,12 +851,14 @@ public class SIPCommander implements ISIPCommander { * 看守位控制命令 * * @param device 视频设备 + * @param channelId 通道id,非通道则是设备本身 + * @param frontCmd 上级平台的指令,如果存在则直接下发 * @param enabled 看守位使能:1 = 开启,0 = 关闭 * @param resetTime 自动归位时间间隔,开启看守位时使用,单位:秒(s) * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255 */ @Override - public void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { + public void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException { StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); @@ -890,7 +893,7 @@ public class SIPCommander implements ISIPCommander { Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); - sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent); + sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent); } /** diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java index 5a3faac64..3f731c44e 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java @@ -1,6 +1,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl; import com.alibaba.fastjson2.JSON; +import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.gb28181.SipLayer; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; @@ -61,6 +62,9 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { @Autowired private SIPSender sipSender; + @Autowired + private DynamicTask dynamicTask; + @Override public void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException { register(parentPlatform, null, null, errorEvent, okEvent, false, true); @@ -109,13 +113,14 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { public String keepalive(ParentPlatform parentPlatform,SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException { String characterSet = parentPlatform.getCharacterSet(); StringBuffer keepaliveXml = new StringBuffer(200); - keepaliveXml.append("\r\n"); - keepaliveXml.append("\r\n"); - keepaliveXml.append("Keepalive\r\n"); - keepaliveXml.append("" + (int)((Math.random()*9+1)*100000) + "\r\n"); - keepaliveXml.append("" + parentPlatform.getDeviceGBId() + "\r\n"); - keepaliveXml.append("OK\r\n"); - keepaliveXml.append("\r\n"); + keepaliveXml.append("\r\n") + .append("\r\n") + .append("Keepalive\r\n") + .append("" + (int)((Math.random()*9+1)*100000) + "\r\n") + .append("" + parentPlatform.getDeviceGBId() + "\r\n") + .append("OK\r\n") + .append("\r\n"); CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport()); @@ -133,7 +138,6 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { * 向上级回复通道信息 * @param channel 通道信息 * @param parentPlatform 平台信息 - * @return */ @Override public void catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size) throws SipException, InvalidArgumentException, ParseException { @@ -160,18 +164,18 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { if ( parentPlatform ==null) { return ; } - sendCatalogResponse(channels, parentPlatform, sn, fromTag, 0); + sendCatalogResponse(channels, parentPlatform, sn, fromTag, 0, true); } private String getCatalogXml(List channels, String sn, ParentPlatform parentPlatform, int size) { String characterSet = parentPlatform.getCharacterSet(); StringBuffer catalogXml = new StringBuffer(600); - catalogXml.append("\r\n"); - catalogXml.append("\r\n"); - catalogXml.append("Catalog\r\n"); - catalogXml.append("" +sn + "\r\n"); - catalogXml.append("" + parentPlatform.getDeviceGBId() + "\r\n"); - catalogXml.append("" + size + "\r\n"); - catalogXml.append("\r\n"); + catalogXml.append("\r\n") + .append("\r\n") + .append("Catalog\r\n") + .append("" +sn + "\r\n") + .append("" + parentPlatform.getDeviceGBId() + "\r\n") + .append("" + size + "\r\n") + .append("\r\n"); if (channels.size() > 0) { for (DeviceChannel channel : channels) { if (parentPlatform.getServerGBId().equals(channel.getParentId())) { @@ -222,7 +226,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { return catalogXml.toString(); } - private void sendCatalogResponse(List channels, ParentPlatform parentPlatform, String sn, String fromTag, int index) throws SipException, InvalidArgumentException, ParseException { + private void sendCatalogResponse(List channels, ParentPlatform parentPlatform, String sn, String fromTag, int index, boolean sendAfterResponse) throws SipException, InvalidArgumentException, ParseException { if (index >= channels.size()) { return; } @@ -236,15 +240,49 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { // callid CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport()); - Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, catalogXml, fromTag, SipUtils.getNewViaTag(), callIdHeader); - sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, null, eventResult -> { - int indexNext = index + parentPlatform.getCatalogGroup(); - try { - sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext); - } catch (SipException | InvalidArgumentException | ParseException e) { - logger.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage()); - } - }); + SIPRequest request = (SIPRequest)headerProviderPlatformProvider.createMessageRequest(parentPlatform, catalogXml, fromTag, SipUtils.getNewViaTag(), callIdHeader); + + String timeoutTaskKey = "catalog_task_" + parentPlatform.getServerGBId() + sn; + + String callId = request.getCallIdHeader().getCallId(); + + if (sendAfterResponse) { + // 默认按照收到200回复后发送下一条, 如果超时收不到回复,就以30毫秒的间隔直接发送。 + dynamicTask.startDelay(timeoutTaskKey, ()->{ + sipSubscribe.removeOkSubscribe(callId); + int indexNext = index + parentPlatform.getCatalogGroup(); + try { + sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext, false); + } catch (SipException | InvalidArgumentException | ParseException e) { + logger.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage()); + } + }, 3000); + sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, eventResult -> { + logger.error("[目录推送失败] 国标级联 platform : {}, code: {}, msg: {}, 停止发送", parentPlatform.getServerGBId(), eventResult.statusCode, eventResult.msg); + dynamicTask.stop(timeoutTaskKey); + }, eventResult -> { + dynamicTask.stop(timeoutTaskKey); + int indexNext = index + parentPlatform.getCatalogGroup(); + try { + sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext, true); + } catch (SipException | InvalidArgumentException | ParseException e) { + logger.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage()); + } + }); + }else { + sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, eventResult -> { + logger.error("[目录推送失败] 国标级联 platform : {}, code: {}, msg: {}, 停止发送", parentPlatform.getServerGBId(), eventResult.statusCode, eventResult.msg); + dynamicTask.stop(timeoutTaskKey); + }, null); + dynamicTask.startDelay(timeoutTaskKey, ()->{ + int indexNext = index + parentPlatform.getCatalogGroup(); + try { + sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext, false); + } catch (SipException | InvalidArgumentException | ParseException e) { + logger.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage()); + } + }, 30); + } } /** @@ -255,7 +293,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { * @return */ @Override - public void deviceInfoResponse(ParentPlatform parentPlatform, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException { + public void deviceInfoResponse(ParentPlatform parentPlatform,Device device, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException { if (parentPlatform == null) { return; } @@ -265,11 +303,11 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { deviceInfoXml.append("\r\n"); deviceInfoXml.append("DeviceInfo\r\n"); deviceInfoXml.append("" +sn + "\r\n"); - deviceInfoXml.append("" + parentPlatform.getDeviceGBId() + "\r\n"); - deviceInfoXml.append("" + parentPlatform.getName() + "\r\n"); - deviceInfoXml.append("wvp\r\n"); - deviceInfoXml.append("wvp-28181-2.0\r\n"); - deviceInfoXml.append("2.0.202107\r\n"); + deviceInfoXml.append("" + device.getDeviceId() + "\r\n"); + deviceInfoXml.append("" + device.getName() + "\r\n"); + deviceInfoXml.append("" + device.getManufacturer() + "\r\n"); + deviceInfoXml.append("" + device.getModel() + "\r\n"); + deviceInfoXml.append("" + device.getFirmware() + "\r\n"); deviceInfoXml.append("OK\r\n"); deviceInfoXml.append("\r\n"); @@ -294,15 +332,15 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { String statusStr = (status==1)?"ONLINE":"OFFLINE"; String characterSet = parentPlatform.getCharacterSet(); StringBuffer deviceStatusXml = new StringBuffer(600); - deviceStatusXml.append("\r\n"); - deviceStatusXml.append("\r\n"); - deviceStatusXml.append("DeviceStatus\r\n"); - deviceStatusXml.append("" +sn + "\r\n"); - deviceStatusXml.append("" + channelId + "\r\n"); - deviceStatusXml.append("OK\r\n"); - deviceStatusXml.append(""+statusStr+"\r\n"); - deviceStatusXml.append("OK\r\n"); - deviceStatusXml.append("\r\n"); + deviceStatusXml.append("\r\n") + .append("\r\n") + .append("DeviceStatus\r\n") + .append("" +sn + "\r\n") + .append("" + channelId + "\r\n") + .append("OK\r\n") + .append(""+statusStr+"\r\n") + .append("OK\r\n") + .append("\r\n"); CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport()); @@ -321,18 +359,18 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { String characterSet = parentPlatform.getCharacterSet(); StringBuffer deviceStatusXml = new StringBuffer(600); - deviceStatusXml.append("\r\n"); - deviceStatusXml.append("\r\n"); - deviceStatusXml.append("MobilePosition\r\n"); - deviceStatusXml.append("" + (int)((Math.random()*9+1)*100000) + "\r\n"); - deviceStatusXml.append("" + gpsMsgInfo.getId() + "\r\n"); - deviceStatusXml.append("\r\n"); - deviceStatusXml.append("" + gpsMsgInfo.getLng() + "\r\n"); - deviceStatusXml.append("" + gpsMsgInfo.getLat() + "\r\n"); - deviceStatusXml.append("" + gpsMsgInfo.getSpeed() + "\r\n"); - deviceStatusXml.append("" + gpsMsgInfo.getDirection() + "\r\n"); - deviceStatusXml.append("" + gpsMsgInfo.getAltitude() + "\r\n"); - deviceStatusXml.append("\r\n"); + deviceStatusXml.append("\r\n") + .append("\r\n") + .append("MobilePosition\r\n") + .append("" + (int)((Math.random()*9+1)*100000) + "\r\n") + .append("" + gpsMsgInfo.getId() + "\r\n") + .append("\r\n") + .append("" + gpsMsgInfo.getLng() + "\r\n") + .append("" + gpsMsgInfo.getLat() + "\r\n") + .append("" + gpsMsgInfo.getSpeed() + "\r\n") + .append("" + gpsMsgInfo.getDirection() + "\r\n") + .append("" + gpsMsgInfo.getAltitude() + "\r\n") + .append("\r\n"); sendNotify(parentPlatform, deviceStatusXml.toString(), subscribeInfo, eventResult -> { logger.error("发送NOTIFY通知消息失败。错误:{} {}", eventResult.statusCode, eventResult.msg); @@ -349,21 +387,21 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { deviceAlarm.getLongitude(), deviceAlarm.getLatitude(), JSON.toJSONString(deviceAlarm)); String characterSet = parentPlatform.getCharacterSet(); StringBuffer deviceStatusXml = new StringBuffer(600); - deviceStatusXml.append("\r\n"); - deviceStatusXml.append("\r\n"); - deviceStatusXml.append("Alarm\r\n"); - deviceStatusXml.append("" + (int)((Math.random()*9+1)*100000) + "\r\n"); - deviceStatusXml.append("" + deviceAlarm.getChannelId() + "\r\n"); - deviceStatusXml.append("" + deviceAlarm.getAlarmPriority() + "\r\n"); - deviceStatusXml.append("" + deviceAlarm.getAlarmMethod() + "\r\n"); - deviceStatusXml.append("" + deviceAlarm.getAlarmTime() + "\r\n"); - deviceStatusXml.append("" + deviceAlarm.getAlarmDescription() + "\r\n"); - deviceStatusXml.append("" + deviceAlarm.getLongitude() + "\r\n"); - deviceStatusXml.append("" + deviceAlarm.getLatitude() + "\r\n"); - deviceStatusXml.append("\r\n"); - deviceStatusXml.append("" + deviceAlarm.getAlarmType() + "\r\n"); - deviceStatusXml.append("\r\n"); - deviceStatusXml.append("\r\n"); + deviceStatusXml.append("\r\n") + .append("\r\n") + .append("Alarm\r\n") + .append("" + (int)((Math.random()*9+1)*100000) + "\r\n") + .append("" + deviceAlarm.getChannelId() + "\r\n") + .append("" + deviceAlarm.getAlarmPriority() + "\r\n") + .append("" + deviceAlarm.getAlarmMethod() + "\r\n") + .append("" + deviceAlarm.getAlarmTime() + "\r\n") + .append("" + deviceAlarm.getAlarmDescription() + "\r\n") + .append("" + deviceAlarm.getLongitude() + "\r\n") + .append("" + deviceAlarm.getLatitude() + "\r\n") + .append("\r\n") + .append("" + deviceAlarm.getAlarmType() + "\r\n") + .append("\r\n") + .append("\r\n"); CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport()); @@ -422,13 +460,13 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { StringBuffer catalogXml = new StringBuffer(600); String characterSet = parentPlatform.getCharacterSet(); - catalogXml.append("\r\n"); - catalogXml.append("\r\n"); - catalogXml.append("Catalog\r\n"); - catalogXml.append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n"); - catalogXml.append("" + parentPlatform.getDeviceGBId() + "\r\n"); - catalogXml.append("1\r\n"); - catalogXml.append("\r\n"); + catalogXml.append("\r\n") + .append("\r\n") + .append("Catalog\r\n") + .append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n") + .append("" + parentPlatform.getDeviceGBId() + "\r\n") + .append("1\r\n") + .append("\r\n"); if (channels.size() > 0) { for (DeviceChannel channel : channels) { if (parentPlatform.getServerGBId().equals(channel.getParentId())) { @@ -449,16 +487,16 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { catalogXml.append("" + channel.getParental() + "\r\n"); if (channel.getParental() == 0) { // 通道项 - catalogXml.append("" + channel.getManufacture() + "\r\n"); - catalogXml.append("" + channel.getSecrecy() + "\r\n"); - catalogXml.append("" + channel.getRegisterWay() + "\r\n"); - catalogXml.append("" + (channel.getStatus() == 0 ? "OFF" : "ON") + "\r\n"); + catalogXml.append("" + channel.getManufacture() + "\r\n") + .append("" + channel.getSecrecy() + "\r\n") + .append("" + channel.getRegisterWay() + "\r\n") + .append("" + (channel.getStatus() == 0 ? "OFF" : "ON") + "\r\n"); if (channel.getChannelType() != 2) { // 业务分组/虚拟组织/行政区划 不设置以下属性 - catalogXml.append("" + channel.getModel() + "\r\n"); - catalogXml.append(" " + channel.getOwner()+ "\r\n"); - catalogXml.append("" + channel.getCivilCode() + "\r\n"); - catalogXml.append("
" + channel.getAddress() + "
\r\n"); + catalogXml.append("" + channel.getModel() + "\r\n") + .append(" " + channel.getOwner()+ "\r\n") + .append("" + channel.getCivilCode() + "\r\n") + .append("
" + channel.getAddress() + "
\r\n"); } if (!"presence".equals(subscribeInfo.getEventType())) { catalogXml.append("" + type + "\r\n"); @@ -468,8 +506,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { catalogXml.append("\r\n"); } } - catalogXml.append("
\r\n"); - catalogXml.append("
\r\n"); + catalogXml.append("
\r\n") + .append("
\r\n"); return catalogXml.toString(); } @@ -515,26 +553,26 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { String characterSet = parentPlatform.getCharacterSet(); StringBuffer catalogXml = new StringBuffer(600); - catalogXml.append("\r\n"); - catalogXml.append("\r\n"); - catalogXml.append("Catalog\r\n"); - catalogXml.append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n"); - catalogXml.append("" + parentPlatform.getDeviceGBId() + "\r\n"); - catalogXml.append("1\r\n"); - catalogXml.append("\r\n"); + catalogXml.append("\r\n") + .append("\r\n") + .append("Catalog\r\n") + .append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n") + .append("" + parentPlatform.getDeviceGBId() + "\r\n") + .append("1\r\n") + .append("\r\n"); if (channels.size() > 0) { for (DeviceChannel channel : channels) { if (parentPlatform.getServerGBId().equals(channel.getParentId())) { channel.setParentId(parentPlatform.getDeviceGBId()); } - catalogXml.append("\r\n"); - catalogXml.append("" + channel.getChannelId() + "\r\n"); - catalogXml.append("" + type + "\r\n"); - catalogXml.append("\r\n"); + catalogXml.append("\r\n") + .append("" + channel.getChannelId() + "\r\n") + .append("" + type + "\r\n") + .append("\r\n"); } } - catalogXml.append("\r\n"); - catalogXml.append("\r\n"); + catalogXml.append("\r\n") + .append("\r\n"); return catalogXml.toString(); } @Override @@ -544,12 +582,12 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { } String characterSet = parentPlatform.getCharacterSet(); StringBuffer recordXml = new StringBuffer(600); - recordXml.append("\r\n"); - recordXml.append("\r\n"); - recordXml.append("RecordInfo\r\n"); - recordXml.append("" +recordInfo.getSn() + "\r\n"); - recordXml.append("" + recordInfo.getDeviceId() + "\r\n"); - recordXml.append("" + recordInfo.getSumNum() + "\r\n"); + recordXml.append("\r\n") + .append("\r\n") + .append("RecordInfo\r\n") + .append("" +recordInfo.getSn() + "\r\n") + .append("" + recordInfo.getDeviceId() + "\r\n") + .append("" + recordInfo.getSumNum() + "\r\n"); if (recordInfo.getRecordList() == null ) { recordXml.append("\r\n"); }else { @@ -558,12 +596,12 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { for (RecordItem recordItem : recordInfo.getRecordList()) { recordXml.append("\r\n"); if (deviceChannel != null) { - recordXml.append("" + recordItem.getDeviceId() + "\r\n"); - recordXml.append("" + recordItem.getName() + "\r\n"); - recordXml.append("" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getStartTime()) + "\r\n"); - recordXml.append("" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getEndTime()) + "\r\n"); - recordXml.append("" + recordItem.getSecrecy() + "\r\n"); - recordXml.append("" + recordItem.getType() + "\r\n"); + recordXml.append("" + recordItem.getDeviceId() + "\r\n") + .append("" + recordItem.getName() + "\r\n") + .append("" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getStartTime()) + "\r\n") + .append("" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getEndTime()) + "\r\n") + .append("" + recordItem.getSecrecy() + "\r\n") + .append("" + recordItem.getType() + "\r\n"); if (!ObjectUtils.isEmpty(recordItem.getFileSize())) { recordXml.append("" + recordItem.getFileSize() + "\r\n"); } @@ -576,8 +614,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { } } - recordXml.append("\r\n"); - recordXml.append("\r\n"); + recordXml.append("\r\n") + .append("\r\n"); // callid CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport()); @@ -596,13 +634,13 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { String characterSet = parentPlatform.getCharacterSet(); StringBuffer mediaStatusXml = new StringBuffer(200); - mediaStatusXml.append("\r\n"); - mediaStatusXml.append("\r\n"); - mediaStatusXml.append("MediaStatus\r\n"); - mediaStatusXml.append("" + (int)((Math.random()*9+1)*100000) + "\r\n"); - mediaStatusXml.append("" + sendRtpItem.getChannelId() + "\r\n"); - mediaStatusXml.append("121\r\n"); - mediaStatusXml.append("\r\n"); + mediaStatusXml.append("\r\n") + .append("\r\n") + .append("MediaStatus\r\n") + .append("" + (int)((Math.random()*9+1)*100000) + "\r\n") + .append("" + sendRtpItem.getChannelId() + "\r\n") + .append("121\r\n") + .append("\r\n"); SIPRequest messageRequest = (SIPRequest)headerProviderPlatformProvider.createMessageRequest(parentPlatform, mediaStatusXml.toString(), sendRtpItem); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java index f3dfa9668..1a7f4eb8e 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java @@ -16,7 +16,6 @@ import org.springframework.beans.factory.annotation.Autowired; import javax.sip.*; import javax.sip.address.Address; -import javax.sip.address.AddressFactory; import javax.sip.address.SipURI; import javax.sip.header.ContentTypeHeader; import javax.sip.header.ExpiresHeader; @@ -42,15 +41,6 @@ public abstract class SIPRequestProcessorParent { @Autowired private SIPSender sipSender; - public AddressFactory getAddressFactory() { - try { - return SipFactory.getInstance().createAddressFactory(); - } catch (PeerUnavailableException e) { - e.printStackTrace(); - } - return null; - } - public HeaderFactory getHeaderFactory() { try { return SipFactory.getInstance().createHeaderFactory(); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java index c9b979f78..56cadb34e 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java @@ -275,7 +275,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements } return; } else { - logger.info("通道不存在,返回404"); + logger.info("通道不存在,返回404: {}", channelId); try { // 通道不存在,发404,资源不存在 responseAck(request, Response.NOT_FOUND); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java index 3ed223e01..2effe1123 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java @@ -1,5 +1,6 @@ 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; @@ -82,6 +83,19 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen RequestEventExt evtExt = (RequestEventExt) evt; String requestAddress = evtExt.getRemoteIpAddress() + ":" + evtExt.getRemotePort(); logger.info("[注册请求] 开始处理: {}", requestAddress); +// MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer(); +// QueryExp protocol = Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")); +//// ObjectName name = new ObjectName("*:type=Connector,*"); +// ObjectName name = new ObjectName("*:*"); +// Set objectNames = beanServer.queryNames(name, protocol); +// for (ObjectName objectName : objectNames) { +// String catalina = objectName.getDomain(); +// if ("Catalina".equals(catalina)) { +// System.out.println(objectName.getKeyProperty("port")); +// } +// } + + System.out.println(ServiceInfo.getServerPort()); SIPRequest request = (SIPRequest)evt.getRequest(); Response response = null; boolean passwordCorrect = false; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java index f97a6592f..4ac83de75 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java @@ -1,8 +1,11 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.control.cmd; -import com.genersoft.iot.vmp.VManageBootstrap; +import com.genersoft.iot.vmp.common.enums.DeviceControlType; import com.genersoft.iot.vmp.gb28181.bean.Device; +import com.genersoft.iot.vmp.gb28181.bean.DragZoomRequest; +import com.genersoft.iot.vmp.gb28181.bean.HomePositionRequest; import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; +import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; @@ -19,17 +22,14 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; import javax.sip.*; import javax.sip.address.SipURI; -import javax.sip.header.HeaderAddress; -import javax.sip.header.ToHeader; import javax.sip.message.Response; import java.text.ParseException; -import java.util.Iterator; +import java.util.List; -import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; +import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.*; @Component public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { @@ -81,7 +81,7 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent } catch (InvalidArgumentException | ParseException | SipException e) { logger.error("[命令发送失败] 国标级联 注销: {}", e.getMessage()); } - taskExecutor.execute(()->{ + taskExecutor.execute(() -> { // 远程启动 // try { // Thread.sleep(3000); @@ -101,13 +101,12 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent // logger.error("[任务执行失败] 服务重启: {}", e.getMessage()); // } }); - } else { - // 远程启动指定设备 } } - // 云台/前端控制命令 - if (!ObjectUtils.isEmpty(getText(rootElement,"PTZCmd")) && !parentPlatform.getServerGBId().equals(targetGBId)) { - String cmdString = getText(rootElement,"PTZCmd"); + DeviceControlType deviceControlType = DeviceControlType.typeOf(rootElement); + logger.info("[接受deviceControl命令] 命令: {}", deviceControlType); + if (!ObjectUtils.isEmpty(deviceControlType) && !parentPlatform.getServerGBId().equals(targetGBId)) { + //判断是否存在该通道 Device deviceForPlatform = storager.queryVideoDeviceByPlatformIdAndChannelId(parentPlatform.getServerGBId(), channelId); if (deviceForPlatform == null) { try { @@ -117,25 +116,240 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent } return; } - try { - cmder.fronEndCmd(deviceForPlatform, channelId, cmdString, eventResult -> { - // 失败的回复 - try { - responseAck(request, eventResult.statusCode, eventResult.msg); - } catch (SipException | InvalidArgumentException | ParseException e) { - logger.error("[命令发送失败] 云台/前端回复: {}", e.getMessage()); - } - }, eventResult -> { - // 成功的回复 - try { - responseAck(request, eventResult.statusCode); - } catch (SipException | InvalidArgumentException | ParseException e) { - logger.error("[命令发送失败] 云台/前端回复: {}", e.getMessage()); - } - }); - } catch (InvalidArgumentException | SipException | ParseException e) { - logger.error("[命令发送失败] 云台/前端: {}", e.getMessage()); + switch (deviceControlType) { + case PTZ: + handlePtzCmd(deviceForPlatform, channelId, rootElement, request, DeviceControlType.PTZ); + break; + case ALARM: + handleAlarmCmd(deviceForPlatform, rootElement, request); + break; + case GUARD: + handleGuardCmd(deviceForPlatform, rootElement, request, DeviceControlType.GUARD); + break; + case RECORD: + handleRecordCmd(deviceForPlatform, channelId, rootElement, request, DeviceControlType.RECORD); + break; + case I_FRAME: + handleIFameCmd(deviceForPlatform, request, channelId); + break; + case TELE_BOOT: + handleTeleBootCmd(deviceForPlatform, request); + break; + case DRAG_ZOOM_IN: + handleDragZoom(deviceForPlatform, channelId, rootElement, request, DeviceControlType.DRAG_ZOOM_IN); + break; + case DRAG_ZOOM_OUT: + handleDragZoom(deviceForPlatform, channelId, rootElement, request, DeviceControlType.DRAG_ZOOM_OUT); + break; + case HOME_POSITION: + handleHomePositionCmd(deviceForPlatform, channelId, rootElement, request, DeviceControlType.HOME_POSITION); + break; + default: + break; } } } + + /** + * 处理云台指令 + * + * @param device 设备 + * @param channelId 通道id + * @param rootElement + * @param request + */ + private void handlePtzCmd(Device device, String channelId, Element rootElement, SIPRequest request, DeviceControlType type) { + String cmdString = getText(rootElement, type.getVal()); + try { + cmder.fronEndCmd(device, channelId, cmdString, + errorResult -> onError(request, errorResult), + okResult -> onOk(request, okResult)); + } catch (InvalidArgumentException | SipException | ParseException e) { + logger.error("[命令发送失败] 云台/前端: {}", e.getMessage()); + } + } + + /** + * 处理强制关键帧 + * + * @param device 设备 + * @param channelId 通道id + */ + private void handleIFameCmd(Device device, SIPRequest request, String channelId) { + try { + cmder.iFrameCmd(device, channelId); + responseAck(request, Response.OK); + } catch (InvalidArgumentException | SipException | ParseException e) { + logger.error("[命令发送失败] 强制关键帧: {}", e.getMessage()); + } + } + + /** + * 处理重启命令 + * + * @param device 设备信息 + */ + private void handleTeleBootCmd(Device device, SIPRequest request) { + try { + cmder.teleBootCmd(device); + responseAck(request, Response.OK); + } catch (InvalidArgumentException | SipException | ParseException e) { + logger.error("[命令发送失败] 重启: {}", e.getMessage()); + } + + } + + /** + * 处理拉框控制*** + * + * @param device 设备信息 + * @param channelId 通道id + * @param rootElement 根节点 + * @param type 消息类型 + */ + private void handleDragZoom(Device device, String channelId, Element rootElement, SIPRequest request, DeviceControlType type) { + try { + DragZoomRequest dragZoomRequest = loadElement(rootElement, DragZoomRequest.class); + DragZoomRequest.DragZoom dragZoom = dragZoomRequest.getDragZoomIn(); + if (dragZoom == null) { + dragZoom = dragZoomRequest.getDragZoomOut(); + } + StringBuffer cmdXml = new StringBuffer(200); + cmdXml.append("<" + type.getVal() + ">\r\n"); + cmdXml.append("" + dragZoom.getLength() + "\r\n"); + cmdXml.append("" + dragZoom.getWidth() + "\r\n"); + cmdXml.append("" + dragZoom.getMidPointX() + "\r\n"); + cmdXml.append("" + dragZoom.getMidPointY() + "\r\n"); + cmdXml.append("" + dragZoom.getLengthX() + "\r\n"); + cmdXml.append("" + dragZoom.getLengthY() + "\r\n"); + cmdXml.append("\r\n"); + cmder.dragZoomCmd(device, channelId, cmdXml.toString()); + responseAck(request, Response.OK); + } catch (Exception e) { + logger.error("[命令发送失败] 拉框控制: {}", e.getMessage()); + } + + } + + /** + * 处理看守位命令*** + * + * @param device 设备信息 + * @param channelId 通道id + * @param rootElement 根节点 + * @param request 请求信息 + * @param type 消息类型 + */ + private void handleHomePositionCmd(Device device, String channelId, Element rootElement, SIPRequest request, DeviceControlType type) { + try { + HomePositionRequest homePosition = loadElement(rootElement, HomePositionRequest.class); + //获取整个消息主体,我们只需要修改请求头即可 + HomePositionRequest.HomePosition info = homePosition.getHomePosition(); + cmder.homePositionCmd(device, channelId, info.getEnabled(), info.getResetTime(), info.getPresetIndex(), + errorResult -> onError(request, errorResult), + okResult -> onOk(request, okResult)); + } catch (Exception e) { + logger.error("[命令发送失败] 看守位设置: {}", e.getMessage()); + } + } + + /** + * 处理告警消息*** + * + * @param device 设备信息 + * @param rootElement 根节点 + * @param request 请求信息 + */ + private void handleAlarmCmd(Device device, Element rootElement, SIPRequest request) { + //告警方法 + String alarmMethod = ""; + //告警类型 + String alarmType = ""; + List info = rootElement.elements("Info"); + if (info != null) { + for (Element element : info) { + alarmMethod = getText(element, "AlarmMethod"); + alarmType = getText(element, "AlarmType"); + } + } + try { + cmder.alarmCmd(device, alarmMethod, alarmType, + errorResult -> onError(request, errorResult), + okResult -> onOk(request, okResult)); + } catch (InvalidArgumentException | SipException | ParseException e) { + logger.error("[命令发送失败] 告警消息: {}", e.getMessage()); + } + } + + /** + * 处理录像控制 + * + * @param device 设备信息 + * @param channelId 通道id + * @param rootElement 根节点 + * @param request 请求信息 + * @param type 消息类型 + */ + private void handleRecordCmd(Device device, String channelId, Element rootElement, SIPRequest request, DeviceControlType type) { + //获取整个消息主体,我们只需要修改请求头即可 + String cmdString = getText(rootElement, type.getVal()); + try { + cmder.recordCmd(device, channelId, cmdString, + errorResult -> onError(request, errorResult), + okResult -> onOk(request, okResult)); + } catch (InvalidArgumentException | SipException | ParseException e) { + logger.error("[命令发送失败] 录像控制: {}", e.getMessage()); + } + } + + /** + * 处理报警布防/撤防命令 + * + * @param device 设备信息 + * @param rootElement 根节点 + * @param request 请求信息 + * @param type 消息类型 + */ + private void handleGuardCmd(Device device, Element rootElement, SIPRequest request, DeviceControlType type) { + //获取整个消息主体,我们只需要修改请求头即可 + String cmdString = getText(rootElement, type.getVal()); + try { + cmder.guardCmd(device, cmdString, + errorResult -> onError(request, errorResult), + okResult -> onOk(request, okResult)); + } catch (InvalidArgumentException | SipException | ParseException e) { + logger.error("[命令发送失败] 布防/撤防命令: {}", e.getMessage()); + } + } + + + /** + * 错误响应处理 + * + * @param request 请求 + * @param eventResult 响应结构 + */ + private void onError(SIPRequest request, SipSubscribe.EventResult eventResult) { + // 失败的回复 + try { + responseAck(request, eventResult.statusCode, eventResult.msg); + } catch (SipException | InvalidArgumentException | ParseException e) { + logger.error("[命令发送失败] 回复: {}", e.getMessage()); + } + } + + /** + * 成功响应处理 + * + * @param request 请求 + * @param eventResult 响应结构 + */ + private void onOk(SIPRequest request, SipSubscribe.EventResult eventResult) { + // 成功的回复 + try { + responseAck(request, eventResult.statusCode); + } catch (SipException | InvalidArgumentException | ParseException e) { + logger.error("[命令发送失败] 回复: {}", e.getMessage()); + } + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java index 09a5ffc2c..e89e88abe 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java @@ -181,11 +181,14 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme } } logger.info("[收到报警通知]内容:{}", JSON.toJSONString(deviceAlarm)); - if ("7".equals(deviceAlarm.getAlarmMethod()) ) { + // 作者自用判断,其他小伙伴需要此消息可以自行修改,但是不要提在pr里 + if (DeviceAlarmMethod.Other.getVal() == Integer.parseInt(deviceAlarm.getAlarmMethod())) { // 发送给平台的报警信息。 发送redis通知 + logger.info("[发送给平台的报警信息]内容:{}", JSONObject.toJSONString(deviceAlarm)); AlarmChannelMessage alarmChannelMessage = new AlarmChannelMessage(); alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod())); alarmChannelMessage.setAlarmDescription(deviceAlarm.getAlarmDescription()); + alarmChannelMessage.setAlarmType(Integer.parseInt(deviceAlarm.getAlarmType())); alarmChannelMessage.setGbId(channelId); redisCatchStorage.sendAlarmMsg(alarmChannelMessage); continue; @@ -264,6 +267,7 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod())); alarmChannelMessage.setAlarmDescription(deviceAlarm.getAlarmDescription()); alarmChannelMessage.setGbId(channelId); + alarmChannelMessage.setAlarmType(Integer.parseInt(deviceAlarm.getAlarmType())); redisCatchStorage.sendAlarmMsg(alarmChannelMessage); return; } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java index cb1e7ae3c..25a3df375 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java @@ -1,9 +1,10 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.cmd; import com.genersoft.iot.vmp.conf.SipConfig; -import com.genersoft.iot.vmp.gb28181.bean.*; +import com.genersoft.iot.vmp.gb28181.bean.Device; +import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; +import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; -import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler; @@ -63,7 +64,6 @@ public class CatalogQueryMessageHandler extends SIPRequestProcessorParent implem @Override public void handForPlatform(RequestEvent evt, ParentPlatform parentPlatform, Element rootElement) { - String key = DeferredResultHolder.CALLBACK_CMD_CATALOG + parentPlatform.getServerGBId(); FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME); try { // 回复200 OK diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceInfoQueryMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceInfoQueryMessageHandler.java index df6e056ae..0faf29452 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceInfoQueryMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceInfoQueryMessageHandler.java @@ -6,6 +6,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.QueryMessageHandler; +import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import gov.nist.javax.sip.message.SIPRequest; import org.dom4j.Element; import org.slf4j.Logger; @@ -21,6 +22,8 @@ import javax.sip.header.FromHeader; import javax.sip.message.Response; import java.text.ParseException; +import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; + @Component public class DeviceInfoQueryMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { @@ -32,6 +35,8 @@ public class DeviceInfoQueryMessageHandler extends SIPRequestProcessorParent imp @Autowired private SIPCommanderFroPlatform cmderFroPlatform; + @Autowired + private IVideoManagerStorage storager; @Override public void afterPropertiesSet() throws Exception { @@ -52,10 +57,20 @@ public class DeviceInfoQueryMessageHandler extends SIPRequestProcessorParent imp responseAck((SIPRequest) evt.getRequest(), Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { logger.error("[命令发送失败] DeviceInfo查询回复: {}", e.getMessage()); + return; } String sn = rootElement.element("SN").getText(); + /*根据WVP原有的数据结构,设备和通道是分开放置,设备信息都是存放在设备表里,通道表里的设备信息不可作为真实信息处理 + 大部分NVR/IPC设备对他的通道信息实现都是返回默认的值没有什么参考价值。NVR/IPC通道我们统一使用设备表的设备信息来作为返回。 + 我们这里使用查询数据库的方式来实现这个设备信息查询的功能,在其他地方对设备信息更新达到正确的目的。*/ + String channelId = getText(rootElement, "DeviceID"); + Device device = storager.queryDeviceInfoByPlatformIdAndChannelId(parentPlatform.getServerGBId(), channelId); + if (device ==null){ + logger.error("[平台没有该通道的使用权限]:platformId"+parentPlatform.getServerGBId()+" deviceID:"+channelId); + return; + } try { - cmderFroPlatform.deviceInfoResponse(parentPlatform, sn, fromHeader.getTag()); + cmderFroPlatform.deviceInfoResponse(parentPlatform,device, sn, fromHeader.getTag()); } catch (SipException | InvalidArgumentException | ParseException e) { logger.error("[命令发送失败] 国标级联 DeviceInfo查询回复: {}", e.getMessage()); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java index 11d239ef3..8b4ae2e1c 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java @@ -102,8 +102,9 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent 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); - recordDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, new ArrayList<>()); releaseRequest(take.getDevice().getDeviceId(), sn); } else { Iterator recordListIterator = recordListElement.elementIterator(); @@ -137,12 +138,11 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent 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); - int count = recordDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, recordList); - logger.info("[国标录像], {}->{}: {}/{}", take.getDevice().getDeviceId(), sn, count, sumNum); } - if (recordDataCatch.isComplete(take.getDevice().getDeviceId(), sn)){ releaseRequest(take.getDevice().getDeviceId(), sn); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/utils/MessageElement.java b/src/main/java/com/genersoft/iot/vmp/gb28181/utils/MessageElement.java new file mode 100644 index 000000000..f94237cd8 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/utils/MessageElement.java @@ -0,0 +1,17 @@ +package com.genersoft.iot.vmp.gb28181.utils; + +import java.lang.annotation.*; + +/** + * @author gaofuwang + * @version 1.0 + * @date 2022/6/28 14:58 + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface MessageElement { + String value(); + + String subVal() default ""; +} 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 index 35d563d90..0ea6d8772 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java @@ -1,5 +1,6 @@ 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; @@ -15,12 +16,16 @@ import org.dom4j.io.SAXReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; +import org.springframework.util.ReflectionUtils; import javax.sip.RequestEvent; import javax.sip.message.Request; import java.io.ByteArrayInputStream; import java.io.StringReader; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.*; /** @@ -411,4 +416,76 @@ public class XmlUtil { } return deviceChannel; } + + /** + * 新增方法支持内部嵌套 + * + * @param element xmlElement + * @param clazz 结果类 + * @param 泛型 + * @return 结果对象 + * @throws NoSuchMethodException + * @throws InvocationTargetException + * @throws InstantiationException + * @throws IllegalAccessException + */ + public static T loadElement(Element element, Class clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + Field[] fields = clazz.getDeclaredFields(); + T t = clazz.getDeclaredConstructor().newInstance(); + for (Field field : fields) { + ReflectionUtils.makeAccessible(field); + MessageElement annotation = field.getAnnotation(MessageElement.class); + if (annotation == null) { + continue; + } + String value = annotation.value(); + String subVal = annotation.subVal(); + Element element1 = element.element(value); + if (element1 == null) { + continue; + } + if ("".equals(subVal)) { + // 无下级数据 + Object fieldVal = element1.isTextOnly() ? element1.getText() : loadElement(element1, field.getType()); + Object o = simpleTypeDeal(field.getType(), fieldVal); + ReflectionUtils.setField(field, t, o); + } else { + // 存在下级数据 + ArrayList list = new ArrayList<>(); + Type genericType = field.getGenericType(); + if (!(genericType instanceof ParameterizedType)) { + continue; + } + Class aClass = (Class) ((ParameterizedType) genericType).getActualTypeArguments()[0]; + for (Element element2 : element1.elements(subVal)) { + list.add(loadElement(element2, aClass)); + } + ReflectionUtils.setField(field, t, list); + } + } + return t; + } + + /** + * 简单类型处理 + * + * @param tClass + * @param val + * @return + */ + private static Object simpleTypeDeal(Class tClass, Object val) { + if (tClass.equals(String.class)) { + return val.toString(); + } + if (tClass.equals(Integer.class)) { + return Integer.valueOf(val.toString()); + } + if (tClass.equals(Double.class)) { + return Double.valueOf(val.toString()); + } + if (tClass.equals(Long.class)) { + return Long.valueOf(val.toString()); + } + return val; + } } \ No newline at end of file diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java index ba95972b2..dae42c75b 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java @@ -10,6 +10,8 @@ import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; +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.media.zlm.dto.HookType; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; @@ -19,7 +21,10 @@ import com.genersoft.iot.vmp.media.zlm.dto.hook.*; import com.genersoft.iot.vmp.service.*; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; +import com.genersoft.iot.vmp.vmanager.bean.DeferredResultEx; +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; +import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -27,6 +32,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.request.async.DeferredResult; import javax.servlet.http.HttpServletRequest; import javax.sip.InvalidArgumentException; @@ -35,20 +41,21 @@ import java.text.ParseException; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; -/** +/** * @description:针对 ZLMediaServer的hook事件监听 * @author: swwheihei - * @date: 2020年5月8日 上午10:46:48 + * @date: 2020年5月8日 上午10:46:48 */ @RestController @RequestMapping("/index/hook") public class ZLMHttpHookListener { - private final static Logger logger = LoggerFactory.getLogger(ZLMHttpHookListener.class); + private final static Logger logger = LoggerFactory.getLogger(ZLMHttpHookListener.class); - @Autowired - private SIPCommander cmder; + @Autowired + private SIPCommander cmder; @Autowired private ISIPCommanderForPlatform commanderFroPlatform; @@ -59,265 +66,243 @@ public class ZLMHttpHookListener { @Autowired private ZLMRTPServerFactory zlmrtpServerFactory; - @Autowired - private IPlayService playService; + @Autowired + private IPlayService playService; - @Autowired - private IVideoManagerStorage storager; + @Autowired + private IVideoManagerStorage storager; - @Autowired - private IRedisCatchStorage redisCatchStorage; + @Autowired + private IRedisCatchStorage redisCatchStorage; - @Autowired - private IDeviceService deviceService; + @Autowired + private IDeviceService deviceService; - @Autowired - private IMediaServerService mediaServerService; + @Autowired + private IMediaServerService mediaServerService; - @Autowired - private IStreamProxyService streamProxyService; + @Autowired + private IStreamProxyService streamProxyService; - @Autowired - private IStreamPushService streamPushService; + @Autowired + private DeferredResultHolder resultHolder; - @Autowired - private IMediaService mediaService; + @Autowired + private IMediaService mediaService; - @Autowired - private EventPublisher eventPublisher; + @Autowired + private EventPublisher eventPublisher; - @Autowired - private ZLMMediaListManager zlmMediaListManager; + @Autowired + private ZLMMediaListManager zlmMediaListManager; - @Autowired - private ZlmHttpHookSubscribe subscribe; + @Autowired + private ZlmHttpHookSubscribe subscribe; - @Autowired - private UserSetting userSetting; + @Autowired + private UserSetting userSetting; - @Autowired - private IUserService userService; + @Autowired + private IUserService userService; - @Autowired - private VideoStreamSessionManager sessionManager; + @Autowired + private VideoStreamSessionManager sessionManager; - @Autowired - private AssistRESTfulUtils assistRESTfulUtils; + @Autowired + private AssistRESTfulUtils assistRESTfulUtils; - @Qualifier("taskExecutor") - @Autowired - private ThreadPoolTaskExecutor taskExecutor; + @Qualifier("taskExecutor") + @Autowired + private ThreadPoolTaskExecutor taskExecutor; - /** - * 服务器定时上报时间,上报间隔可配置,默认10s上报一次 - * - */ - @ResponseBody - @PostMapping(value = "/on_server_keepalive", produces = "application/json;charset=UTF-8") - public JSONObject onServerKeepalive(@RequestBody OnServerKeepaliveHookParam param){ + /** + * 服务器定时上报时间,上报间隔可配置,默认10s上报一次 + */ + @ResponseBody + @PostMapping(value = "/on_server_keepalive", produces = "application/json;charset=UTF-8") + public HookResult onServerKeepalive(@RequestBody OnServerKeepaliveHookParam param) { - logger.info("[ZLM HOOK] 收到zlm心跳:" + param.getMediaServerId()); + logger.info("[ZLM HOOK] 收到zlm心跳:" + param.getMediaServerId()); - taskExecutor.execute(()->{ - List subscribes = this.subscribe.getSubscribes(HookType.on_server_keepalive); - JSONObject json = (JSONObject) JSON.toJSON(param); - if (subscribes != null && subscribes.size() > 0) { - for (ZlmHttpHookSubscribe.Event subscribe : subscribes) { - subscribe.response(null, json); - } - } - }); - mediaServerService.updateMediaServerKeepalive(param.getMediaServerId(), param.getData()); + taskExecutor.execute(() -> { + List subscribes = this.subscribe.getSubscribes(HookType.on_server_keepalive); + JSONObject json = (JSONObject) JSON.toJSON(param); + if (subscribes != null && subscribes.size() > 0) { + for (ZlmHttpHookSubscribe.Event subscribe : subscribes) { + subscribe.response(null, json); + } + } + }); + mediaServerService.updateMediaServerKeepalive(param.getMediaServerId(), param.getData()); - JSONObject ret = new JSONObject(); - ret.put("code", 0); - ret.put("msg", "success"); + return HookResult.SUCCESS(); + } - return ret; - } - - /** - * 播放器鉴权事件,rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件。 - * - */ - @ResponseBody - @PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8") - public JSONObject onPlay(@RequestBody OnPlayHookParam param){ - if (logger.isDebugEnabled()) { - logger.debug("[ZLM HOOK] 播放鉴权:{}->{}" + param.getMediaServerId(), param); - } - String mediaServerId = param.getMediaServerId(); + /** + * 播放器鉴权事件,rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件。 + */ + @ResponseBody + @PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8") + public HookResult onPlay(@RequestBody OnPlayHookParam param) { + if (logger.isDebugEnabled()) { + logger.debug("[ZLM HOOK] 播放鉴权:{}->{}" + param.getMediaServerId(), param); + } + String mediaServerId = param.getMediaServerId(); - taskExecutor.execute(()->{ - JSONObject json = (JSONObject) JSON.toJSON(param); - ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_play, json); - if (subscribe != null ) { - MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId); - if (mediaInfo != null) { - subscribe.response(mediaInfo, json); - } - } - }); - JSONObject ret = new JSONObject(); - if (!"rtp".equals(param.getApp())) { - Map paramMap = urlParamToMap(param.getParams()); - StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream()); - if (streamAuthorityInfo != null && streamAuthorityInfo.getCallId() != null && !streamAuthorityInfo.getCallId().equals(paramMap.get("callId"))) { - ret.put("code", 401); - ret.put("msg", "Unauthorized"); - return ret; - } - } + taskExecutor.execute(() -> { + JSONObject json = (JSONObject) JSON.toJSON(param); + ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_play, json); + if (subscribe != null) { + MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId); + if (mediaInfo != null) { + subscribe.response(mediaInfo, json); + } + } + }); + if (!"rtp".equals(param.getApp())) { + Map paramMap = urlParamToMap(param.getParams()); + StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream()); + if (streamAuthorityInfo != null && streamAuthorityInfo.getCallId() != null && !streamAuthorityInfo.getCallId().equals(paramMap.get("callId"))) { + return new HookResult(401, "Unauthorized"); + } + } - ret.put("code", 0); - ret.put("msg", "success"); - return ret; - } - - /** - * rtsp/rtmp/rtp推流鉴权事件。 - * - */ - @ResponseBody - @PostMapping(value = "/on_publish", produces = "application/json;charset=UTF-8") - public JSONObject onPublish(@RequestBody OnPublishHookParam param) { + return HookResult.SUCCESS(); + } - JSONObject json = (JSONObject) JSON.toJSON(param); + /** + * rtsp/rtmp/rtp推流鉴权事件。 + */ + @ResponseBody + @PostMapping(value = "/on_publish", produces = "application/json;charset=UTF-8") + public HookResultForOnPublish onPublish(@RequestBody OnPublishHookParam param) { - logger.info("[ZLM HOOK]推流鉴权:{}->{}", param.getMediaServerId(), param); - JSONObject ret = new JSONObject(); - String mediaServerId = json.getString("mediaServerId"); - MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId); + JSONObject json = (JSONObject) JSON.toJSON(param); - if (!"rtp".equals(param.getApp())) { - if (userSetting.getPushAuthority()) { - // 推流鉴权 - if (param.getParams() == null) { - logger.info("推流鉴权失败: 缺少不要参数:sign=md5(user表的pushKey)"); - ret.put("code", 401); - ret.put("msg", "Unauthorized"); - return ret; - } - Map paramMap = urlParamToMap(param.getParams()); - String sign = paramMap.get("sign"); - if (sign == null) { - logger.info("推流鉴权失败: 缺少不要参数:sign=md5(user表的pushKey)"); - ret.put("code", 401); - ret.put("msg", "Unauthorized"); - return ret; - } - // 推流自定义播放鉴权码 - String callId = paramMap.get("callId"); - // 鉴权配置 - boolean hasAuthority = userService.checkPushAuthority(callId, sign); - if (!hasAuthority) { - logger.info("推流鉴权失败: sign 无权限: callId={}. sign={}", callId, sign); - ret.put("code", 401); - ret.put("msg", "Unauthorized"); - return ret; - } - StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param); - streamAuthorityInfo.setCallId(callId); - streamAuthorityInfo.setSign(sign); - // 鉴权通过 - redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo); - // 通知assist新的callId - if (mediaInfo != null && mediaInfo.getRecordAssistPort() > 0) { - taskExecutor.execute(()->{ - assistRESTfulUtils.addStreamCallInfo(mediaInfo, param.getApp(), param.getStream(), callId, null); - }); - } - } - }else { - zlmMediaListManager.sendStreamEvent(param.getApp(),param.getStream(), param.getMediaServerId()); - } + logger.info("[ZLM HOOK]推流鉴权:{}->{}", param.getMediaServerId(), param); - ret.put("code", 0); - ret.put("msg", "success"); + String mediaServerId = json.getString("mediaServerId"); + MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId); - if (!"rtp".equals(param.getApp())) { - ret.put("enable_audio", true); - } - - taskExecutor.execute(()->{ - ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_publish, json); - if (subscribe != null) { - if (mediaInfo != null) { - subscribe.response(mediaInfo, json); - }else { - ret.put("code", 1); - ret.put("msg", "zlm not register"); - } - } - }); - - if ("rtp".equals(param.getApp())) { - ret.put("enable_mp4", userSetting.getRecordSip()); - }else { - ret.put("enable_mp4", userSetting.isRecordPushLive()); - } - List ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, param.getStream()); - if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) { - String deviceId = ssrcTransactionForAll.get(0).getDeviceId(); - String channelId = ssrcTransactionForAll.get(0).getChannelId(); - DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId); - if (deviceChannel != null) { - ret.put("enable_audio", deviceChannel.isHasAudio()); - } - // 如果是录像下载就设置视频间隔十秒 - if (ssrcTransactionForAll.get(0).getType() == VideoStreamSessionManager.SessionType.download) { - ret.put("mp4_max_second", 10); - ret.put("enable_mp4", true); - ret.put("enable_audio", true); - } - } - return ret; - } - - /** - * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。 - * - */ - @ResponseBody - @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8") - public JSONObject onStreamChanged(@RequestBody OnStreamChangedHookParam param){ - - if (param.isRegist()) { - logger.info("[ZLM HOOK] 流注册, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); - }else { - logger.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); - } + if (!"rtp".equals(param.getApp())) { + if (userSetting.getPushAuthority()) { + // 推流鉴权 + if (param.getParams() == null) { + logger.info("推流鉴权失败: 缺少不要参数:sign=md5(user表的pushKey)"); + return new HookResultForOnPublish(401, "Unauthorized"); + } + Map paramMap = urlParamToMap(param.getParams()); + String sign = paramMap.get("sign"); + if (sign == null) { + logger.info("推流鉴权失败: 缺少不要参数:sign=md5(user表的pushKey)"); + return new HookResultForOnPublish(401, "Unauthorized"); + } + // 推流自定义播放鉴权码 + String callId = paramMap.get("callId"); + // 鉴权配置 + boolean hasAuthority = userService.checkPushAuthority(callId, sign); + if (!hasAuthority) { + logger.info("推流鉴权失败: sign 无权限: callId={}. sign={}", callId, sign); + return new HookResultForOnPublish(401, "Unauthorized"); + } + StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param); + streamAuthorityInfo.setCallId(callId); + streamAuthorityInfo.setSign(sign); + // 鉴权通过 + redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo); + // 通知assist新的callId + if (mediaInfo != null && mediaInfo.getRecordAssistPort() > 0) { + taskExecutor.execute(() -> { + assistRESTfulUtils.addStreamCallInfo(mediaInfo, param.getApp(), param.getStream(), callId, null); + }); + } + } + } else { + zlmMediaListManager.sendStreamEvent(param.getApp(), param.getStream(), param.getMediaServerId()); + } - JSONObject json = (JSONObject) JSON.toJSON(param); - taskExecutor.execute(()-> { - ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json); - if (subscribe != null) { - MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId()); - if (mediaInfo != null) { - subscribe.response(mediaInfo, json); - } - } - // 流消失移除redis play - List tracks = param.getTracks(); - if (param.isRegist()) { - if (param.getOriginType() == OriginType.RTMP_PUSH.ordinal() - || param.getOriginType() == OriginType.RTSP_PUSH.ordinal() - || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) { + HookResultForOnPublish result = HookResultForOnPublish.SUCCESS(); + if (!"rtp".equals(param.getApp())) { + result.setEnable_audio(true); + } - StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream()); - if (streamAuthorityInfo == null) { - streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param); - } else { - streamAuthorityInfo.setOriginType(param.getOriginType()); - streamAuthorityInfo.setOriginTypeStr(param.getOriginTypeStr()); - } - redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo); - } - } else { - redisCatchStorage.removeStreamAuthorityInfo(param.getApp(), param.getStream()); - } - }); + taskExecutor.execute(() -> { + ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_publish, json); + if (subscribe != null) { + if (mediaInfo != null) { + subscribe.response(mediaInfo, json); + } else { + new HookResultForOnPublish(1, "zlm not register"); + } + } + }); + + if ("rtp".equals(param.getApp())) { + result.setEnable_mp4(userSetting.getRecordSip()); + } else { + result.setEnable_mp4(userSetting.isRecordPushLive()); + } + List ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, param.getStream()); + if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) { + String deviceId = ssrcTransactionForAll.get(0).getDeviceId(); + String channelId = ssrcTransactionForAll.get(0).getChannelId(); + DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId); + if (deviceChannel != null) { + result.setEnable_audio(deviceChannel.isHasAudio()); + } + // 如果是录像下载就设置视频间隔十秒 + if (ssrcTransactionForAll.get(0).getType() == VideoStreamSessionManager.SessionType.download) { + result.setMp4_max_second(10); + result.setEnable_audio(true); + result.setEnable_mp4(true); + } + } + return result; + } + + /** + * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。 + */ + @ResponseBody + @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8") + public HookResult onStreamChanged(@RequestBody OnStreamChangedHookParam param) { + + if (param.isRegist()) { + logger.info("[ZLM HOOK] 流注册, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); + } else { + logger.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); + } + + + JSONObject json = (JSONObject) JSON.toJSON(param); + taskExecutor.execute(() -> { + ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json); + if (subscribe != null) { + MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId()); + if (mediaInfo != null) { + subscribe.response(mediaInfo, json); + } + } + // 流消失移除redis play + List tracks = param.getTracks(); + if (param.isRegist()) { + if (param.getOriginType() == OriginType.RTMP_PUSH.ordinal() + || param.getOriginType() == OriginType.RTSP_PUSH.ordinal() + || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) { + + StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream()); + if (streamAuthorityInfo == null) { + streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param); + } else { + streamAuthorityInfo.setOriginType(param.getOriginType()); + streamAuthorityInfo.setOriginTypeStr(param.getOriginTypeStr()); + } + redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo); + } + } else { + redisCatchStorage.removeStreamAuthorityInfo(param.getApp(), param.getStream()); + } if ("rtsp".equals(param.getSchema())){ logger.info("流变化:注册->{}, app->{}, stream->{}", param.isRegist(), param.getApp(), param.getStream()); @@ -465,72 +450,57 @@ public class ZLMHttpHookListener { GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream()); if (gbStream != null) { // eventPublisher.catalogEventPublishForStream(null, gbStream, CatalogEvent.OFF); - } - zlmMediaListManager.removeMedia(param.getApp(), param.getStream()); - } - if (type != null) { - // 发送流变化redis消息 - JSONObject jsonObject = new JSONObject(); - jsonObject.put("serverId", userSetting.getServerId()); - jsonObject.put("app", param.getApp()); - jsonObject.put("stream", param.getStream()); - jsonObject.put("register", param.isRegist()); - jsonObject.put("mediaServerId", param.getMediaServerId()); - redisCatchStorage.sendStreamChangeMsg(type, jsonObject); - } - } - } - } - if (!param.isRegist()) { - List sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream()); - if (sendRtpItems.size() > 0) { - for (SendRtpItem sendRtpItem : sendRtpItems) { - if (sendRtpItem.getApp().equals(param.getApp())) { - String platformId = sendRtpItem.getPlatformId(); - ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId); - Device device = deviceService.getDevice(platformId); + } + zlmMediaListManager.removeMedia(param.getApp(), param.getStream()); + } + if (type != null) { + // 发送流变化redis消息 + JSONObject jsonObject = new JSONObject(); + jsonObject.put("serverId", userSetting.getServerId()); + jsonObject.put("app", param.getApp()); + jsonObject.put("stream", param.getStream()); + jsonObject.put("register", param.isRegist()); + jsonObject.put("mediaServerId", param.getMediaServerId()); + redisCatchStorage.sendStreamChangeMsg(type, jsonObject); + } + } + } + } + if (!param.isRegist()) { + List sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream()); + if (sendRtpItems.size() > 0) { + for (SendRtpItem sendRtpItem : sendRtpItems) { + if (sendRtpItem.getApp().equals(param.getApp())) { + String platformId = sendRtpItem.getPlatformId(); + ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId); + Device device = deviceService.getDevice(platformId); - try { - if (platform != null) { - commanderFroPlatform.streamByeCmd(platform, sendRtpItem); - }else { - if (sendRtpItem.isOnlyAudio()) { - AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); - if (audioBroadcastCatch != null) { -// playService.stopAudioBroadcast(device.getDeviceId(), sendRtpItem.getChannelId()); - if ("talk".equals(param.getApp())) { -// cmder.streamByeCmd(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null); - }else { -// cmder.streamByeCmd(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null); - } - } - } + try { + if (platform != null) { + commanderFroPlatform.streamByeCmd(platform, sendRtpItem); + } else { + cmder.streamByeCmd(device, sendRtpItem.getChannelId(), param.getStream(), sendRtpItem.getCallId()); + } + } catch (SipException | InvalidArgumentException | ParseException | + SsrcTransactionNotFoundException e) { + logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); + } + } + } + } + } + } + }); + return HookResult.SUCCESS(); + } - - } - } catch (SipException | InvalidArgumentException | ParseException e) { - logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); - } - } - } - } - } - } - - JSONObject ret = new JSONObject(); - ret.put("code", 0); - ret.put("msg", "success"); - return ret; - } - - /** - * 流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流。 - * - */ - @ResponseBody - @PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8") - public JSONObject onStreamNoneReader(@RequestBody OnStreamNoneReaderHookParam param){ + /** + * 流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流。 + */ + @ResponseBody + @PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8") + public JSONObject onStreamNoneReader(@RequestBody OnStreamNoneReaderHookParam param) { logger.info("[ZLM HOOK]流无人观看:{]->{}->{}/{}" + param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); JSONObject ret = new JSONObject(); @@ -571,215 +541,243 @@ public class ZLMHttpHookListener { } } - redisCatchStorage.stopPlay(streamInfoForPlayCatch); - storager.stopPlay(streamInfoForPlayCatch.getDeviceID(), streamInfoForPlayCatch.getChannelId()); - return ret; - } - // 录像回放 - StreamInfo streamInfoForPlayBackCatch = redisCatchStorage.queryPlayback(null, null, param.getStream(), null); - if (streamInfoForPlayBackCatch != null ) { - if (streamInfoForPlayBackCatch.isPause()) { - ret.put("close", false); - }else { - Device device = deviceService.getDevice(streamInfoForPlayBackCatch.getDeviceID()); - if (device != null) { - try { - cmder.streamByeCmd(device,streamInfoForPlayBackCatch.getChannelId(), - streamInfoForPlayBackCatch.getStream(), null); - } catch (InvalidArgumentException | ParseException | SipException | - SsrcTransactionNotFoundException e) { - logger.error("[无人观看]回放, 发送BYE失败 {}", e.getMessage()); - } - } - redisCatchStorage.stopPlayback(streamInfoForPlayBackCatch.getDeviceID(), - streamInfoForPlayBackCatch.getChannelId(), streamInfoForPlayBackCatch.getStream(), null); - } - return ret; - } - // 录像下载 - StreamInfo streamInfoForDownload = redisCatchStorage.queryDownload(null, null, param.getStream(), null); - // 进行录像下载时无人观看不断流 - if (streamInfoForDownload != null) { - ret.put("close", false); - return ret; - } - }else { - // 非国标流 推流/拉流代理 - // 拉流代理 - StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream()); - if (streamProxyItem != null ) { - if (streamProxyItem.isEnable_remove_none_reader()) { - // 无人观看自动移除 - ret.put("close", true); - streamProxyService.del(param.getApp(), param.getStream()); - String url = streamProxyItem.getUrl() != null?streamProxyItem.getUrl():streamProxyItem.getSrc_url(); - logger.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除", param.getApp(), param.getStream(), url); - }else if (streamProxyItem.isEnable_disable_none_reader()) { - // 无人观看停用 - ret.put("close", true); - // 修改数据 - streamProxyService.stop(param.getApp(), param.getStream()); - }else { - // 无人观看不做处理 - ret.put("close", false); - } - return ret; - } - // 推流具有主动性,暂时不做处理 + redisCatchStorage.stopPlay(streamInfoForPlayCatch); + storager.stopPlay(streamInfoForPlayCatch.getDeviceID(), streamInfoForPlayCatch.getChannelId()); + return ret; + } + // 录像回放 + StreamInfo streamInfoForPlayBackCatch = redisCatchStorage.queryPlayback(null, null, param.getStream(), null); + if (streamInfoForPlayBackCatch != null) { + if (streamInfoForPlayBackCatch.isPause()) { + ret.put("close", false); + } else { + Device device = deviceService.getDevice(streamInfoForPlayBackCatch.getDeviceID()); + if (device != null) { + try { + cmder.streamByeCmd(device, streamInfoForPlayBackCatch.getChannelId(), + streamInfoForPlayBackCatch.getStream(), null); + } catch (InvalidArgumentException | ParseException | SipException | + SsrcTransactionNotFoundException e) { + logger.error("[无人观看]回放, 发送BYE失败 {}", e.getMessage()); + } + } + redisCatchStorage.stopPlayback(streamInfoForPlayBackCatch.getDeviceID(), + streamInfoForPlayBackCatch.getChannelId(), streamInfoForPlayBackCatch.getStream(), null); + } + return ret; + } + // 录像下载 + StreamInfo streamInfoForDownload = redisCatchStorage.queryDownload(null, null, param.getStream(), null); + // 进行录像下载时无人观看不断流 + if (streamInfoForDownload != null) { + ret.put("close", false); + return ret; + } + } else { + // 非国标流 推流/拉流代理 + // 拉流代理 + StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream()); + if (streamProxyItem != null) { + if (streamProxyItem.isEnable_remove_none_reader()) { + // 无人观看自动移除 + ret.put("close", true); + streamProxyService.del(param.getApp(), param.getStream()); + String url = streamProxyItem.getUrl() != null ? streamProxyItem.getUrl() : streamProxyItem.getSrc_url(); + logger.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除", param.getApp(), param.getStream(), url); + } else if (streamProxyItem.isEnable_disable_none_reader()) { + // 无人观看停用 + ret.put("close", true); + // 修改数据 + streamProxyService.stop(param.getApp(), param.getStream()); + } else { + // 无人观看不做处理 + ret.put("close", false); + } + return ret; + } + // 推流具有主动性,暂时不做处理 // StreamPushItem streamPushItem = streamPushService.getPush(app, streamId); // if (streamPushItem != null) { // // TODO 发送停止 // // } - } - return ret; - } - - /** - * 流未找到事件,用户可以在此事件触发时,立即去拉流,这样可以实现按需拉流;此事件对回复不敏感。 - * - */ - @ResponseBody - @PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8") - public JSONObject onStreamNotFound(@RequestBody OnStreamNotFoundHookParam param){ - logger.info("[ZLM HOOK] 流未找到:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); - taskExecutor.execute(()->{ - MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId()); - if (userSetting.isAutoApplyPlay() && mediaInfo != null) { - if ("rtp".equals(param.getApp())) { - if (mediaInfo.isRtpEnable()) { - String[] s = param.getStream().split("_"); - if (s.length == 2) { - String deviceId = s[0]; - String channelId = s[1]; - Device device = redisCatchStorage.getDevice(deviceId); - if (device != null) { - playService.play(mediaInfo,deviceId, channelId, null, null, null); - } - } - } - }else { - // 拉流代理 - StreamProxyItem streamProxyByAppAndStream = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream()); - if (streamProxyByAppAndStream != null && streamProxyByAppAndStream.isEnable_disable_none_reader()) { - streamProxyService.start(param.getApp(), param.getStream()); - } - } - } - }); + } + return ret; + } + /** + * 流未找到事件,用户可以在此事件触发时,立即去拉流,这样可以实现按需拉流;此事件对回复不敏感。 + */ + @ResponseBody + @PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8") + public DeferredResult onStreamNotFound(@RequestBody OnStreamNotFoundHookParam param) { + logger.info("[ZLM HOOK] 流未找到:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); - JSONObject ret = new JSONObject(); - ret.put("code", 0); - ret.put("msg", "success"); - return ret; - } - - /** - * 服务器启动事件,可以用于监听服务器崩溃重启;此事件对回复不敏感。 - * - */ - @ResponseBody - @PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8") - public JSONObject onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject){ + DeferredResult defaultResult = new DeferredResult<>(); - jsonObject.put("ip", request.getRemoteAddr()); - ZLMServerConfig zlmServerConfig = JSON.to(ZLMServerConfig.class, jsonObject); - zlmServerConfig.setIp(request.getRemoteAddr()); - logger.info("[ZLM HOOK] zlm 启动 " + zlmServerConfig.getGeneralMediaServerId()); - taskExecutor.execute(()->{ - List subscribes = this.subscribe.getSubscribes(HookType.on_server_started); - if (subscribes != null && subscribes.size() > 0) { - for (ZlmHttpHookSubscribe.Event subscribe : subscribes) { - subscribe.response(null, jsonObject); - } - } - mediaServerService.zlmServerOnline(zlmServerConfig); - }); + MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId()); + if (!userSetting.isAutoApplyPlay() || mediaInfo == null) { + defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg())); + return defaultResult; + } - JSONObject ret = new JSONObject(); - ret.put("code", 0); - ret.put("msg", "success"); - return ret; - } + if ("rtp".equals(param.getApp())) { + String[] s = param.getStream().split("_"); + if (!mediaInfo.isRtpEnable() || s.length != 2) { + defaultResult.setResult(HookResult.SUCCESS()); + return defaultResult; + } + String deviceId = s[0]; + String channelId = s[1]; + Device device = redisCatchStorage.getDevice(deviceId); + if (device == null) { + defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg())); + return defaultResult; + } + DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId); + if (deviceChannel == null) { + defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg())); + return defaultResult; + } + logger.info("[ZLM HOOK] 流未找到, 发起自动点播:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); + RequestMessage msg = new RequestMessage(); + String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId; + boolean exist = resultHolder.exist(key, null); + msg.setKey(key); + String uuid = UUID.randomUUID().toString(); + msg.setId(uuid); + DeferredResult result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); + DeferredResultEx deferredResultEx = new DeferredResultEx<>(result); - /** - * 发送rtp(startSendRtp)被动关闭时回调 - */ - @ResponseBody - @PostMapping(value = "/on_send_rtp_stopped", produces = "application/json;charset=UTF-8") - public JSONObject onSendRtpStopped(HttpServletRequest request, @RequestBody OnSendRtpStoppedHookParam param){ + result.onTimeout(() -> { + logger.info("点播接口等待超时"); + // 释放rtpserver + msg.setData(new HookResult(ErrorCode.ERROR100.getCode(), "点播超时")); + resultHolder.invokeResult(msg); + }); + // TODO 在点播未成功的情况下在此调用接口点播会导致返回的流地址ip错误 + deferredResultEx.setFilter(result1 -> { + WVPResult wvpResult1 = (WVPResult) result1; + HookResult resultForEnd = new HookResult(); + resultForEnd.setCode(wvpResult1.getCode()); + resultForEnd.setMsg(wvpResult1.getMsg()); + return resultForEnd; + }); - logger.info("[ZLM HOOK] rtp发送关闭:{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream()); + // 录像查询以channelId作为deviceId查询 + resultHolder.put(key, uuid, deferredResultEx); - JSONObject ret = new JSONObject(); - ret.put("code", 0); - ret.put("msg", "success"); + if (!exist) { + playService.play(mediaInfo, deviceId, channelId, null, eventResult -> { + msg.setData(new HookResult(eventResult.statusCode, eventResult.msg)); + resultHolder.invokeResult(msg); + }, null); + } + return result; + } else { + // 拉流代理 + StreamProxyItem streamProxyByAppAndStream = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream()); + if (streamProxyByAppAndStream != null && streamProxyByAppAndStream.isEnable_disable_none_reader()) { + streamProxyService.start(param.getApp(), param.getStream()); + } + DeferredResult result = new DeferredResult<>(); + result.setResult(HookResult.SUCCESS()); + return result; + } + } - // 查找对应的上级推流,发送停止 - if (!"rtp".equals(param.getApp())) { - return ret; - } - taskExecutor.execute(()->{ - List sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream()); - if (sendRtpItems.size() > 0) { - for (SendRtpItem sendRtpItem : sendRtpItems) { - ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); - try { - commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId()); - } catch (SipException | InvalidArgumentException | ParseException e) { - logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); - } - redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(), - sendRtpItem.getCallId(), sendRtpItem.getStreamId()); - } - } - }); + /** + * 服务器启动事件,可以用于监听服务器崩溃重启;此事件对回复不敏感。 + */ + @ResponseBody + @PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8") + public HookResult onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject) { + jsonObject.put("ip", request.getRemoteAddr()); + ZLMServerConfig zlmServerConfig = JSON.to(ZLMServerConfig.class, jsonObject); + zlmServerConfig.setIp(request.getRemoteAddr()); + logger.info("[ZLM HOOK] zlm 启动 " + zlmServerConfig.getGeneralMediaServerId()); + taskExecutor.execute(() -> { + List subscribes = this.subscribe.getSubscribes(HookType.on_server_started); + if (subscribes != null && subscribes.size() > 0) { + for (ZlmHttpHookSubscribe.Event subscribe : subscribes) { + subscribe.response(null, jsonObject); + } + } + mediaServerService.zlmServerOnline(zlmServerConfig); + }); - return ret; - } + return HookResult.SUCCESS(); + } - /** - * rtpServer收流超时 - */ - @ResponseBody - @PostMapping(value = "/on_rtp_server_timeout", produces = "application/json;charset=UTF-8") - public JSONObject onRtpServerTimeout(HttpServletRequest request, @RequestBody OnRtpServerTimeoutHookParam param){ - logger.info("[ZLM HOOK] rtpServer rtp超时:{}->{}({})", param.getMediaServerId(), param.getStream_id(), param.getSsrc()); + /** + * 发送rtp(startSendRtp)被动关闭时回调 + */ + @ResponseBody + @PostMapping(value = "/on_send_rtp_stopped", produces = "application/json;charset=UTF-8") + public HookResult onSendRtpStopped(HttpServletRequest request, @RequestBody OnSendRtpStoppedHookParam param) { - JSONObject ret = new JSONObject(); - ret.put("code", 0); - ret.put("msg", "success"); + logger.info("[ZLM HOOK] rtp发送关闭:{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream()); - taskExecutor.execute(()->{ - JSONObject json = (JSONObject) JSON.toJSON(param); - List subscribes = this.subscribe.getSubscribes(HookType.on_rtp_server_timeout); - if (subscribes != null && subscribes.size() > 0) { - for (ZlmHttpHookSubscribe.Event subscribe : subscribes) { - subscribe.response(null, json); - } - } - }); + // 查找对应的上级推流,发送停止 + if (!"rtp".equals(param.getApp())) { + return HookResult.SUCCESS(); + } + taskExecutor.execute(() -> { + List sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream()); + if (sendRtpItems.size() > 0) { + for (SendRtpItem sendRtpItem : sendRtpItems) { + ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); + try { + commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId()); + } catch (SipException | InvalidArgumentException | ParseException e) { + logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); + } + redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(), + sendRtpItem.getCallId(), sendRtpItem.getStreamId()); + } + } + }); - return ret; - } + return HookResult.SUCCESS(); + } - private Map urlParamToMap(String params) { - HashMap map = new HashMap<>(); - if (ObjectUtils.isEmpty(params)) { - return map; - } - String[] paramsArray = params.split("&"); - if (paramsArray.length == 0) { - return map; - } - for (String param : paramsArray) { - String[] paramArray = param.split("="); - if (paramArray.length == 2){ - map.put(paramArray[0], paramArray[1]); - } - } - return map; - } + /** + * rtpServer收流超时 + */ + @ResponseBody + @PostMapping(value = "/on_rtp_server_timeout", produces = "application/json;charset=UTF-8") + public HookResult onRtpServerTimeout(HttpServletRequest request, @RequestBody OnRtpServerTimeoutHookParam param) { + logger.info("[ZLM HOOK] rtpServer收流超时:{}->{}({})", param.getMediaServerId(), param.getStream_id(), param.getSsrc()); + + taskExecutor.execute(() -> { + JSONObject json = (JSONObject) JSON.toJSON(param); + List subscribes = this.subscribe.getSubscribes(HookType.on_rtp_server_timeout); + if (subscribes != null && subscribes.size() > 0) { + for (ZlmHttpHookSubscribe.Event subscribe : subscribes) { + subscribe.response(null, json); + } + } + }); + + return HookResult.SUCCESS(); + } + + private Map urlParamToMap(String params) { + HashMap map = new HashMap<>(); + if (ObjectUtils.isEmpty(params)) { + return map; + } + String[] paramsArray = params.split("&"); + if (paramsArray.length == 0) { + return map; + } + for (String param : paramsArray) { + String[] paramArray = param.split("="); + if (paramArray.length == 2) { + map.put(paramArray[0], paramArray[1]); + } + } + return map; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java index fc7d90af7..b6753c436 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java @@ -36,7 +36,7 @@ public class ZLMRESTfulUtils { // 设置连接超时时间 httpClientBuilder.connectTimeout(5,TimeUnit.SECONDS); // 设置读取超时时间 - httpClientBuilder.readTimeout(15,TimeUnit.SECONDS); + httpClientBuilder.readTimeout(10,TimeUnit.SECONDS); // 设置连接池 httpClientBuilder.connectionPool(new ConnectionPool(16, 5, TimeUnit.MINUTES)); if (logger.isDebugEnabled()) { @@ -189,6 +189,7 @@ public class ZLMRESTfulUtils { FileOutputStream outStream = new FileOutputStream(snapFile); outStream.write(Objects.requireNonNull(response.body()).bytes()); + outStream.flush(); outStream.close(); } else { logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResult.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResult.java new file mode 100644 index 000000000..a2da561d8 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResult.java @@ -0,0 +1,36 @@ +package com.genersoft.iot.vmp.media.zlm.dto.hook; + +public class HookResult { + + private int code; + private String msg; + + + public HookResult() { + } + + public HookResult(int code, String msg) { + this.code = code; + this.msg = msg; + } + + public static HookResult SUCCESS(){ + return new HookResult(0, "success"); + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java new file mode 100644 index 000000000..38aa87b32 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java @@ -0,0 +1,44 @@ +package com.genersoft.iot.vmp.media.zlm.dto.hook; + +public class HookResultForOnPublish extends HookResult{ + + private boolean enable_audio; + private boolean enable_mp4; + private int mp4_max_second; + + public HookResultForOnPublish() { + } + + public static HookResultForOnPublish SUCCESS(){ + return new HookResultForOnPublish(0, "success"); + } + + public HookResultForOnPublish(int code, String msg) { + setCode(code); + setMsg(msg); + } + + public boolean isEnable_audio() { + return enable_audio; + } + + public void setEnable_audio(boolean enable_audio) { + this.enable_audio = enable_audio; + } + + public boolean isEnable_mp4() { + return enable_mp4; + } + + public void setEnable_mp4(boolean enable_mp4) { + this.enable_mp4 = enable_mp4; + } + + public int getMp4_max_second() { + return mp4_max_second; + } + + public void setMp4_max_second(int mp4_max_second) { + this.mp4_max_second = mp4_max_second; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java index dc9d927c7..89acb065a 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java @@ -152,6 +152,10 @@ public class GbStreamServiceImpl implements IGbStreamService { @Override public void sendCatalogMsg(GbStream gbStream, String type) { + if (gbStream == null || type == null) { + logger.warn("[发送目录订阅]类型:流信息或类型为NULL"); + return; + } List gbStreams = new ArrayList<>(); if (gbStream.getGbId() != null) { gbStreams.add(gbStream); diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java index 28b540562..522879a80 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java @@ -33,6 +33,7 @@ import com.genersoft.iot.vmp.service.bean.SSRCInfo; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.dao.MediaServerMapper; import com.genersoft.iot.vmp.utils.DateUtil; +import com.genersoft.iot.vmp.utils.JsonUtil; import com.genersoft.iot.vmp.utils.redis.RedisUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import okhttp3.OkHttpClient; @@ -241,7 +242,10 @@ public class MediaServerServiceImpl implements IMediaServerService { String onlineKey = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetting.getServerId(); for (Object mediaServerKey : mediaServerKeys) { String key = (String) mediaServerKey; - MediaServerItem mediaServerItem = (MediaServerItem) RedisUtil.get(key); + MediaServerItem mediaServerItem = JsonUtil.redisJsonToObject(key, MediaServerItem.class); + if (Objects.isNull(mediaServerItem)) { + continue; + } // 检查状态 Double aDouble = RedisUtil.zScore(onlineKey, mediaServerItem.getId()); if (aDouble != null) { @@ -293,7 +297,7 @@ public class MediaServerServiceImpl implements IMediaServerService { return null; } String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + mediaServerId; - return (MediaServerItem)RedisUtil.get(key); + return JsonUtil.redisJsonToObject(key, MediaServerItem.class); } @@ -410,8 +414,10 @@ public class MediaServerServiceImpl implements IMediaServerService { SsrcConfig ssrcConfig = new SsrcConfig(zlmServerConfig.getGeneralMediaServerId(), null, sipConfig.getDomain()); serverItem.setSsrcConfig(ssrcConfig); }else { - MediaServerItem mediaServerItemInRedis = (MediaServerItem)RedisUtil.get(key); - serverItem.setSsrcConfig(mediaServerItemInRedis.getSsrcConfig()); + MediaServerItem mediaServerItemInRedis = JsonUtil.redisJsonToObject(key, MediaServerItem.class); + if (Objects.nonNull(mediaServerItemInRedis)) { + serverItem.setSsrcConfig(mediaServerItemInRedis.getSsrcConfig()); + } } RedisUtil.set(key, serverItem); resetOnlineServerItem(serverItem); diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java index 45166de52..b98f18861 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java @@ -184,7 +184,9 @@ public class StreamPushServiceImpl implements IStreamPushService { @Override public boolean stop(String app, String streamId) { StreamPushItem streamPushItem = streamPushMapper.selectOne(app, streamId); - gbStreamService.sendCatalogMsg(streamPushItem, CatalogEvent.DEL); + if (streamPushItem != null) { + gbStreamService.sendCatalogMsg(streamPushItem, CatalogEvent.DEL); + } platformGbStreamMapper.delByAppAndStream(app, streamId); gbStreamMapper.del(app, streamId); diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java index 6aba87957..2e18db39d 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java @@ -62,16 +62,16 @@ public class RedisAlarmMsgListener implements MessageListener { } String gbId = alarmChannelMessage.getGbId(); - DeviceAlarm deviceAlarm = new DeviceAlarm(); - deviceAlarm.setCreateTime(DateUtil.getNow()); - deviceAlarm.setChannelId(gbId); - deviceAlarm.setAlarmDescription(alarmChannelMessage.getAlarmDescription()); - deviceAlarm.setAlarmMethod("" + alarmChannelMessage.getAlarmSn()); - deviceAlarm.setAlarmPriority("1"); - deviceAlarm.setAlarmTime(DateUtil.getNowForISO8601()); - deviceAlarm.setAlarmType("1"); - deviceAlarm.setLongitude(0D); - deviceAlarm.setLatitude(0D); + DeviceAlarm deviceAlarm = new DeviceAlarm(); + deviceAlarm.setCreateTime(DateUtil.getNow()); + deviceAlarm.setChannelId(gbId); + deviceAlarm.setAlarmDescription(alarmChannelMessage.getAlarmDescription()); + deviceAlarm.setAlarmMethod("" + alarmChannelMessage.getAlarmSn()); + deviceAlarm.setAlarmType("" + alarmChannelMessage.getAlarmType()); + deviceAlarm.setAlarmPriority("1"); + deviceAlarm.setAlarmTime(DateUtil.getNowForISO8601()); + deviceAlarm.setLongitude(0); + deviceAlarm.setLatitude(0); if (ObjectUtils.isEmpty(gbId)) { // 发送给所有的上级 diff --git a/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java b/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java index b4644bfb4..0b0a7d91b 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java @@ -2,7 +2,6 @@ package com.genersoft.iot.vmp.storager; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; -import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import com.genersoft.iot.vmp.storager.dao.dto.ChannelSourceInfo; import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce; @@ -186,7 +185,13 @@ public interface IVideoManagerStorage { Device queryVideoDeviceByPlatformIdAndChannelId(String platformId, String channelId); - + /** + * 针对deviceinfo指令的查询接口 + * @param platformId 平台id + * @param channelId 通道id + * @return 设备信息 + */ + Device queryDeviceInfoByPlatformIdAndChannelId(String platformId, String channelId); /** * 添加Mobile Position设备移动位置 * @param mobilePosition @@ -324,6 +329,8 @@ public interface IVideoManagerStorage { */ boolean resetChannels(String deviceId, List deviceChannelList); + boolean updateChannels(String deviceId, List deviceChannelList); + /** * 获取目录信息 * @param platformId diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceAlarmMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceAlarmMapper.java index f67e152f4..f927d51eb 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceAlarmMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceAlarmMapper.java @@ -1,9 +1,10 @@ package com.genersoft.iot.vmp.storager.dao; import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; -import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; -import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce; -import org.apache.ibatis.annotations.*; +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; import org.springframework.stereotype.Repository; import java.util.List; @@ -20,7 +21,7 @@ public interface DeviceAlarmMapper { int add(DeviceAlarm alarm); - @Select(value = {" ") int delChannelForGBByCatalogId(String platformId, String catalogId); + + @Select("select dc.channelId deviceId,dc.name,d.manufacturer,d.model,d.firmware\n" + + "from platform_gb_channel pgc\n" + + " left join device_channel dc on dc.id = pgc.deviceChannelId\n" + + " left join device d on dc.deviceId = d.deviceId\n" + + "where dc.channelId = #{channelId} and pgc.platformId=#{platformId}") + List queryDeviceInfoByPlatformIdAndChannelId(String platformId, String channelId); + + @Select("SELECT pgc.platformId FROM platform_gb_channel pgc left join device_channel dc on dc.id = pgc.deviceChannelId WHERE dc.channelId='${channelId}'") + List queryParentPlatformByChannelId(String channelId); } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java b/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java index 8cf5293bc..e997e4d5b 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java @@ -17,6 +17,7 @@ import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper; import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo; import com.genersoft.iot.vmp.utils.DateUtil; +import com.genersoft.iot.vmp.utils.JsonUtil; import com.genersoft.iot.vmp.utils.SystemInfoUtils; import com.genersoft.iot.vmp.utils.redis.RedisUtil; import org.slf4j.Logger; @@ -157,7 +158,10 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { } for (Object player : players) { String key = (String) player; - StreamInfo streamInfo = (StreamInfo) RedisUtil.get(key); + StreamInfo streamInfo = JsonUtil.redisJsonToObject(key, StreamInfo.class); + if (Objects.isNull(streamInfo)) { + continue; + } streamInfos.put(streamInfo.getDeviceID() + "_" + streamInfo.getChannelId(), streamInfo); } return streamInfos; @@ -624,8 +628,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { @Override public ThirdPartyGB queryMemberNoGBId(String queryKey) { String key = VideoManagerConstants.WVP_STREAM_GB_ID_PREFIX + queryKey; - JSONObject jsonObject = (JSONObject)RedisUtil.get(key); - return jsonObject.to(ThirdPartyGB.class); + return JsonUtil.redisJsonToObject(key, ThirdPartyGB.class); } @Override @@ -664,7 +667,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { @Override public Device getDevice(String deviceId) { String key = VideoManagerConstants.DEVICE_PREFIX + userSetting.getServerId() + "_" + deviceId; - return (Device)RedisUtil.get(key); + return JsonUtil.redisJsonToObject(key, Device.class); } @Override @@ -676,7 +679,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { @Override public GPSMsgInfo getGpsMsgInfo(String gbId) { String key = VideoManagerConstants.WVP_STREAM_GPS_MSG_PREFIX + userSetting.getServerId() + "_" + gbId; - return (GPSMsgInfo)RedisUtil.get(key); + return JsonUtil.redisJsonToObject(key, GPSMsgInfo.class); } @Override @@ -686,9 +689,9 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { List keys = RedisUtil.scan(scanKey); for (Object o : keys) { String key = (String) o; - GPSMsgInfo gpsMsgInfo = (GPSMsgInfo) RedisUtil.get(key); - if (!gpsMsgInfo.isStored()) { // 只取没有存过得 - result.add((GPSMsgInfo) RedisUtil.get(key)); + GPSMsgInfo gpsMsgInfo = JsonUtil.redisJsonToObject(key, GPSMsgInfo.class); + if (Objects.nonNull(gpsMsgInfo) && !gpsMsgInfo.isStored()) { // 只取没有存过得 + result.add(JsonUtil.redisJsonToObject(key, GPSMsgInfo.class)); } } @@ -710,7 +713,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { @Override public StreamAuthorityInfo getStreamAuthorityInfo(String app, String stream) { String key = VideoManagerConstants.MEDIA_STREAM_AUTHORITY + userSetting.getServerId() + "_" + app+ "_" + stream ; - return (StreamAuthorityInfo) RedisUtil.get(key); + return JsonUtil.redisJsonToObject(key, StreamAuthorityInfo.class); } @@ -721,7 +724,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { List keys = RedisUtil.scan(scanKey); for (Object o : keys) { String key = (String) o; - result.add((StreamAuthorityInfo) RedisUtil.get(key)); + result.add(JsonUtil.redisJsonToObject(key, StreamAuthorityInfo.class)); } return result; } @@ -735,7 +738,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { List keys = RedisUtil.scan(scanKey); if (keys.size() > 0) { String key = (String) keys.get(0); - result = (OnStreamChangedHookParam)RedisUtil.get(key); + result = JsonUtil.redisJsonToObject(key, OnStreamChangedHookParam.class); } return result; @@ -827,7 +830,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { @Override public void sendAlarmMsg(AlarmChannelMessage msg) { - String key = VideoManagerConstants.VM_MSG_SUBSCRIBE_ALARM; + String key = VideoManagerConstants.VM_MSG_SUBSCRIBE_ALARM_RECEIVE; logger.info("[redis发送通知] 报警{}: {}", key, JSON.toJSON(msg)); RedisUtil.convertAndSend(key, (JSONObject)JSON.toJSON(msg)); } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java b/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java index 139f018cb..655a54c51 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java @@ -126,6 +126,15 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage { if (allChannelMap.containsKey(deviceChannel.getChannelId())) { deviceChannel.setStreamId(allChannelMap.get(deviceChannel.getChannelId()).getStreamId()); deviceChannel.setHasAudio(allChannelMap.get(deviceChannel.getChannelId()).isHasAudio()); + if (allChannelMap.get(deviceChannel.getChannelId()).getStatus() !=deviceChannel.getStatus()){ + List strings = platformChannelMapper.queryParentPlatformByChannelId(deviceChannel.getChannelId()); + if (!CollectionUtils.isEmpty(strings)){ + strings.forEach(platformId->{ + eventPublisher.catalogEventPublish(platformId, deviceChannel, deviceChannel.getStatus()==1?CatalogEvent.ON:CatalogEvent.OFF); + }); + } + + } } channels.add(deviceChannel); if (!ObjectUtils.isEmpty(deviceChannel.getParentId())) { @@ -187,6 +196,119 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage { } + + @Override + public boolean updateChannels(String deviceId, List deviceChannelList) { + if (CollectionUtils.isEmpty(deviceChannelList)) { + return false; + } + List allChannels = deviceChannelMapper.queryAllChannels(deviceId); + Map allChannelMap = new ConcurrentHashMap<>(); + if (allChannels.size() > 0) { + for (DeviceChannel deviceChannel : allChannels) { + allChannelMap.put(deviceChannel.getChannelId(), deviceChannel); + } + } + List addChannels = new ArrayList<>(); + List updateChannels = new ArrayList<>(); + + + TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition); + // 数据去重 + StringBuilder stringBuilder = new StringBuilder(); + Map subContMap = new HashMap<>(); + if (deviceChannelList.size() > 0) { + // 数据去重 + Set gbIdSet = new HashSet<>(); + for (DeviceChannel deviceChannel : deviceChannelList) { + if (!gbIdSet.contains(deviceChannel.getChannelId())) { + gbIdSet.add(deviceChannel.getChannelId()); + if (allChannelMap.containsKey(deviceChannel.getChannelId())) { + deviceChannel.setStreamId(allChannelMap.get(deviceChannel.getChannelId()).getStreamId()); + deviceChannel.setHasAudio(allChannelMap.get(deviceChannel.getChannelId()).isHasAudio()); + updateChannels.add(deviceChannel); + }else { + addChannels.add(deviceChannel); + } + if (!ObjectUtils.isEmpty(deviceChannel.getParentId())) { + if (subContMap.get(deviceChannel.getParentId()) == null) { + subContMap.put(deviceChannel.getParentId(), 1); + }else { + Integer count = subContMap.get(deviceChannel.getParentId()); + subContMap.put(deviceChannel.getParentId(), count++); + } + } + }else { + stringBuilder.append(deviceChannel.getChannelId()).append(","); + } + } + if (addChannels.size() > 0) { + for (DeviceChannel channel : addChannels) { + if (subContMap.get(channel.getChannelId()) != null){ + channel.setSubCount(subContMap.get(channel.getChannelId())); + } + } + } + if (updateChannels.size() > 0) { + for (DeviceChannel channel : updateChannels) { + if (subContMap.get(channel.getChannelId()) != null){ + channel.setSubCount(subContMap.get(channel.getChannelId())); + } + } + } + + } + if (stringBuilder.length() > 0) { + logger.info("[目录查询]收到的数据存在重复: {}" , stringBuilder); + } + if(CollectionUtils.isEmpty(updateChannels) && CollectionUtils.isEmpty(addChannels) ){ + logger.info("通道更新,数据为空={}" , deviceChannelList); + return false; + } + try { + int limitCount = 300; + boolean result = false; + if (addChannels.size() > 0) { + if (addChannels.size() > limitCount) { + for (int i = 0; i < addChannels.size(); i += limitCount) { + int toIndex = i + limitCount; + if (i + limitCount > addChannels.size()) { + toIndex = addChannels.size(); + } + result = result || deviceChannelMapper.batchAdd(addChannels.subList(i, toIndex)) < 0; + } + }else { + result = result || deviceChannelMapper.batchAdd(addChannels) < 0; + } + } + if (updateChannels.size() > 0) { + if (updateChannels.size() > limitCount) { + for (int i = 0; i < updateChannels.size(); i += limitCount) { + int toIndex = i + limitCount; + if (i + limitCount > updateChannels.size()) { + toIndex = updateChannels.size(); + } + result = result || deviceChannelMapper.batchUpdate(updateChannels.subList(i, toIndex)) < 0; + } + }else { + result = result || deviceChannelMapper.batchUpdate(updateChannels) < 0; + } + } + if (result) { + //事务回滚 + dataSourceTransactionManager.rollback(transactionStatus); + }else { + //手动提交 + dataSourceTransactionManager.commit(transactionStatus); + } + return true; + }catch (Exception e) { + e.printStackTrace(); + dataSourceTransactionManager.rollback(transactionStatus); + return false; + } + } + @Override public void deviceChannelOnline(String deviceId, String channelId) { deviceChannelMapper.online(deviceId, channelId); @@ -464,6 +586,20 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage { } + @Override + public Device queryDeviceInfoByPlatformIdAndChannelId(String platformId, String channelId) { + List devices = platformChannelMapper.queryDeviceInfoByPlatformIdAndChannelId(platformId, channelId); + if (devices.size() > 1) { + // 出现长度大于0的时候肯定是国标通道的ID重复了 + logger.warn("国标ID存在重复:{}", channelId); + } + if (devices.size() == 0) { + return null; + }else { + return devices.get(0); + } + } + /** * 查询最新移动位置 * @param deviceId diff --git a/src/main/java/com/genersoft/iot/vmp/utils/JsonUtil.java b/src/main/java/com/genersoft/iot/vmp/utils/JsonUtil.java new file mode 100644 index 000000000..60e2dbe2e --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/utils/JsonUtil.java @@ -0,0 +1,36 @@ +package com.genersoft.iot.vmp.utils; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.genersoft.iot.vmp.utils.redis.RedisUtil; + +import java.util.Objects; + +/** + * JsonUtil + * + * @author KunLong-Luo + * @version 1.0.0 + * @since 2023/2/2 15:24 + */ +public final class JsonUtil { + + private JsonUtil() { + } + + /** + * safe json type conversion + * + * @param key redis key + * @param clazz cast type + * @param + * @return result type + */ + public static T redisJsonToObject(String key, Class clazz) { + Object jsonObject = RedisUtil.get(key); + if (Objects.isNull(jsonObject)) { + return null; + } + return clazz.cast(jsonObject); + } +} \ No newline at end of file diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java index 18618e74f..ff0d8b4f3 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java @@ -110,7 +110,7 @@ public class DeviceControl { msg.setKey(key); msg.setData(String.format("开始/停止录像操作失败,错误码: %s, %s", event.statusCode, event.msg)); resultHolder.invokeAllResult(msg); - }); + },null); } catch (InvalidArgumentException | SipException | ParseException e) { logger.error("[命令发送失败] 开始/停止录像: {}", e.getMessage()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); @@ -143,7 +143,7 @@ public class DeviceControl { msg.setKey(key); msg.setData(String.format("布防/撤防操作失败,错误码: %s, %s", event.statusCode, event.msg)); resultHolder.invokeResult(msg); - }); + },null); } catch (InvalidArgumentException | SipException | ParseException e) { logger.error("[命令发送失败] 布防/撤防操作: {}", e.getMessage()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage()); @@ -192,7 +192,7 @@ public class DeviceControl { msg.setKey(key); msg.setData(String.format("报警复位操作失败,错误码: %s, %s", event.statusCode, event.msg)); resultHolder.invokeResult(msg); - }); + },null); } catch (InvalidArgumentException | SipException | ParseException e) { logger.error("[命令发送失败] 报警复位: {}", e.getMessage()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); @@ -274,7 +274,7 @@ public class DeviceControl { msg.setKey(key); msg.setData(String.format("看守位控制操作失败,错误码: %s, %s", event.statusCode, event.msg)); resultHolder.invokeResult(msg); - }); + },null); } catch (InvalidArgumentException | SipException | ParseException e) { logger.error("[命令发送失败] 看守位控制: {}", e.getMessage()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java index 511b98d5e..eb6f997d1 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java @@ -1,6 +1,7 @@ package com.genersoft.iot.vmp.vmanager.gb28181.playback; import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.exception.ServiceException; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; @@ -64,13 +65,16 @@ public class PlaybackController { @Autowired private DeferredResultHolder resultHolder; + @Autowired + private UserSetting userSetting; + @Operation(summary = "开始视频回放") @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "startTime", description = "开始时间", required = true) @Parameter(name = "endTime", description = "结束时间", required = true) @GetMapping("/start/{deviceId}/{channelId}") - public DeferredResult> play(@PathVariable String deviceId, @PathVariable String channelId, + public DeferredResult> start(@PathVariable String deviceId, @PathVariable String channelId, String startTime, String endTime) { if (logger.isDebugEnabled()) { @@ -79,7 +83,7 @@ public class PlaybackController { String uuid = UUID.randomUUID().toString(); String key = DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId; - DeferredResult> result = new DeferredResult<>(30000L); + DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); resultHolder.put(key, uuid, result); WVPResult wvpResult = new WVPResult<>(); diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java index 6ebd614b3..91c3cf5bc 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java @@ -11,17 +11,13 @@ import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.github.pagehelper.PageInfo; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.util.DigestUtils; import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import javax.security.sasl.AuthenticationException; @@ -90,7 +86,7 @@ public class UserController { @PostMapping("/add") - @Operation(summary = "停止视频回放") + @Operation(summary = "添加用户") @Parameter(name = "username", description = "用户名", required = true) @Parameter(name = "password", description = "密码(未md5加密的密码)", required = true) @Parameter(name = "roleId", description = "角色ID", required = true) diff --git a/src/main/resources/all-application.yml b/src/main/resources/all-application.yml index 052fa7442..c48c26ef2 100644 --- a/src/main/resources/all-application.yml +++ b/src/main/resources/all-application.yml @@ -167,7 +167,7 @@ user-settings: senior-sdp: false # 保存移动位置历史轨迹:true:保留历史数据,false:仅保留最后的位置(默认) save-position-history: false - # 点播等待超时时间,单位:毫秒 + # 点播/录像回放 等待超时时间,单位:毫秒 play-timeout: 18000 # 上级点播等待超时时间,单位:毫秒 platform-play-timeout: 60000 diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 7d1e116d1..f06bd3e9b 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -1,86 +1,86 @@ spring: - # [可选]上传文件大小限制 - servlet: - multipart: - max-file-size: 10MB - max-request-size: 100MB - # REDIS数据库配置 - redis: - # [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1 - host: 127.0.0.1 - # [必须修改] 端口号 - port: 6379 - # [可选] 数据库 DB - database: 6 - # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接 - password: face2020 - # [可选] 超时时间 - timeout: 10000 - # mysql数据源 - datasource: - type: com.alibaba.druid.pool.DruidDataSource - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true - username: root - password: 123456 - druid: - initialSize: 10 # 连接池初始化连接数 - maxActive: 200 # 连接池最大连接数 - minIdle: 5 # 连接池最小空闲连接数 - maxWait: 60000 # 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 - keepAlive: true # 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作。 - validationQuery: select 1 # 检测连接是否有效sql,要求是查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 - testWhileIdle: true # 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 - testOnBorrow: false # 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 - testOnReturn: false # 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 - poolPreparedStatements: false # 是否開啟PSCache,並且指定每個連線上PSCache的大小 - timeBetweenEvictionRunsMillis: 60000 # 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒 - minEvictableIdleTimeMillis: 300000 # 配置一個連線在池中最小生存的時間,單位是毫秒 - filters: stat,slf4j # 配置监控统计拦截的filters,监控统计用的filter:sta, 日志用的filter:log4j - useGlobalDataSourceStat: true # 合并多个DruidDataSource的监控数据 - # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 - connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=1000 - #stat-view-servlet.url-pattern: /admin/druid/* + # [可选]上传文件大小限制 + servlet: + multipart: + max-file-size: 10MB + max-request-size: 100MB + # REDIS数据库配置 + redis: + # [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1 + host: 127.0.0.1 + # [必须修改] 端口号 + port: 6379 + # [可选] 数据库 DB + database: 6 + # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接 + password: face2020 + # [可选] 超时时间 + timeout: 10000 + # mysql数据源 + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/wvp2?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true + username: root + password: 123456 + druid: + initialSize: 10 # 连接池初始化连接数 + maxActive: 200 # 连接池最大连接数 + minIdle: 5 # 连接池最小空闲连接数 + maxWait: 60000 # 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 + keepAlive: true # 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作。 + validationQuery: select 1 # 检测连接是否有效sql,要求是查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 + testWhileIdle: true # 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 + testOnBorrow: false # 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 + testOnReturn: false # 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 + poolPreparedStatements: false # 是否開啟PSCache,並且指定每個連線上PSCache的大小 + timeBetweenEvictionRunsMillis: 60000 # 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒 + minEvictableIdleTimeMillis: 300000 # 配置一個連線在池中最小生存的時間,單位是毫秒 + filters: stat,slf4j # 配置监控统计拦截的filters,监控统计用的filter:sta, 日志用的filter:log4j + useGlobalDataSourceStat: true # 合并多个DruidDataSource的监控数据 + # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 + connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=1000 + #stat-view-servlet.url-pattern: /admin/druid/* #[可选] WVP监听的HTTP端口, 网页和接口调用都是这个端口 server: - port: 18080 + port: 18080 # 作为28181服务器的配置 sip: - # [必须修改] 本机的IP - ip: 192.168.41.16 - # [可选] 28181服务监听的端口 - port: 5060 - # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007) - # 后两位为行业编码,定义参照附录D.3 - # 3701020049标识山东济南历下区 信息行业接入 - # [可选] - domain: 4401020049 - # [可选] - id: 44010200492000000001 - # [可选] 默认设备认证密码,后续扩展使用设备单独密码, 移除密码将不进行校验 - password: admin123 + # [必须修改] 本机的IP + ip: 192.168.41.16 + # [可选] 28181服务监听的端口 + port: 5060 + # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007) + # 后两位为行业编码,定义参照附录D.3 + # 3701020049标识山东济南历下区 信息行业接入 + # [可选] + domain: 4401020049 + # [可选] + id: 44010200492000000001 + # [可选] 默认设备认证密码,后续扩展使用设备单独密码, 移除密码将不进行校验 + password: admin123 #zlm 默认服务器配置 media: - id: FQ3TF8yT83wh5Wvz - # [必须修改] zlm服务器的内网IP - ip: 192.168.41.16 - # [必须修改] zlm服务器的http.port - http-port: 8091 - # [可选] zlm服务器的hook.admin_params=secret - secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc - # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试 - rtp: - # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输 - enable: true - # [可选] 在此范围内选择端口用于媒体流传输, 必须提前在zlm上配置该属性,不然自动配置此属性可能不成功 - port-range: 30000,30500 # 端口范围 - # [可选] 国标级联在此范围内选择端口发送媒体流, - send-port-range: 30000,30500 # 端口范围 - # 录像辅助服务, 部署此服务可以实现zlm录像的管理与下载, 0 表示不使用 - record-assist-port: 18081 + id: FQ3TF8yT83wh5Wvz + # [必须修改] zlm服务器的内网IP + ip: 192.168.41.16 + # [必须修改] zlm服务器的http.port + http-port: 8091 + # [可选] zlm服务器的hook.admin_params=secret + secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc + # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试 + rtp: + # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输 + enable: true + # [可选] 在此范围内选择端口用于媒体流传输, 必须提前在zlm上配置该属性,不然自动配置此属性可能不成功 + port-range: 30000,30500 # 端口范围 + # [可选] 国标级联在此范围内选择端口发送媒体流, + send-port-range: 30000,30500 # 端口范围 + # 录像辅助服务, 部署此服务可以实现zlm录像的管理与下载, 0 表示不使用 + record-assist-port: 18081 # [可选] 日志配置, 一般不需要改 logging: - config: classpath:logback-spring-local.xml + config: classpath:logback-spring-local.xml diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d74c444c1..4efb527d5 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,3 +1,16 @@ spring: + application: + name: wvp profiles: active: local + # flayway相关配置 + flyway: + enabled: true #是否启用flyway(默认true) + locations: classpath:db/migration #这个路径指的是fly版本控制的sql语句存放的路径,可以多个,可以给每个环境使用不同位置,比如classpath:db/migration,classpath:test/db/migration + baseline-on-migrate: true #开启自动创建flyway元数据表标识 默认: false + # 与 baseline-on-migrate: true 搭配使用,将当前数据库初始版本设置为0 + baseline-version: 0 + clean-disabled: true #禁止flyway执行清理 + # 假如已经执行了版本1和版本3,如果增加了一个版本2,下面这个选项将会允许执行版本2的脚本 + out-of-order: true + table: flyway_schema_history_${spring.application.name} #用于记录所有的版本变化记录 \ No newline at end of file diff --git a/sql/mysql.sql b/src/main/resources/db/migration/V2.6.7_20230201__初始化.sql similarity index 99% rename from sql/mysql.sql rename to src/main/resources/db/migration/V2.6.7_20230201__初始化.sql index d9010e2d0..7a15f905c 100644 --- a/sql/mysql.sql +++ b/src/main/resources/db/migration/V2.6.7_20230201__初始化.sql @@ -39,6 +39,7 @@ CREATE TABLE `device` ( `updateTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `port` int DEFAULT NULL, `expires` int DEFAULT NULL, + `keepaliveIntervalTime` int DEFAULT NULL, `subscribeCycleForCatalog` int DEFAULT NULL, `hostAddress` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `charset` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, diff --git a/web_src/build/utils.js b/web_src/build/utils.js index e534fb0fd..bc98c135e 100644 --- a/web_src/build/utils.js +++ b/web_src/build/utils.js @@ -47,7 +47,8 @@ exports.cssLoaders = function (options) { if (options.extract) { return ExtractTextPlugin.extract({ use: loaders, - fallback: 'vue-style-loader' + fallback: 'vue-style-loader', + publicPath: '../../' }) } else { return ['vue-style-loader'].concat(loaders) diff --git a/web_src/config/index.js b/web_src/config/index.js index c6287d05d..fc0aa1ff5 100644 --- a/web_src/config/index.js +++ b/web_src/config/index.js @@ -8,8 +8,8 @@ module.exports = { dev: { // Paths - assetsSubDirectory: 'static', - assetsPublicPath: '/', + assetsSubDirectory: './static', + assetsPublicPath: './', proxyTable: { '/debug': { target: 'https://default.wvp-pro.cn:18080', @@ -61,7 +61,7 @@ module.exports = { // Paths assetsRoot: path.resolve(__dirname, '../../src/main/resources/static/'), assetsSubDirectory: './static', - assetsPublicPath: '/', + assetsPublicPath: './', /** * Source Maps diff --git a/web_src/src/components/CloudRecord.vue b/web_src/src/components/CloudRecord.vue index b046fc917..bd374fc8f 100644 --- a/web_src/src/components/CloudRecord.vue +++ b/web_src/src/components/CloudRecord.vue @@ -133,7 +133,7 @@ let that = this; this.$axios({ method: 'get', - url:`/record_proxy/${that.mediaServerId}/api/record/list`, + url:`./record_proxy/${that.mediaServerId}/api/record/list`, params: { page: that.currentPage, count: that.count @@ -185,7 +185,7 @@ let that = this; this.$axios({ method: 'delete', - url:`/record_proxy/api/record/delete`, + url:`./record_proxy/api/record/delete`, params: { page: that.currentPage, count: that.count diff --git a/web_src/src/components/CloudRecordDetail.vue b/web_src/src/components/CloudRecordDetail.vue index 8d04eb045..d76101a2d 100644 --- a/web_src/src/components/CloudRecordDetail.vue +++ b/web_src/src/components/CloudRecordDetail.vue @@ -1,14 +1,15 @@