diff --git a/pom.xml b/pom.xml index 9899d275..5b3e945d 100644 --- a/pom.xml +++ b/pom.xml @@ -1,368 +1,382 @@ - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.7.2 - + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.17 + - com.genersoft - wvp-pro - 2.6.9 - web video platform - 国标28181视频平台 - ${project.packaging} + com.genersoft + wvp-pro + 2.6.9 + web video platform + 国标28181视频平台 + ${project.packaging} - - - nexus-aliyun - Nexus aliyun - https://maven.aliyun.com/repository/public - default - - false - - - true - - - - - - nexus-aliyun - Nexus aliyun - https://maven.aliyun.com/repository/public - - false - - - true - - - + + + nexus-aliyun + Nexus aliyun + https://maven.aliyun.com/repository/public + default + + false + + + true + + + - - UTF-8 - MMddHHmm - 3.1.1 + + + nexus-aliyun + Nexus aliyun + https://maven.aliyun.com/repository/public + + false + + + true + + + - - ${project.build.directory}/generated-snippets - ${project.basedir}/docs/asciidoc - ${project.build.directory}/asciidoc - ${project.build.directory}/asciidoc/html - ${project.build.directory}/asciidoc/pdf - + + UTF-8 + MMddHHmm + 3.1.1 - - - 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 - - - - + + ${project.build.directory}/generated-snippets + ${project.basedir}/docs/asciidoc + ${project.build.directory}/asciidoc + ${project.build.directory}/asciidoc/html + ${project.build.directory}/asciidoc/pdf + - - - org.springframework.boot - spring-boot-starter-data-redis - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.mybatis.spring.boot - mybatis-spring-boot-starter - 2.2.2 - - - com.zaxxer - HikariCP - - - - - org.springframework.boot - spring-boot-starter-security - + + + 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 - spring-boot-starter-jdbc - + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.2.2 + + + com.zaxxer + HikariCP + + + + + org.springframework.boot + spring-boot-starter-security + - - - mysql - mysql-connector-java - 8.0.30 - + + org.springframework.boot + spring-boot-starter-jdbc + - - - org.postgresql - postgresql - 42.5.1 - + + + com.mysql + mysql-connector-j + 8.2.0 + - - - - - com.kingbase - kingbase8 - 8.6.0 - system - ${basedir}/libs/jdbc-aarch/kingbase8-8.6.0.jar - + + + org.postgresql + postgresql + 42.5.1 + - - - com.github.pagehelper - pagehelper-spring-boot-starter - 1.4.6 - + + + + + com.kingbase + kingbase8 + 8.6.0 + system + ${basedir}/libs/jdbc-aarch/kingbase8-8.6.0.jar + - - - org.springdoc - springdoc-openapi-ui - 1.6.10 - + + + com.github.pagehelper + pagehelper-spring-boot-starter + 1.4.6 + - - com.github.xiaoymin - knife4j-springdoc-ui - 3.0.3 - + + + org.springdoc + springdoc-openapi-ui + 1.7.0 + + + org.yaml + snakeyaml + + + + + org.yaml + snakeyaml + 2.2 + - - - javax.validation - validation-api - + + com.github.xiaoymin + knife4j-springdoc-ui + 3.0.3 + - - - org.springframework.boot - spring-boot-starter-aop - + + + javax.validation + validation-api + - - - javax.sip - jain-sip-ri - 1.3.0-91 - + + + org.springframework.boot + spring-boot-starter-aop + - - - org.slf4j - log4j-over-slf4j - 1.7.36 - + + + javax.sip + jain-sip-ri + 1.3.0-91 + - - - org.dom4j - dom4j - 2.1.3 - + + + org.slf4j + log4j-over-slf4j + 1.7.36 + - - com.google.guava - guava - 20.0 - + + + org.dom4j + dom4j + 2.1.3 + - - - com.alibaba.fastjson2 - fastjson2 - 2.0.17 - - - com.alibaba.fastjson2 - fastjson2-extension - 2.0.17 - + + + com.alibaba.fastjson2 + fastjson2 + 2.0.17 + + + com.alibaba.fastjson2 + fastjson2-extension + 2.0.17 + - - - com.squareup.okhttp3 - okhttp - 4.10.0 - + + + com.squareup.okhttp3 + okhttp + 4.10.0 + - - - com.squareup.okhttp3 - logging-interceptor - 4.10.0 - + + + com.squareup.okhttp3 + logging-interceptor + 4.10.0 + - - - io.github.rburgst - okhttp-digest - 2.7 - + + + io.github.rburgst + okhttp-digest + 2.7 + - - - - - - + + + + + + - - - org.bitbucket.b_c - jose4j - 0.9.3 - + + + org.bitbucket.b_c + jose4j + 0.9.3 + - - - org.mitre.dsmiley.httpproxy - smiley-http-proxy-servlet - 1.12.1 - + + + org.mitre.dsmiley.httpproxy + smiley-http-proxy-servlet + 1.12.1 + - - - com.alibaba - easyexcel - 3.1.1 - + + + com.alibaba + easyexcel + 3.3.2 + + + org.apache.commons + commons-compress + + + + + org.apache.commons + commons-compress + 1.24.0 + - - - com.github.oshi - oshi-core - 6.2.2 - + + + com.github.oshi + oshi-core + 6.2.2 + - - org.springframework.session - spring-session-core - + + org.springframework.session + spring-session-core + - - - - - - - + + + + + + + - - - com.google.guava - guava - 31.1-jre - + + + com.google.guava + guava + 32.1.3-jre + + + org.springframework.boot + spring-boot-starter-test + test + + - - org.springframework.boot - spring-boot-starter-test - - + + ${project.artifactId}-${project.version}-${maven.build.timestamp} + + + org.springframework.boot + spring-boot-maven-plugin + 2.7.2 + + true + + - + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + pl.project13.maven + git-commit-id-plugin + 3.0.1 + + true + false + yyyyMMdd + + - - ${project.artifactId}-${project.version}-${maven.build.timestamp} - - - org.springframework.boot - spring-boot-maven-plugin - 2.7.2 - - true - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - - pl.project13.maven - git-commit-id-plugin - 3.0.1 - - true - false - yyyyMMdd - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.22.2 - - true - - - - - - - src/main/resources - - - src/main/java - - **/*.xml - - - - + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + true + + + + + + src/main/resources + + + src/main/java + + **/*.xml + + + + diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java index 6a247359..9cb3a1ff 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java @@ -1,12 +1,12 @@ package com.genersoft.iot.vmp.conf.security; import com.genersoft.iot.vmp.conf.UserSetting; -import org.springframework.core.annotation.Order; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; @@ -28,6 +28,7 @@ import java.util.Arrays; /** * 配置Spring Security + * * @author lin */ @Configuration @@ -75,6 +76,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { matchers.add("/js/**"); matchers.add("/api/device/query/snap/**"); matchers.add("/record_proxy/*/**"); + matchers.add("/api/emit"); matchers.addAll(userSetting.getInterfaceAuthenticationExcludes()); // 可以直接访问的静态数据 web.ignoring().antMatchers(matchers.toArray(new String[0])); @@ -83,6 +85,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { /** * 配置认证方式 + * * @param auth * @throws Exception */ @@ -111,7 +114,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { .authorizeRequests() .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() .antMatchers(userSetting.getInterfaceAuthenticationExcludes().toArray(new String[0])).permitAll() - .antMatchers("/api/user/login","/index/hook/**","/zlm_Proxy/FhTuMYqB2HeCuNOb/record/t/1/2023-03-25/16:35:07-16:35:16-9353.mp4").permitAll() + .antMatchers("/api/user/login", "/index/hook/**").permitAll() .anyRequest().authenticated() // 异常处理器 .and() @@ -124,7 +127,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { } - CorsConfigurationSource configurationSource(){ + CorsConfigurationSource configurationSource() { // 配置跨域 CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowedHeaders(Arrays.asList("*")); @@ -135,7 +138,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { corsConfiguration.setExposedHeaders(Arrays.asList(JwtUtils.getHeader())); UrlBasedCorsConfigurationSource url = new UrlBasedCorsConfigurationSource(); - url.registerCorsConfiguration("/**",corsConfiguration); + url.registerCorsConfiguration("/**", corsConfiguration); return url; } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/alarm/AlarmEventListener.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/alarm/AlarmEventListener.java index 9ee64773..aef59076 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/alarm/AlarmEventListener.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/alarm/AlarmEventListener.java @@ -1,55 +1,68 @@ package com.genersoft.iot.vmp.gb28181.event.alarm; -import org.springframework.context.ApplicationListener; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; -import java.io.IOException; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.Map; - +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import java.io.PrintWriter; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** - * @description: 报警事件监听 - * @author: lawrencehj - * @data: 2021-01-20 + * 报警事件监听器. + * + * @author lawrencehj + * @author xiaoQQya + * @since 2021/01/20 */ - @Component public class AlarmEventListener implements ApplicationListener { - private final static Logger logger = LoggerFactory.getLogger(AlarmEventListener.class); + private static final Logger logger = LoggerFactory.getLogger(AlarmEventListener.class); - private static Map sseEmitters = new Hashtable<>(); + private static final Map SSE_CACHE = new ConcurrentHashMap<>(); - public void addSseEmitters(String browserId, SseEmitter sseEmitter) { - sseEmitters.put(browserId, sseEmitter); + public void addSseEmitter(String browserId, PrintWriter writer) { + SSE_CACHE.put(browserId, writer); + logger.info("SSE 在线数量: {}", SSE_CACHE.size()); + } + + public void removeSseEmitter(String browserId, PrintWriter writer) { + SSE_CACHE.remove(browserId, writer); + logger.info("SSE 在线数量: {}", SSE_CACHE.size()); } @Override - public void onApplicationEvent(AlarmEvent event) { + public void onApplicationEvent(@NotNull AlarmEvent event) { if (logger.isDebugEnabled()) { - logger.debug("设备报警事件触发,deviceId:" + event.getAlarmInfo().getDeviceId() + ", " - + event.getAlarmInfo().getAlarmDescription()); + logger.debug("设备报警事件触发, deviceId: {}, {}", event.getAlarmInfo().getDeviceId(), event.getAlarmInfo().getAlarmDescription()); } - String msg = "设备编码: " + event.getAlarmInfo().getDeviceId() + "" - + "
报警描述: " + event.getAlarmInfo().getAlarmDescription() + "" - + "
报警时间: " + event.getAlarmInfo().getAlarmTime() + "" - + "
报警位置: " + event.getAlarmInfo().getLongitude() + "" - + ", " + event.getAlarmInfo().getLatitude() + ""; - for (Iterator> it = sseEmitters.entrySet().iterator(); it.hasNext();) { - Map.Entry emitter = it.next(); - logger.info("推送到SSE连接,浏览器ID: " + emitter.getKey()); + String msg = "设备编号: " + event.getAlarmInfo().getDeviceId() + "" + + "
通道编号: " + event.getAlarmInfo().getChannelId() + "" + + "
报警描述: " + event.getAlarmInfo().getAlarmDescription() + "" + + "
报警时间: " + event.getAlarmInfo().getAlarmTime() + ""; + + for (Iterator> it = SSE_CACHE.entrySet().iterator(); it.hasNext(); ) { + Map.Entry response = it.next(); + logger.info("推送到 SSE 连接, 浏览器 ID: {}", response.getKey()); try { - emitter.getValue().send(msg); - } catch (IOException | IllegalStateException e) { - if (logger.isDebugEnabled()) { - logger.debug("SSE连接已关闭"); + PrintWriter writer = response.getValue(); + + if (writer.checkError()) { + it.remove(); + continue; } - // 移除已关闭的连接 + + String sseMsg = "event:message\n" + + "data:" + msg + "\n" + + "\n"; + writer.write(sseMsg); + writer.flush(); + } catch (Exception e) { it.remove(); } } 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 01bfaa4d..3c296c0c 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java @@ -244,7 +244,6 @@ public class ZLMHttpHookListener { HookResultForOnPublish result = HookResultForOnPublish.SUCCESS(); - result.setEnable_audio(true); taskExecutor.execute(() -> { ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_publish, json); if (subscribe != null) { @@ -262,29 +261,36 @@ public class ZLMHttpHookListener { } else { result.setEnable_mp4(userSetting.isRecordPushLive()); } - // 替换流地址 - if ("rtp".equals(param.getApp()) && !mediaInfo.isRtpEnable()) { - String ssrc = String.format("%010d", Long.parseLong(param.getStream(), 16));; + + // 国标流 + if ("rtp".equals(param.getApp())) { + String ssrc = String.format("%010d", Long.parseLong(param.getStream(), 16)); InviteInfo inviteInfo = inviteStreamService.getInviteInfoBySSRC(ssrc); - if (inviteInfo != null) { + + // 单端口模式下修改流 ID + if (!mediaInfo.isRtpEnable() && inviteInfo != null) { result.setStream_replace(inviteInfo.getStream()); logger.info("[ZLM HOOK]推流鉴权 stream: {} 替换为 {}", param.getStream(), inviteInfo.getStream()); } - } - 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() == InviteSessionType.DOWNLOAD) { - result.setMp4_max_second(10); - result.setEnable_mp4(true); + + // 设置音频信息及录制信息 + List ssrcTransactionForAll = (inviteInfo == null ? null : + sessionManager.getSsrcTransactionForAll(inviteInfo.getDeviceId(), inviteInfo.getChannelId(), null, null)); + 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() == InviteSessionType.DOWNLOAD) { + result.setMp4_max_second(10); + result.setEnable_mp4(true); + } } } + if (mediaInfo.getRecordAssistPort() > 0 && userSetting.getRecordPath() == null) { logger.info("推流时发现尚未设置录像路径,从assist服务中读取"); JSONObject info = assistRESTfulUtils.getInfo(mediaInfo, null); 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 6bbb4bc3..347d0e63 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java @@ -569,7 +569,7 @@ public class MediaServerServiceImpl implements IMediaServerService { Map param = new HashMap<>(); param.put("api.secret",mediaServerItem.getSecret()); // -profile:v Baseline if (mediaServerItem.getRtspPort() != 0) { - param.put("ffmpeg.snap", "%s -rtsp_transport tcp -i %s -y -f mjpeg -t 0.001 %s"); + param.put("ffmpeg.snap", "%s -rtsp_transport tcp -i %s -y -f mjpeg -frames:v 1 %s"); } param.put("hook.enable","1"); param.put("hook.on_flow_report",""); diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/SseController/SseController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/SseController/SseController.java deleted file mode 100755 index b1ad3b9f..00000000 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/SseController/SseController.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.genersoft.iot.vmp.vmanager.gb28181.SseController; - -import com.genersoft.iot.vmp.gb28181.event.alarm.AlarmEventListener; - -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.CrossOrigin; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -/** - * @description: SSE推送 - * @author: lawrencehj - * @data: 2021-01-20 - */ -@Tag(name = "SSE推送") - -@Controller -@RequestMapping("/api") -public class SseController { - @Autowired - AlarmEventListener alarmEventListener; - - @GetMapping("/emit") - public SseEmitter emit(@RequestParam String browserId) { - final SseEmitter sseEmitter = new SseEmitter(0L); - try { - alarmEventListener.addSseEmitters(browserId, sseEmitter); - }catch (Exception e){ - sseEmitter.completeWithError(e); - } - return sseEmitter; - } -} diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/sse/SseController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/sse/SseController.java new file mode 100644 index 00000000..575f22b3 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/sse/SseController.java @@ -0,0 +1,55 @@ +package com.genersoft.iot.vmp.vmanager.gb28181.sse; + +import com.genersoft.iot.vmp.gb28181.event.alarm.AlarmEventListener; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + + +/** + * SSE 推送. + * + * @author lawrencehj + * @author xiaoQQya + * @since 2021/01/20 + */ +@Tag(name = "SSE 推送") +@RestController +@RequestMapping("/api") +public class SseController { + + @Resource + private AlarmEventListener alarmEventListener; + + /** + * SSE 推送. + * + * @param response 响应 + * @param browserId 浏览器ID + * @throws IOException IOEXCEPTION + * @author xiaoQQya + * @since 2023/11/06 + */ + @GetMapping("/emit") + public void emit(HttpServletResponse response, @RequestParam String browserId) throws IOException, InterruptedException { + response.setContentType("text/event-stream"); + response.setCharacterEncoding("utf-8"); + + PrintWriter writer = response.getWriter(); + alarmEventListener.addSseEmitter(browserId, writer); + + while (!writer.checkError()) { + Thread.sleep(1000); + writer.write(":keep alive\n\n"); + writer.flush(); + } + alarmEventListener.removeSseEmitter(browserId, writer); + } +} diff --git a/web_src/src/components/common/jessibuca.vue b/web_src/src/components/common/jessibuca.vue index c02960e6..5b48da4a 100755 --- a/web_src/src/components/common/jessibuca.vue +++ b/web_src/src/components/common/jessibuca.vue @@ -1,6 +1,6 @@