fix(报警推送): 修复报警推送功能无效的问题
该问题原因为 com.genersoft.iot.vmp.conf.GlobalResponseAdvice 类改变了 sse 响应体的数据结构,导致前端无法正确解析 sse 数据,调试后未发现 GlobalResponseAdvice 如何修改的 sse 数据结构,故根据 sse 消息体结构自定义实现了 sse 连接pull/1137/head
parent
8d7d751d44
commit
f78657473e
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 @@ export default {
|
|||
};
|
||||
},
|
||||
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 @@ export default {
|
|||
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);
|
||||
});
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
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
|
||||
Fingerprint2.get(function (components) {
|
||||
|
@ -29,10 +27,9 @@ Fingerprint2.get(function (components) {
|
|||
//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 @@ axios.interceptors.request.use(
|
|||
);
|
||||
|
||||
Vue.prototype.$axios = axios;
|
||||
Vue.prototype.$cookies.config(60*30);
|
||||
Vue.prototype.$cookies.config(60 * 30);
|
||||
|
||||
new Vue({
|
||||
router: router,
|
||||
|
|
Loading…
Reference in New Issue