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;
|
package com.genersoft.iot.vmp.conf.security;
|
||||||
|
|
||||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||||
import org.springframework.core.annotation.Order;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
|
@ -28,6 +28,7 @@ import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 配置Spring Security
|
* 配置Spring Security
|
||||||
|
*
|
||||||
* @author lin
|
* @author lin
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
|
@ -75,6 +76,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
matchers.add("/js/**");
|
matchers.add("/js/**");
|
||||||
matchers.add("/api/device/query/snap/**");
|
matchers.add("/api/device/query/snap/**");
|
||||||
matchers.add("/record_proxy/*/**");
|
matchers.add("/record_proxy/*/**");
|
||||||
|
matchers.add("/api/emit");
|
||||||
matchers.addAll(userSetting.getInterfaceAuthenticationExcludes());
|
matchers.addAll(userSetting.getInterfaceAuthenticationExcludes());
|
||||||
// 可以直接访问的静态数据
|
// 可以直接访问的静态数据
|
||||||
web.ignoring().antMatchers(matchers.toArray(new String[0]));
|
web.ignoring().antMatchers(matchers.toArray(new String[0]));
|
||||||
|
@ -83,6 +85,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 配置认证方式
|
* 配置认证方式
|
||||||
|
*
|
||||||
* @param auth
|
* @param auth
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
|
@ -111,7 +114,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
|
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
|
||||||
.antMatchers(userSetting.getInterfaceAuthenticationExcludes().toArray(new String[0])).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()
|
.anyRequest().authenticated()
|
||||||
// 异常处理器
|
// 异常处理器
|
||||||
.and()
|
.and()
|
||||||
|
|
|
@ -1,55 +1,68 @@
|
||||||
package com.genersoft.iot.vmp.gb28181.event.alarm;
|
package com.genersoft.iot.vmp.gb28181.event.alarm;
|
||||||
|
|
||||||
import org.springframework.context.ApplicationListener;
|
import org.jetbrains.annotations.NotNull;
|
||||||
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.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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
|
@Component
|
||||||
public class AlarmEventListener implements ApplicationListener<AlarmEvent> {
|
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) {
|
public void addSseEmitter(String browserId, PrintWriter writer) {
|
||||||
sseEmitters.put(browserId, sseEmitter);
|
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
|
@Override
|
||||||
public void onApplicationEvent(AlarmEvent event) {
|
public void onApplicationEvent(@NotNull AlarmEvent event) {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("设备报警事件触发,deviceId:" + event.getAlarmInfo().getDeviceId() + ", "
|
logger.debug("设备报警事件触发, deviceId: {}, {}", event.getAlarmInfo().getDeviceId(), event.getAlarmInfo().getAlarmDescription());
|
||||||
+ 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();) {
|
String msg = "<strong>设备编号:</strong> <i>" + event.getAlarmInfo().getDeviceId() + "</i>"
|
||||||
Map.Entry<String, SseEmitter> emitter = it.next();
|
+ "<br><strong>通道编号:</strong> <i>" + event.getAlarmInfo().getChannelId() + "</i>"
|
||||||
logger.info("推送到SSE连接,浏览器ID: " + emitter.getKey());
|
+ "<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 {
|
try {
|
||||||
emitter.getValue().send(msg);
|
PrintWriter writer = response.getValue();
|
||||||
} catch (IOException | IllegalStateException e) {
|
|
||||||
if (logger.isDebugEnabled()) {
|
if (writer.checkError()) {
|
||||||
logger.debug("SSE连接已关闭");
|
it.remove();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
// 移除已关闭的连接
|
|
||||||
|
String sseMsg = "event:message\n" +
|
||||||
|
"data:" + msg + "\n" +
|
||||||
|
"\n";
|
||||||
|
writer.write(sseMsg);
|
||||||
|
writer.flush();
|
||||||
|
} catch (Exception e) {
|
||||||
it.remove();
|
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>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import changePasswordDialog from '../components/dialog/changePassword.vue'
|
import changePasswordDialog from '../components/dialog/changePassword.vue'
|
||||||
import userService from '../components/service/UserService'
|
import userService from '../components/service/UserService'
|
||||||
import {Notification} from 'element-ui';
|
import {Notification} from 'element-ui';
|
||||||
|
@ -55,18 +54,19 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
console.log(4444)
|
|
||||||
console.log(JSON.stringify(userService.getUser()))
|
console.log(JSON.stringify(userService.getUser()))
|
||||||
if (this.$route.path.startsWith("/channelList")) {
|
if (this.$route.path.startsWith("/channelList")) {
|
||||||
this.activeIndex = "/deviceList"
|
this.activeIndex = "/deviceList"
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
window.addEventListener('beforeunload', e => this.beforeunloadHandler(e))
|
window.addEventListener('beforeunload', e => this.beforeunloadHandler(e))
|
||||||
// window.addEventListener('unload', e => this.unloadHandler(e))
|
|
||||||
this.alarmNotify = this.getAlarmSwitchStatus() === "true";
|
this.alarmNotify = this.getAlarmSwitchStatus() === "true";
|
||||||
this.sseControl();
|
|
||||||
|
// TODO: 此处延迟连接 sse, 避免 sse 连接时 browserId 还未生成, 后续待优化
|
||||||
|
setTimeout(() => {
|
||||||
|
this.sseControl()
|
||||||
|
}, 3000);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loginout() {
|
loginout() {
|
||||||
|
@ -107,10 +107,12 @@ export default {
|
||||||
this.sseSource = new EventSource('/api/emit?browserId=' + this.$browserId);
|
this.sseSource = new EventSource('/api/emit?browserId=' + this.$browserId);
|
||||||
this.sseSource.addEventListener('message', function (evt) {
|
this.sseSource.addEventListener('message', function (evt) {
|
||||||
that.$notify({
|
that.$notify({
|
||||||
title: '收到报警信息',
|
title: '报警信息',
|
||||||
dangerouslyUseHTMLString: true,
|
dangerouslyUseHTMLString: true,
|
||||||
message: evt.data,
|
message: evt.data,
|
||||||
type: 'warning'
|
type: 'warning',
|
||||||
|
position: 'bottom-right',
|
||||||
|
duration: 3000
|
||||||
});
|
});
|
||||||
console.log("收到信息:" + evt.data);
|
console.log("收到信息:" + evt.data);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,22 +1,20 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
|
import ElementUI, {Notification} from 'element-ui';
|
||||||
Vue.config.productionTip = false;
|
|
||||||
import ElementUI from 'element-ui';
|
|
||||||
import 'element-ui/lib/theme-chalk/index.css';
|
import 'element-ui/lib/theme-chalk/index.css';
|
||||||
import router from './router/index.js';
|
import router from './router/index.js';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import VueCookies from 'vue-cookies';
|
import VueCookies from 'vue-cookies';
|
||||||
import echarts from 'echarts';
|
|
||||||
import VCharts from 'v-charts';
|
import VCharts from 'v-charts';
|
||||||
|
|
||||||
import VueClipboard from 'vue-clipboard2';
|
import VueClipboard from 'vue-clipboard2';
|
||||||
import {Notification} from 'element-ui';
|
|
||||||
import Fingerprint2 from 'fingerprintjs2';
|
import Fingerprint2 from 'fingerprintjs2';
|
||||||
import VueClipboards from 'vue-clipboards';
|
import VueClipboards from 'vue-clipboards';
|
||||||
import Contextmenu from "vue-contextmenujs"
|
import Contextmenu from "vue-contextmenujs"
|
||||||
import userService from "./components/service/UserService"
|
import userService from "./components/service/UserService"
|
||||||
|
|
||||||
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
|
|
||||||
// 生成唯一ID
|
// 生成唯一ID
|
||||||
Fingerprint2.get(function (components) {
|
Fingerprint2.get(function (components) {
|
||||||
|
@ -29,10 +27,9 @@ Fingerprint2.get(function (components) {
|
||||||
//console.log(values) //使用的浏览器信息npm
|
//console.log(values) //使用的浏览器信息npm
|
||||||
// 生成最终id
|
// 生成最终id
|
||||||
let port = window.location.port;
|
let port = window.location.port;
|
||||||
console.log(port);
|
|
||||||
const fingerPrint = Fingerprint2.x64hash128(values.join(port), 31)
|
const fingerPrint = Fingerprint2.x64hash128(values.join(port), 31)
|
||||||
Vue.prototype.$browserId = fingerPrint;
|
Vue.prototype.$browserId = fingerPrint;
|
||||||
console.log("唯一标识码:" + fingerPrint);
|
console.log("浏览器 ID: " + fingerPrint);
|
||||||
});
|
});
|
||||||
|
|
||||||
Vue.use(VueClipboard);
|
Vue.use(VueClipboard);
|
||||||
|
|
Loading…
Reference in New Issue