From 05cd60971cff4c0d158028ec9751daf6a7da9bf8 Mon Sep 17 00:00:00 2001 From: xjs <1294405880@qq.com> Date: Fri, 14 Jan 2022 22:48:40 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E5=89=8D=E7=AB=AF=E5=BC=95=E5=85=A5w?= =?UTF-8?q?ebsocket=202=E3=80=81=E5=AE=9E=E7=8E=B0=E9=A2=84=E8=AD=A6?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E9=80=9A=E8=BF=87websocket=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E5=9C=A8=E5=8F=B3=E4=B8=8A=E8=A7=92=203=E3=80=81=E4=BD=BF?= =?UTF-8?q?=E7=94=A8redis=E8=AE=B0=E5=BD=95websocket=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF=E7=94=A8=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/redis/service/RedisService.java | 19 ++ .../annotation/EnableCustomConfig.java | 2 + ruoyi-ui/.env.development | 2 +- ruoyi-ui/src/layout/components/Navbar.vue | 76 +++++- ruoyi-ui/src/layout/index.vue | 235 ++++++++++-------- ruoyi-ui/src/store/getters.js | 37 +-- ruoyi-ui/src/store/modules/app.js | 120 ++++----- ruoyi-ui/src/utils/socket-server.js | 134 ++++++++++ .../main/java/com/xjs/consts/RedisConst.java | 5 + .../xjs/controller/ApiWarningController.java | 36 ++- .../java/com/xjs/server/WebSocketServer.java | 43 +++- 11 files changed, 510 insertions(+), 199 deletions(-) create mode 100644 ruoyi-ui/src/utils/socket-server.js diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java b/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java index 6ae71fec..70ee1612 100644 --- a/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java @@ -164,6 +164,25 @@ public class RedisService { return redisTemplate.opsForSet().members(key); } + + /** + * set中移除指定元素 + * @param key redis键 + * @param value set值 + * @param obj + * @return 删除数量 + */ + public Long removeSet(String key, T value) { + Long size = null; + try { + size = redisTemplate.opsForSet().remove(key, value); + } catch (Exception e) { + return size; + } + return size; + } + + /** * 缓存Map * diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/annotation/EnableCustomConfig.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/annotation/EnableCustomConfig.java index be7f7f6d..b58b099c 100644 --- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/annotation/EnableCustomConfig.java +++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/annotation/EnableCustomConfig.java @@ -7,6 +7,7 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.Import; import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.transaction.annotation.EnableTransactionManagement; import java.lang.annotation.*; @@ -24,6 +25,7 @@ import java.lang.annotation.*; @Import({ ApplicationConfig.class, FeignAutoConfiguration.class }) //自定义bean扫描,添加xjs路径下的bean @ComponentScan(basePackages = {"com.ruoyi","com.xjs"}) +@EnableTransactionManagement public @interface EnableCustomConfig { diff --git a/ruoyi-ui/.env.development b/ruoyi-ui/.env.development index 1465c903..03d7279c 100644 --- a/ruoyi-ui/.env.development +++ b/ruoyi-ui/.env.development @@ -4,7 +4,7 @@ VUE_APP_TITLE = 管理平台 # 开发环境配置 ENV = 'development' -# 若依管理系统/开发环境 +# 管理系统/开发环境 VUE_APP_BASE_API = '/dev-api' # 路由懒加载 diff --git a/ruoyi-ui/src/layout/components/Navbar.vue b/ruoyi-ui/src/layout/components/Navbar.vue index 93ebd63c..fe3b4651 100644 --- a/ruoyi-ui/src/layout/components/Navbar.vue +++ b/ruoyi-ui/src/layout/components/Navbar.vue @@ -1,28 +1,34 @@ + + + + + diff --git a/ruoyi-ui/src/store/getters.js b/ruoyi-ui/src/store/getters.js index 8d723813..d31a7454 100644 --- a/ruoyi-ui/src/store/getters.js +++ b/ruoyi-ui/src/store/getters.js @@ -1,18 +1,19 @@ -const getters = { - sidebar: state => state.app.sidebar, - size: state => state.app.size, - device: state => state.app.device, - visitedViews: state => state.tagsView.visitedViews, - cachedViews: state => state.tagsView.cachedViews, - token: state => state.user.token, - avatar: state => state.user.avatar, - name: state => state.user.name, - introduction: state => state.user.introduction, - roles: state => state.user.roles, - permissions: state => state.user.permissions, - permission_routes: state => state.permission.routes, - topbarRouters:state => state.permission.topbarRouters, - defaultRoutes:state => state.permission.defaultRoutes, - sidebarRouters:state => state.permission.sidebarRouters, -} -export default getters +const getters = { + sidebar: state => state.app.sidebar, + size: state => state.app.size, + device: state => state.app.device, + visitedViews: state => state.tagsView.visitedViews, + cachedViews: state => state.tagsView.cachedViews, + token: state => state.user.token, + avatar: state => state.user.avatar, + name: state => state.user.name, + introduction: state => state.user.introduction, + roles: state => state.user.roles, + permissions: state => state.user.permissions, + permission_routes: state => state.permission.routes, + topbarRouters:state => state.permission.topbarRouters, + defaultRoutes:state => state.permission.defaultRoutes, + sidebarRouters:state => state.permission.sidebarRouters, + $socket: state => state.app.$socket, +} +export default getters diff --git a/ruoyi-ui/src/store/modules/app.js b/ruoyi-ui/src/store/modules/app.js index c8d8ee91..a38527dd 100644 --- a/ruoyi-ui/src/store/modules/app.js +++ b/ruoyi-ui/src/store/modules/app.js @@ -1,56 +1,64 @@ -import Cookies from 'js-cookie' - -const state = { - sidebar: { - opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, - withoutAnimation: false - }, - device: 'desktop', - size: Cookies.get('size') || 'medium' -} - -const mutations = { - TOGGLE_SIDEBAR: state => { - state.sidebar.opened = !state.sidebar.opened - state.sidebar.withoutAnimation = false - if (state.sidebar.opened) { - Cookies.set('sidebarStatus', 1) - } else { - Cookies.set('sidebarStatus', 0) - } - }, - CLOSE_SIDEBAR: (state, withoutAnimation) => { - Cookies.set('sidebarStatus', 0) - state.sidebar.opened = false - state.sidebar.withoutAnimation = withoutAnimation - }, - TOGGLE_DEVICE: (state, device) => { - state.device = device - }, - SET_SIZE: (state, size) => { - state.size = size - Cookies.set('size', size) - } -} - -const actions = { - toggleSideBar({ commit }) { - commit('TOGGLE_SIDEBAR') - }, - closeSideBar({ commit }, { withoutAnimation }) { - commit('CLOSE_SIDEBAR', withoutAnimation) - }, - toggleDevice({ commit }, device) { - commit('TOGGLE_DEVICE', device) - }, - setSize({ commit }, size) { - commit('SET_SIZE', size) - } -} - -export default { - namespaced: true, - state, - mutations, - actions -} +import Cookies from 'js-cookie' + +const state = { + sidebar: { + opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, + withoutAnimation: false + }, + device: 'desktop', + size: Cookies.get('size') || 'medium', + // websocket实例 + $socket: null +} + +const mutations = { + TOGGLE_SIDEBAR: state => { + state.sidebar.opened = !state.sidebar.opened + state.sidebar.withoutAnimation = false + if (state.sidebar.opened) { + Cookies.set('sidebarStatus', 1) + } else { + Cookies.set('sidebarStatus', 0) + } + }, + CLOSE_SIDEBAR: (state, withoutAnimation) => { + Cookies.set('sidebarStatus', 0) + state.sidebar.opened = false + state.sidebar.withoutAnimation = withoutAnimation + }, + TOGGLE_DEVICE: (state, device) => { + state.device = device + }, + SET_SIZE: (state, size) => { + state.size = size + Cookies.set('size', size) + }, + SET_$SOCKET: (state, socket) => { + state.$socket = socket + } +} + +const actions = { + toggleSideBar({commit}) { + commit('TOGGLE_SIDEBAR') + }, + closeSideBar({commit}, {withoutAnimation}) { + commit('CLOSE_SIDEBAR', withoutAnimation) + }, + toggleDevice({commit}, device) { + commit('TOGGLE_DEVICE', device) + }, + setSize({commit}, size) { + commit('SET_SIZE', size) + }, + set$Socket({commit}, socket) { + commit('SET_$SOCKET', socket) + } +} + +export default { + namespaced: true, + state, + mutations, + actions +} diff --git a/ruoyi-ui/src/utils/socket-server.js b/ruoyi-ui/src/utils/socket-server.js new file mode 100644 index 00000000..2af4148a --- /dev/null +++ b/ruoyi-ui/src/utils/socket-server.js @@ -0,0 +1,134 @@ +import { Message } from "element-ui"; +import store from '@/store' +// let wsUrl = 'ws://127.0.0.1:9002/warning/api'; + +export default class SocketService { + /** + * 单例 + */ + static instance = null; + static get Instance() { + if (!this.instance) { + this.instance = new SocketService(); + } + return this.instance; + } + + // 和服务端连接的socket对象 + ws = null; + + // 存储回调函数 + callBackMapping = {}; + + // 标识是否连接成功 + connected = false; + + // 记录重试的次数 + sendRetryCount = 0; + + // 重新连接尝试的次数 + connectRetryCount = 0; + + // 是否手动关闭websocket连接 + isManualClose = false + + // 定义连接服务器的方法 + connect() { + // 连接服务器 + if (!window.WebSocket) { + return console.log("您的浏览器不支持WebSocket"); + } + //网关转发 + let wsUrl = 'ws://localhost:8080/warning/warning/api'; + wsUrl += `/${store.getters.name}` + this.ws = new WebSocket(wsUrl) + + // 连接成功的事件 + this.ws.onopen = () => { + console.log("连接服务端成功了"); + this.connected = true; + // 重置重新连接的次数 + this.connectRetryCount = 0; + }; + // 1.连接服务端失败 + // 2.当连接成功之后, 服务器关闭的情况 + this.ws.onclose = (event) => { + // e.code === 1000 表示正常关闭。 无论为何目的而创建, 该链接都已成功完成任务。 + // e.code !== 1000 表示非正常关闭。 + console.log("onclose event: ", event); + this.connected = false; + if (event && event.code !== 1000) { + console.log("连接服务端失败"); + // 如果不是手动关闭,这里的重连会执行;如果调用了手动关闭函数,这里重连不会执行 + this.connectRetryCount++; + setTimeout(() => { + this.connect(); + }, 500 * this.connectRetryCount); + } + }; + // 得到服务端发送过来的数据 + this.ws.onmessage = (msg) => { + console.log("从服务端获取到了数据"); + // 真正服务端发送过来的原始数据时在msg中的data字段 + const recvData = JSON.parse(msg.data); + console.log(recvData) + const socketType = recvData.socketType; + if (this.callBackMapping[socketType]){ + this.callBackMapping[socketType].call(this, recvData); + } + }; + } + + // 回调函数的注册 + registerCallBack(socketType, callBack) { + this.callBackMapping[socketType] = callBack; + } + + // 取消某一个回调函数 + unRegisterCallBack(socketType) { + this.callBackMapping[socketType] = null; + } + + + // 发送数据的方法 + send(data) { + // 判断此时此刻有没有连接成功 + if (this.connected) { + this.sendRetryCount = 0; + this.ws.send(JSON.stringify(data)); + console.log('xxxxxxxxxxxx') + } else if (!this.isManualClose) { + console.log('=============') + this.sendRetryCount++; + setTimeout(() => { + this.send(data); + }, this.sendRetryCount * 500); + } + } + + // 手动关闭websocket (这里手动关闭会执行onclose事件) + closeWebsocket() { + if (this.ws) { + try { + this.ws.close(); // 关闭websocket + this.ws = null + this.callBackMapping = {} + this.connected = false + this.sendRetryCount = 0 + this.connectRetryCount = 0 + this.isManualClose = true + this.instance = null + SocketService.instance = null + } catch (error) { + console.log(error) + } + } + } +} + +const MessageEvent = { // onmessage回调函数的event + data: { // websocket通讯接收到的数据 + socketType: '', // websocket双方通讯的名称 + data: {}, // 返回的真实数据 + } +} diff --git a/xjs-business/xjs-business-common/src/main/java/com/xjs/consts/RedisConst.java b/xjs-business/xjs-business-common/src/main/java/com/xjs/consts/RedisConst.java index 2ce15935..b59d0ea1 100644 --- a/xjs-business/xjs-business-common/src/main/java/com/xjs/consts/RedisConst.java +++ b/xjs-business/xjs-business-common/src/main/java/com/xjs/consts/RedisConst.java @@ -24,6 +24,11 @@ public class RedisConst { */ public static final String HOT = "hot"; + /** + * websocket常量key + */ + public static final String WEBSOCKET= "WEBSOCKET"; + //-------------------有效时间----------------------- public static final Integer TRAN_DICT_EXPIRE = 7; //天 diff --git a/xjs-business/xjs-business-warning/src/main/java/com/xjs/controller/ApiWarningController.java b/xjs-business/xjs-business-warning/src/main/java/com/xjs/controller/ApiWarningController.java index 504c2f87..6c4ba9ab 100644 --- a/xjs-business/xjs-business-warning/src/main/java/com/xjs/controller/ApiWarningController.java +++ b/xjs-business/xjs-business-warning/src/main/java/com/xjs/controller/ApiWarningController.java @@ -1,5 +1,6 @@ package com.xjs.controller; +import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.utils.poi.ExcelUtil; @@ -8,16 +9,23 @@ import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.common.core.web.page.TableDataInfo; import com.ruoyi.common.log.annotation.Log; import com.ruoyi.common.log.enums.BusinessType; +import com.ruoyi.common.redis.service.RedisService; import com.ruoyi.common.security.annotation.RequiresPermissions; import com.xjs.domain.ApiRecord; import com.xjs.domain.ApiWarning; +import com.xjs.server.WebSocketServer; import com.xjs.service.ApiWarningService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; import java.util.List; import java.util.Objects; +import java.util.Set; + +import static com.xjs.consts.RedisConst.WEBSOCKET; /** * @author xiejs @@ -30,6 +38,8 @@ public class ApiWarningController extends BaseController { @Autowired private ApiWarningService apiWarningService; + @Autowired + private RedisService redisService; /** * 远程保存 apiRecord @@ -66,17 +76,41 @@ public class ApiWarningController extends BaseController { } /** - * 远程保存api预警信息 + * 远程保存api预警信息并websocket推送 * * @param apiWarning 预警实体类 * @return R */ @PostMapping("saveApiwarningForRPC") + @Transactional public R saveApiWarningForRPC(@RequestBody ApiWarning apiWarning) { boolean save = apiWarningService.save(apiWarning); + + this.websocketPush(apiWarning); + return save ? R.ok() : R.fail(); } + /** + * websocket推送 + */ + private void websocketPush(ApiWarning apiWarning) { + long count = apiWarningService.count(); + Set cacheSet = redisService.getCacheSet(WEBSOCKET); + JSONObject jsonData =new JSONObject(); + JSONObject jsonObject = (JSONObject) JSONObject.toJSON(apiWarning); + jsonData.put("count", count); + jsonData.put("data", jsonObject.toJSONString()); + jsonData.put("socketType", "apiWarning"); + for (String userId : cacheSet) { + try { + WebSocketServer.sendInfo(jsonData.toString(),userId); + } catch (IOException e) { + logger.error(e.getMessage()); + } + } + } + /** * 查询api预警列表 diff --git a/xjs-business/xjs-business-warning/src/main/java/com/xjs/server/WebSocketServer.java b/xjs-business/xjs-business-warning/src/main/java/com/xjs/server/WebSocketServer.java index ea5c1ccc..3506644c 100644 --- a/xjs-business/xjs-business-warning/src/main/java/com/xjs/server/WebSocketServer.java +++ b/xjs-business/xjs-business-warning/src/main/java/com/xjs/server/WebSocketServer.java @@ -1,15 +1,23 @@ package com.xjs.server; +import com.alibaba.fastjson.JSONObject; +import com.ruoyi.common.redis.service.RedisService; +import com.xjs.service.ApiWarningService; import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import static com.xjs.consts.RedisConst.WEBSOCKET; + /** * api预警websocket * @@ -17,9 +25,25 @@ import java.util.concurrent.ConcurrentHashMap; * @since 2022-01-13 */ @Log4j2 -@ServerEndpoint("warning/api/{userId}") +@ServerEndpoint("/warning/api/{userId}") @Component public class WebSocketServer { + /** + * 之所有需要set注入,因为WebSocketServer是静态类,需要注入属性就需要用set注入 + */ + private static RedisService redisService; + @Autowired + public void setRedisService(RedisService redisService) { + WebSocketServer.redisService = redisService; + } + + private static ApiWarningService apiWarningService; + @Autowired + public void setApiWarningService(ApiWarningService apiWarningService) { + WebSocketServer.apiWarningService = apiWarningService; + } + + /** * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 */ @@ -56,12 +80,21 @@ public class WebSocketServer { } log.info("用户连接:" + userId + ",当前在线人数为:" + getOnlineCount()); + Set set = new HashSet<>(); + set.add(userId); + redisService.setCacheSet(WEBSOCKET, set); + long count = apiWarningService.count(); + JSONObject jsonData =new JSONObject(); + jsonData.put("count", count); + jsonData.put("socketType", "apiWarning"); + jsonData.put("data", "{}"); try { - sendMessage("连接成功"); + sendMessage(jsonData.toJSONString()); } catch (IOException e) { - log.error("用户:" + userId + ",网络异常!!!!!!"); + e.printStackTrace(); } + } /** @@ -73,6 +106,9 @@ public class WebSocketServer { webSocketMap.remove(userId); //从set中删除 subOnlineCount(); + + //退出移出用户 + redisService.removeSet(WEBSOCKET, this.userId); } log.info("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount()); } @@ -107,6 +143,7 @@ public class WebSocketServer { @OnError public void onError(Session session, Throwable error) { log.error("用户错误:" + this.userId + ",原因:" + error.getMessage()); + redisService.removeSet(WEBSOCKET, this.userId); error.printStackTrace(); }