diff --git a/backend/pom.xml b/backend/pom.xml index 519f4acc6d..540665fb9d 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -36,12 +36,12 @@ org.springframework.boot spring-boot-starter-web - + diff --git a/backend/src/main/java/io/dataease/auth/service/impl/ShiroServiceImpl.java b/backend/src/main/java/io/dataease/auth/service/impl/ShiroServiceImpl.java index 3e4f3a1155..1aae87f3de 100644 --- a/backend/src/main/java/io/dataease/auth/service/impl/ShiroServiceImpl.java +++ b/backend/src/main/java/io/dataease/auth/service/impl/ShiroServiceImpl.java @@ -42,6 +42,7 @@ public class ShiroServiceImpl implements ShiroService { filterChainDefinitionMap.put("/index.html", ANON); filterChainDefinitionMap.put("/link.html", ANON); filterChainDefinitionMap.put("/board/**", ANON); + filterChainDefinitionMap.put("/websocket/**", "anon"); // 获取主题信息 filterChainDefinitionMap.put("/plugin/theme/themes", ANON); diff --git a/backend/src/main/java/io/dataease/websocket/ServerEndpointConfigurator.java b/backend/src/main/java/io/dataease/websocket/ServerEndpointConfigurator.java deleted file mode 100644 index 47462b8f44..0000000000 --- a/backend/src/main/java/io/dataease/websocket/ServerEndpointConfigurator.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.dataease.websocket; - - -import javax.websocket.HandshakeResponse; -import javax.websocket.server.HandshakeRequest; -import javax.websocket.server.ServerEndpointConfig; - -public class ServerEndpointConfigurator extends ServerEndpointConfig.Configurator { - @Override - public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { - super.modifyHandshake(sec, request, response); - } -} diff --git a/backend/src/main/java/io/dataease/websocket/WebSocketServer.java b/backend/src/main/java/io/dataease/websocket/WebSocketServer.java deleted file mode 100644 index 84a992302e..0000000000 --- a/backend/src/main/java/io/dataease/websocket/WebSocketServer.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.dataease.websocket; - -import org.springframework.stereotype.Component; - -import javax.websocket.*; -import javax.websocket.server.ServerEndpoint; -import java.io.IOException; - -@ServerEndpoint(value = "/socket", configurator = ServerEndpointConfigurator.class) -@Component -public class WebSocketServer { - @OnOpen - public void onOpen(Session session) throws IOException { - - } - - @OnMessage - public void onMessage(Session session, String message) throws IOException { - - } - - @OnClose - public void onClose(Session session) throws IOException { - - } - - @OnError - public void onError(Session session, Throwable throwable) { - throwable.printStackTrace(); - } -} diff --git a/backend/src/main/java/io/dataease/websocket/aop/WSTrigger.java b/backend/src/main/java/io/dataease/websocket/aop/WSTrigger.java new file mode 100644 index 0000000000..fadc688b1a --- /dev/null +++ b/backend/src/main/java/io/dataease/websocket/aop/WSTrigger.java @@ -0,0 +1,33 @@ +package io.dataease.websocket.aop; + +import io.dataease.websocket.entity.WsMessage; +import io.dataease.websocket.service.WsService; +import org.apache.commons.lang3.ArrayUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Aspect +@Component +public class WSTrigger { + + @Autowired + private WsService wsService; + + @AfterReturning(value = "execution(* io.dataease.service.message.service.strategy.SendStation.sendMsg(..))") + public void after(JoinPoint point) { + Object[] args = point.getArgs(); + Optional.ofNullable(args).ifPresent(objs -> { + if (ArrayUtils.isEmpty(objs)) return; + Object arg = args[0]; + Long userId = (Long) arg; + WsMessage message = new WsMessage(userId, "/web-msg-topic", "refresh"); + wsService.releaseMessage(message); + }); + + } +} diff --git a/backend/src/main/java/io/dataease/websocket/config/WsConfig.java b/backend/src/main/java/io/dataease/websocket/config/WsConfig.java new file mode 100644 index 0000000000..b182a1f25a --- /dev/null +++ b/backend/src/main/java/io/dataease/websocket/config/WsConfig.java @@ -0,0 +1,33 @@ +package io.dataease.websocket.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration; + +@Configuration +@EnableWebSocketMessageBroker +public class WsConfig implements WebSocketMessageBrokerConfigurer { + + + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/websocket").setAllowedOriginPatterns("*").withSockJS(); + } + + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + registry.enableSimpleBroker("/topic", "/user"); + registry.setUserDestinationPrefix("/user"); + } + + @Override + public void configureWebSocketTransport(WebSocketTransportRegistration registry) { + registry.setMessageSizeLimit(8192) //设置消息字节数大小 + .setSendBufferSizeLimit(8192)//设置消息缓存大小 + .setSendTimeLimit(10000); //设置消息发送时间限制毫秒 + } +} diff --git a/backend/src/main/java/io/dataease/websocket/entity/WsMessage.java b/backend/src/main/java/io/dataease/websocket/entity/WsMessage.java new file mode 100644 index 0000000000..6e8b147206 --- /dev/null +++ b/backend/src/main/java/io/dataease/websocket/entity/WsMessage.java @@ -0,0 +1,21 @@ +package io.dataease.websocket.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class WsMessage implements Serializable { + private Long userId; + + private String topic; + + private T data; + + +} + diff --git a/backend/src/main/java/io/dataease/websocket/service/WsService.java b/backend/src/main/java/io/dataease/websocket/service/WsService.java new file mode 100644 index 0000000000..d9e7bac1b8 --- /dev/null +++ b/backend/src/main/java/io/dataease/websocket/service/WsService.java @@ -0,0 +1,39 @@ +package io.dataease.websocket.service; + +import io.dataease.websocket.entity.WsMessage; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Optional; + +@Component +public class WsService { + + + + @Resource + private SimpMessagingTemplate messagingTemplate; + + + + + public void releaseMessage (List wsMessages) { + Optional.ofNullable(wsMessages).ifPresent(messages -> { + messages.forEach(this::releaseMessage); + }); + } + + public void releaseMessage(WsMessage wsMessage){ + if(ObjectUtils.isEmpty(wsMessage) || ObjectUtils.isEmpty(wsMessage.getUserId()) || ObjectUtils.isEmpty(wsMessage.getTopic())) return; + + + messagingTemplate.convertAndSendToUser(String.valueOf(wsMessage.getUserId()), wsMessage.getTopic(),wsMessage.getData()); + + + } + + +} diff --git a/frontend/package.json b/frontend/package.json index 1fbba511db..ceb7d001fa 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -45,6 +45,8 @@ "normalize.css": "7.0.0", "nprogress": "0.2.0", "screenfull": "4.2.0", + "sockjs-client": "^1.6.0", + "stompjs": "^2.3.3", "svg-sprite-loader": "4.1.3", "svgo": "1.2.2", "tinymce": "^5.8.2", diff --git a/frontend/src/components/Notification/index.vue b/frontend/src/components/Notification/index.vue index 8bc2c9bdf0..dd3a32a8b2 100644 --- a/frontend/src/components/Notification/index.vue +++ b/frontend/src/components/Notification/index.vue @@ -1,7 +1,6 @@ @@ -81,7 +73,8 @@ export default { total: 0 }, timer: null, - count: 0 + count: 0, + loading: false } }, computed: { @@ -101,14 +94,23 @@ export default { // 先加载消息类型 loadMsgTypes() this.queryCount() + // this.search() // 每30s定时刷新拉取消息 - this.timer = setInterval(() => { + /* this.timer = setInterval(() => { this.queryCount() - }, 30000) + }, 30000) */ }, mounted() { bus.$on('refresh-top-notification', () => { - this.search() + if (this.visible) this.search() + else this.queryCount() + }) + + bus.$on('web-msg-topic-call', msg => { + console.log('收到websocket消息') + this.count = (this.count || this.paginationConfig.total) + 1 + // this.queryCount() + // this.search() }) }, beforeDestroy() { @@ -195,6 +197,7 @@ export default { }) }, search() { + this.loading = true const param = { status: false, orders: [' create_time desc '] @@ -204,7 +207,9 @@ export default { this.data = response.data.listObject this.paginationConfig.total = response.data.itemCount this.count = this.paginationConfig.total + this.loading = false }).catch(() => { + this.loading = false const token = getToken() if (!token || token === 'null' || token === 'undefined') { this.timer && clearInterval(this.timer) diff --git a/frontend/src/main.js b/frontend/src/main.js index fa7049b608..13e4e465ac 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -25,6 +25,7 @@ import '@/components/canvas/custom-component' // 注册自定义组件 import '@/utils/DateUtil' import draggable from 'vuedraggable' +import deWebsocket from '@/websocket' Vue.config.productionTip = false Vue.use(VueClipboard) Vue.use(widgets) @@ -113,6 +114,7 @@ Vue.prototype.checkPermission = function(pers) { }) return hasPermission } +Vue.use(deWebsocket) new Vue({ router, diff --git a/frontend/src/permission.js b/frontend/src/permission.js index c1d473f23b..e06dc31f53 100644 --- a/frontend/src/permission.js +++ b/frontend/src/permission.js @@ -19,6 +19,8 @@ import { import Layout from '@/layout/index' // import bus from './utils/bus' +import { getSocket } from '@/websocket' + NProgress.configure({ showSpinner: false }) // NProgress Configuration @@ -57,6 +59,8 @@ router.beforeEach(async(to, from, next) => { if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息 // get user info store.dispatch('user/getInfo').then(() => { + const deWebsocket = getSocket() + deWebsocket && deWebsocket.reconnect && deWebsocket.reconnect() store.dispatch('lic/getLicInfo').then(() => { loadMenus(next, to) }).catch(() => { diff --git a/frontend/src/websocket/index.js b/frontend/src/websocket/index.js new file mode 100644 index 0000000000..7787e1ba58 --- /dev/null +++ b/frontend/src/websocket/index.js @@ -0,0 +1,94 @@ +import bus from '@/utils/bus' +import SockJS from 'sockjs-client' +import Stomp from 'stompjs' +import store from '@/store' + +class DeWebsocket { + constructor() { + this.ws_url = '/websocket' + this.client = null + this.channels = [ + { + topic: '/web-msg-topic', + event: 'web-msg-topic-call' + } + ] + this.timer = null + this.initialize() + } + + initialize() { + this.connection() + const _this = this + this.timer = this.isLoginStatu() && setInterval(() => { + this.isLoginStatu() || this.destroy() + try { + _this.client && _this.client.send('heart detection') + } catch (error) { + console.log('Disconnection reconnection...') + _this.connection() + } + }, 5000) + } + + destroy() { + this.timer && clearInterval(this.timer) + this.disconnect() + return true + } + + reconnect() { + this.initialize() + } + + isLoginStatu() { + return store.state && store.state.user && store.state.user.user && store.state.user.user.userId + } + + connection() { + const socket = new SockJS(this.ws_url) + /* const socket = new SockJS('http://localhost:8081' + this.ws_url) */ + if (!this.isLoginStatu()) { + return + } + this.client = Stomp.over(socket) + const heads = { + /* Authorization: '', */ + userId: store.state.user.user.userId + } + + this.client.connect( + heads, + res => { + // 连接成功 订阅所有主题 + this.subscribe() + }, + err => { + // 连接失败 打印错误信息 + console.error(err) + } + ).bind(this) + } + subscribe() { + this.channels.forEach(channel => { + this.client.subscribe('/user/' + store.state.user.user.userId + channel.topic, res => { + res && res.body && bus.$emit(channel.event, res.body) + }) + }) + } + disconnect() { + this.client && this.client.disconnect() + } +} + +const result = new DeWebsocket() +export const getSocket = () => { + return result +} +export default { + install(Vue) { + // 使用$$前缀,避免与Element UI的冲突 + Vue.prototype.$deWebsocket = result + } +} + diff --git a/frontend/vue.config.js b/frontend/vue.config.js index 9f40bcca0e..34b4f12eb4 100644 --- a/frontend/vue.config.js +++ b/frontend/vue.config.js @@ -20,7 +20,7 @@ module.exports = { proxy: { '^(?!/login)': { target: 'http://localhost:8081/', - ws: false + ws: true } }, open: true,