src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/main/java/com/genersoft/iot/vmp/gb28181/event/alarm/AlarmEventListener.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/SseController/SseController.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/sse/SseController.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
web_src/src/layout/UiHeader.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
web_src/src/main.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
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 @@ /** * 配置Spring Security * * @author lin */ @Configuration @@ -75,6 +76,7 @@ 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 @@ /** * 配置认证方式 * * @param auth * @throws Exception */ @@ -111,7 +114,7 @@ .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 @@ } CorsConfigurationSource configurationSource(){ CorsConfigurationSource configurationSource() { // 配置跨域 CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowedHeaders(Arrays.asList("*")); @@ -135,7 +138,7 @@ corsConfiguration.setExposedHeaders(Arrays.asList(JwtUtils.getHeader())); UrlBasedCorsConfigurationSource url = new UrlBasedCorsConfigurationSource(); url.registerCorsConfiguration("/**",corsConfiguration); url.registerCorsConfiguration("/**", corsConfiguration); return url; } 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 <a href="mailto:xiaoQQya@126.com">xiaoQQya</a> * @since 2021/01/20 */ @Component public class AlarmEventListener implements ApplicationListener<AlarmEvent> { private final static Logger logger = LoggerFactory.getLogger(AlarmEventListener.class); private static final Logger logger = LoggerFactory.getLogger(AlarmEventListener.class); private static Map<String, SseEmitter> sseEmitters = new Hashtable<>(); private static final Map<String, PrintWriter> 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 = "<strong>设备编码:</strong> <i>" + event.getAlarmInfo().getDeviceId() + "</i>" + "<br><strong>报警描述:</strong> <i>" + event.getAlarmInfo().getAlarmDescription() + "</i>" + "<br><strong>报警时间:</strong> <i>" + event.getAlarmInfo().getAlarmTime() + "</i>" + "<br><strong>报警位置:</strong> <i>" + event.getAlarmInfo().getLongitude() + "</i>" + ", <i>" + event.getAlarmInfo().getLatitude() + "</i>"; for (Iterator<Map.Entry<String, SseEmitter>> it = sseEmitters.entrySet().iterator(); it.hasNext();) { Map.Entry<String, SseEmitter> emitter = it.next(); logger.info("推送到SSE连接,浏览器ID: " + emitter.getKey()); String msg = "<strong>设备编号:</strong> <i>" + event.getAlarmInfo().getDeviceId() + "</i>" + "<br><strong>通道编号:</strong> <i>" + event.getAlarmInfo().getChannelId() + "</i>" + "<br><strong>报警描述:</strong> <i>" + event.getAlarmInfo().getAlarmDescription() + "</i>" + "<br><strong>报警时间:</strong> <i>" + event.getAlarmInfo().getAlarmTime() + "</i>"; for (Iterator<Map.Entry<String, PrintWriter>> it = SSE_CACHE.entrySet().iterator(); it.hasNext(); ) { Map.Entry<String, PrintWriter> 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(); } } src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/SseController/SseController.java
File was deleted src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/sse/SseController.java
New file @@ -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 <a href="mailto:xiaoQQya@126.com">xiaoQQya</a> * @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 <a href="mailto:xiaoQQya@126.com">xiaoQQya</a> * @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); } } web_src/src/layout/UiHeader.vue
@@ -37,7 +37,6 @@ </template> <script> import changePasswordDialog from '../components/dialog/changePassword.vue' import userService from '../components/service/UserService' import {Notification} from 'element-ui'; @@ -55,18 +54,19 @@ }; }, created() { console.log(4444) console.log(JSON.stringify(userService.getUser())) if (this.$route.path.startsWith("/channelList")) { this.activeIndex = "/deviceList" } }, mounted() { window.addEventListener('beforeunload', e => this.beforeunloadHandler(e)) // window.addEventListener('unload', e => this.unloadHandler(e)) this.alarmNotify = this.getAlarmSwitchStatus() === "true"; this.sseControl(); // TODO: 此处延迟连接 sse, 避免 sse 连接时 browserId 还未生成, 后续待优化 setTimeout(() => { this.sseControl() }, 3000); }, methods: { loginout() { @@ -107,10 +107,12 @@ this.sseSource = new EventSource('/api/emit?browserId=' + this.$browserId); this.sseSource.addEventListener('message', function (evt) { that.$notify({ title: '收到报警信息', title: '报警信息', dangerouslyUseHTMLString: true, message: evt.data, type: 'warning' type: 'warning', position: 'bottom-right', duration: 3000 }); console.log("收到信息:" + evt.data); }); web_src/src/main.js
@@ -1,21 +1,19 @@ import Vue from 'vue'; import App from './App.vue'; Vue.config.productionTip = false; import ElementUI from 'element-ui'; import ElementUI, {Notification} from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; import router from './router/index.js'; import axios from 'axios'; import VueCookies from 'vue-cookies'; import echarts from 'echarts'; import VCharts from 'v-charts'; import VueClipboard from 'vue-clipboard2'; import {Notification} from 'element-ui'; import Fingerprint2 from 'fingerprintjs2'; import VueClipboards from 'vue-clipboards'; import Contextmenu from "vue-contextmenujs" import userService from "./components/service/UserService" Vue.config.productionTip = false; // 生成唯一ID @@ -29,10 +27,9 @@ //console.log(values) //使用的浏览器信息npm // 生成最终id let port = window.location.port; console.log(port); const fingerPrint = Fingerprint2.x64hash128(values.join(port), 31) Vue.prototype.$browserId = fingerPrint; console.log("唯一标识码:" + fingerPrint); console.log("浏览器 ID: " + fingerPrint); }); Vue.use(VueClipboard); @@ -75,7 +72,7 @@ ); Vue.prototype.$axios = axios; Vue.prototype.$cookies.config(60*30); Vue.prototype.$cookies.config(60 * 30); new Vue({ router: router,