• ruoyi-nbcio增加websocket与测试页面


       更多ruoyi-nbcio功能请看演示系统

       gitee源代码地址

       前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio 

     

         为了后面流程发起等消息推送,所以需要集成websocket。

         1、后端增加websoket支持

           首先在framework模块里的pom.xml增加websocket

    1. org.springframework.boot
    2. spring-boot-starter-websocket

         2、增加websocket配置 

    1. package com.ruoyi.framework.config;
    2. import org.springframework.context.annotation.Bean;
    3. import org.springframework.context.annotation.Configuration;
    4. import org.springframework.web.socket.server.standard.ServerEndpointExporter;
    5. /**
    6. * 开启WebSocket支持
    7. */
    8. @Configuration
    9. public class WebSocketConfig {
    10. @Bean
    11. public ServerEndpointExporter serverEndpointExporter() {
    12. return new ServerEndpointExporter();
    13. }
    14. }

    3、增加websocket服务,当然这部分后面还要修改

    1. package com.ruoyi.framework.websocket;
    2. import cn.hutool.json.JSONUtil;
    3. import com.ruoyi.common.core.domain.BaseProtocol;
    4. import org.slf4j.Logger;
    5. import org.slf4j.LoggerFactory;
    6. import org.springframework.stereotype.Component;
    7. import javax.websocket.*;
    8. import javax.websocket.server.PathParam;
    9. import javax.websocket.server.ServerEndpoint;
    10. import java.io.IOException;
    11. import java.util.concurrent.CopyOnWriteArraySet;
    12. import java.util.concurrent.atomic.AtomicInteger;
    13. // @ServerEndpoint 声明并创建了webSocket端点, 并且指明了请求路径
    14. // id 为客户端请求时携带的参数, 用于服务端区分客户端使用
    15. /**
    16. * @ServerEndpoint 声明并创建了websocket端点, 并且指明了请求路径
    17. * uid 为客户端请求时携带的用户id, 用于区分发给哪个用户的消息
    18. * @author nbacheng
    19. * @date 2023-09-20
    20. */
    21. @ServerEndpoint("/websocket/{uid}")
    22. @Component
    23. public class WebSocketServer {
    24. // 日志对象
    25. private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);
    26. // 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    27. //千万不要用++
    28. private static AtomicInteger onlineCount = new AtomicInteger(0);
    29. // concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    30. private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet<>();
    31. // private static ConcurrentHashMap websocketList = new ConcurrentHashMap<>();
    32. // 与某个客户端的连接会话,需要通过它来给客户端发送数据
    33. private Session session;
    34. // 接收uid
    35. private String uid = "";
    36. /*
    37. * 客户端创建连接时触发
    38. * */
    39. @OnOpen
    40. public void onOpen(Session session, @PathParam("uid") String uid) {
    41. this.session = session;
    42. webSocketSet.add(this); // 加入set中
    43. addOnlineCount(); // 在线数加1
    44. log.info("有新窗口开始监听:" + uid + ", 当前在线人数为" + getOnlineCount());
    45. this.uid = uid;
    46. try {
    47. sendMessage("连接成功");
    48. } catch (IOException e) {
    49. log.error("websocket IO异常");
    50. }
    51. }
    52. /**
    53. * 客户端连接关闭时触发
    54. **/
    55. @OnClose
    56. public void onClose() {
    57. webSocketSet.remove(this); // 从set中删除
    58. subOnlineCount(); // 在线数减1
    59. log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
    60. }
    61. /**
    62. * 接收到客户端消息时触发
    63. */
    64. @OnMessage
    65. public void onMessage(String message, Session session) {
    66. log.info("收到来自窗口" + uid + "的信息:" + message);
    67. // 群发消息
    68. for (WebSocketServer item : webSocketSet) {
    69. try {
    70. item.sendMessage(message);
    71. } catch (IOException e) {
    72. e.printStackTrace();
    73. }
    74. }
    75. }
    76. /**
    77. * 连接发生异常时候触发
    78. */
    79. @OnError
    80. public void onError(Session session, Throwable error) {
    81. log.error("发生错误");
    82. error.printStackTrace();
    83. }
    84. /**
    85. * 实现服务器主动推送(向浏览器发消息)
    86. */
    87. public void sendMessage(String message) throws IOException {
    88. log.info("服务器消息推送:"+message);
    89. this.session.getAsyncRemote().sendText(message);
    90. }
    91. /**
    92. * 发送消息到所有客户端
    93. * 指定uid则向指定客户端发消息
    94. * 不指定uid则向所有客户端发送消息
    95. * */
    96. public static void sendInfo(String message, @PathParam("uid") String uid) throws IOException {
    97. log.info("推送消息到窗口" + uid + ",推送内容:" + message);
    98. for (WebSocketServer item : webSocketSet) {
    99. try {
    100. // 这里可以设定只推送给这个sid的,为null则全部推送
    101. if (uid == null) {
    102. item.sendMessage(message);
    103. } else if (item.uid.equals(uid)) {
    104. item.sendMessage(message);
    105. }
    106. } catch (IOException e) {
    107. continue;
    108. }
    109. }
    110. }
    111. /**
    112. *
    113. * 给多个指定uid客户端发消息
    114. *
    115. * */
    116. public static void sendInfo(String message, @PathParam("uids") String[] uids ) throws IOException {
    117. log.info("推送消息到窗口" + uids + ",推送内容:" + message);
    118. for (String uid : uids) {
    119. sendInfo(message,uid);
    120. }
    121. }
    122. /**
    123. * 发送消息到所有客户端
    124. * 指定uid则向指定客户端发消息
    125. * 不指定uid则向所有客户端发送消息
    126. * */
    127. public static void sendInfo(BaseProtocol message, @PathParam("uid") String uid) throws IOException {
    128. log.info("推送消息到窗口" + uid + ",推送内容:" + message);
    129. for (WebSocketServer item : webSocketSet) {
    130. try {
    131. // 这里可以设定只推送给这个sid的,为null则全部推送
    132. if (uid == null) {
    133. item.sendMessage(JSONUtil.toJsonStr(message));
    134. } else if (item.uid.equals(uid)) {
    135. item.sendMessage(JSONUtil.toJsonStr(message));
    136. }
    137. } catch (IOException e) {
    138. continue;
    139. }
    140. }
    141. }
    142. public static synchronized int getOnlineCount() {
    143. return onlineCount.get();
    144. }
    145. public static synchronized void addOnlineCount() {
    146. WebSocketServer.onlineCount.incrementAndGet();
    147. }
    148. public static synchronized void subOnlineCount() {
    149. WebSocketServer.onlineCount.decrementAndGet();
    150. }
    151. public static CopyOnWriteArraySet getWebSocketSet() {
    152. return webSocketSet;
    153. }
    154. }

    4、在导航条里增加一个消息

    1. "消息" effect="dark" placement="bottom">
    2. <header-notice id="message" class="right-menu-item-message hover-effect" />

    界面就是

    同时为了样式问题增加下面样式

    1. right-menu-item-message {
    2. display: inline-block;
    3. padding: 0 8px;
    4. height: 100%;
    5. font-size: 18px;
    6. color: #5a5e66;
    7. vertical-align: text-bottom;
    8. width: 36px;
    9. &.hover-effect {
    10. cursor: pointer;
    11. transition: background .3s;
    12. &:hover {
    13. background: rgba(0, 0, 0, .025)
    14. }
    15. }
    16. }

    5、增加HeaderNotice 组件,当然现在是测试,只作为websocket消息测试用,后续正式还需要修改。

    1. <script>
    2. import ShowAnnouncement from './ShowAnnouncement'
    3. import store from '@/store/'
    4. import DynamicNotice from './DynamicNotice'
    5. export default {
    6. name: "HeaderNotice",
    7. components: {
    8. DynamicNotice,
    9. ShowAnnouncement,
    10. },
    11. data() {
    12. return {
    13. loadding: false,
    14. url: {
    15. listCementByUser: "/sys/annountCement/listByUser",
    16. editCementSend: "/sys/sysAnnouncementSend/editByAnntIdAndUserId",
    17. queryById: "/sys/annountCement/queryById",
    18. },
    19. hovered: false,
    20. announcement1: [],
    21. announcement2: [],
    22. announcement3: [],
    23. msg1Count: "0",
    24. msg2Count: "0",
    25. msg3Count: "0",
    26. msg1Title: "通知(0)",
    27. msg2Title: "",
    28. msg3Title: "",
    29. stopTimer: false,
    30. websock: null,
    31. lockReconnect: false,
    32. heartCheck: null,
    33. formData: {},
    34. openPath: ''
    35. }
    36. },
    37. computed: {
    38. msgTotal() {
    39. return parseInt(this.msg1Count) + parseInt(this.msg2Count) + parseInt(this.msg3Count);
    40. }
    41. },
    42. mounted() {
    43. //this.loadData();
    44. //this.timerFun();
    45. this.initWebSocket();
    46. // this.heartCheckFun();
    47. },
    48. destroyed: function() { // 离开页面生命周期函数
    49. this.websocketOnclose();
    50. },
    51. methods: {
    52. timerFun() {
    53. this.stopTimer = false;
    54. let myTimer = setInterval(() => {
    55. // 停止定时器
    56. if (this.stopTimer == true) {
    57. clearInterval(myTimer);
    58. return;
    59. }
    60. this.loadData()
    61. }, 6000)
    62. },
    63. loadData() {
    64. try {
    65. // 获取系统消息
    66. getAction(this.url.listCementByUser).then((res) => {
    67. if (res.success) {
    68. this.announcement1 = res.result.anntMsgList;
    69. this.msg1Count = res.result.anntMsgTotal;
    70. this.msg1Title = "通知(" + res.result.anntMsgTotal + ")";
    71. this.announcement2 = res.result.sysMsgList;
    72. this.msg2Count = res.result.sysMsgTotal;
    73. this.msg2Title = "系统消息(" + res.result.sysMsgTotal + ")";
    74. this.announcement3 = res.result.todealMsgList;
    75. this.msg3Count = res.result.todealMsgTotal;
    76. this.msg3Title = "待办消息(" + res.result.todealMsgTotal + ")";
    77. }
    78. }).catch(error => {
    79. console.log("系统消息通知异常", error); //这行打印permissionName is undefined
    80. this.stopTimer = true;
    81. console.log("清理timer");
    82. });
    83. } catch (err) {
    84. this.stopTimer = true;
    85. console.log("通知异常", err);
    86. }
    87. },
    88. fetchNotice() {
    89. if (this.loadding) {
    90. this.loadding = false
    91. return
    92. }
    93. this.loadding = true
    94. setTimeout(() => {
    95. this.loadding = false
    96. }, 200)
    97. },
    98. showAnnouncement(record) {
    99. putAction(this.url.editCementSend, {
    100. anntId: record.id
    101. }).then((res) => {
    102. if (res.success) {
    103. this.loadData();
    104. }
    105. });
    106. this.hovered = false;
    107. if (record.openType === 'component') {
    108. this.openPath = record.openPage;
    109. this.formData = {
    110. id: record.busId
    111. };
    112. this.$refs.showDynamNotice.detail(record.openPage);
    113. } else {
    114. this.$refs.ShowAnnouncement.detail(record);
    115. }
    116. },
    117. toMyAnnouncement() {
    118. this.$router.push({
    119. path: '/isps/userAnnouncement'
    120. });
    121. },
    122. modalFormOk() {},
    123. handleHoverChange(visible) {
    124. this.hovered = visible;
    125. },
    126. initWebSocket: function() {
    127. // WebSocket与普通的请求所用协议有所不同,ws等同于http,wss等同于https
    128. var uid = store.getters.name;
    129. var url = process.env.VUE_APP_WS_API + "/websocket/" + uid;
    130. console.log("url=",url);
    131. this.websock = new WebSocket(url);
    132. this.websock.onopen = this.websocketOnopen;
    133. this.websock.onerror = this.websocketOnerror;
    134. this.websock.onmessage = this.websocketOnmessage;
    135. this.websock.onclose = this.websocketOnclose;
    136. },
    137. websocketOnopen: function() {
    138. console.log("WebSocket连接成功");
    139. //心跳检测重置
    140. //this.heartCheck.reset().start();
    141. },
    142. websocketOnerror: function(e) {
    143. console.log("WebSocket连接发生错误");
    144. this.reconnect();
    145. },
    146. websocketOnmessage: function(e) {
    147. console.log("-----接收消息-------", e);
    148. console.log("-----接收消息-------", e.data);
    149. var data = eval("(" + e.data + ")"); //解析对象
    150. if (data.cmd == "topic") {
    151. //系统通知
    152. //this.loadData();
    153. this.$notification.open({ //websocket消息通知弹出
    154. message: 'websocket消息通知',
    155. description: data.msgTxt,
    156. style: {
    157. width: '600px',
    158. marginLeft: `${335 - 600}px`,
    159. },
    160. });
    161. } else if (data.cmd == "user") {
    162. //用户消息
    163. //this.loadData();
    164. this.$notification.open({
    165. message: 'websocket消息通知',
    166. description: data.msgTxt,
    167. style: {
    168. width: '600px',
    169. marginLeft: `${335 - 600}px`,
    170. },
    171. });
    172. }
    173. //心跳检测重置
    174. //this.heartCheck.reset().start();
    175. },
    176. websocketOnclose: function(e) {
    177. console.log("connection closed (" + e + ")");
    178. if (e) {
    179. console.log("connection closed (" + e.code + ")");
    180. }
    181. this.reconnect();
    182. },
    183. websocketSend(text) { // 数据发送
    184. try {
    185. this.websock.send(text);
    186. } catch (err) {
    187. console.log("send failed (" + err.code + ")");
    188. }
    189. },
    190. openNotification(data) {
    191. var text = data.msgTxt;
    192. const key = `open${Date.now()}`;
    193. this.$notification.open({
    194. message: '消息提醒',
    195. placement: 'bottomRight',
    196. description: text,
    197. key,
    198. btn: (h) => {
    199. return h('a-button', {
    200. props: {
    201. type: 'primary',
    202. size: 'small',
    203. },
    204. on: {
    205. click: () => this.showDetail(key, data)
    206. }
    207. }, '查看详情')
    208. },
    209. });
    210. },
    211. reconnect() {
    212. var that = this;
    213. if (that.lockReconnect) return;
    214. that.lockReconnect = true;
    215. //没连接上会一直重连,设置延迟避免请求过多
    216. setTimeout(function() {
    217. console.info("尝试重连...");
    218. that.initWebSocket();
    219. that.lockReconnect = false;
    220. }, 5000);
    221. },
    222. heartCheckFun() {
    223. var that = this;
    224. //心跳检测,每20s心跳一次
    225. that.heartCheck = {
    226. timeout: 20000,
    227. timeoutObj: null,
    228. serverTimeoutObj: null,
    229. reset: function() {
    230. clearTimeout(this.timeoutObj);
    231. //clearTimeout(this.serverTimeoutObj);
    232. return this;
    233. },
    234. start: function() {
    235. var self = this;
    236. this.timeoutObj = setTimeout(function() {
    237. //这里发送一个心跳,后端收到后,返回一个心跳消息,
    238. //onmessage拿到返回的心跳就说明连接正常
    239. that.websocketSend("HeartBeat");
    240. console.info("客户端发送心跳");
    241. //self.serverTimeoutObj = setTimeout(function(){//如果超过一定时间还没重置,说明后端主动断开了
    242. // that.websock.close();//如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
    243. //}, self.timeout)
    244. }, this.timeout)
    245. }
    246. }
    247. },
    248. showDetail(key, data) {
    249. this.$notification.close(key);
    250. var id = data.msgId;
    251. getAction(this.url.queryById, {
    252. id: id
    253. }).then((res) => {
    254. if (res.success) {
    255. var record = res.result;
    256. this.showAnnouncement(record);
    257. }
    258. })
    259. },
    260. }
    261. }
    262. script>
    263. <style lang="css">
    264. .header-notice-wrapper {
    265. top: 50px !important;
    266. }
    267. style>
    268. <style lang="less" scoped>
    269. .header-notice {
    270. display: inline-block;
    271. transition: all 0.3s;
    272. span {
    273. vertical-align: initial;
    274. }
    275. }
    276. style>

    6、增加websocket测试页面,以便测试,地址根据自己需要进行填写

    1. <script>
    2. export default {
    3. data() {
    4. return {
    5. url: "ws://127.0.0.1:9060/websocket/ry",
    6. message: "",
    7. text_content: "",
    8. ws: null,
    9. };
    10. },
    11. methods: {
    12. join() {
    13. const wsuri = this.url;
    14. this.ws = new WebSocket(wsuri);
    15. const self = this;
    16. this.ws.onopen = function (event) {
    17. self.text_content = self.text_content + "已经打开连接!" + "\n";
    18. };
    19. this.ws.onmessage = function (event) {
    20. self.text_content = event.data + "\n";
    21. };
    22. this.ws.onclose = function (event) {
    23. self.text_content = self.text_content + "已经关闭连接!" + "\n";
    24. };
    25. },
    26. exit() {
    27. if (this.ws) {
    28. this.ws.close();
    29. this.ws = null;
    30. }
    31. },
    32. send() {
    33. if (this.ws) {
    34. const messageData = {
    35. msgTxt: this.message,
    36. cmd: 'user'
    37. }
    38. let strdata = JSON.stringify(messageData);
    39. console.log("strdata",JSON.stringify(messageData));
    40. this.ws.send(strdata);
    41. //this.ws.send(this.message);
    42. } else {
    43. alert("未连接到服务器");
    44. }
    45. },
    46. },
    47. };
    48. script>

    7、实际效果图

  • 相关阅读:
    图表控件LightningChart使用教程:创建2D 热点图图表
    AntDesignPro快速入门
    day57|647. 回文子串、516.最长回文子序列
    【QT】Qt项目demo:数据在ui界面上显示,鼠标双击可弹窗显示具体信息
    找工作小项目:day16-重构核心库、使用智能指针(1)
    SparkStreaming写入Hive慢
    机器学习(V)--无监督学习(一)聚类
    纯CSS 实现 img 图片换色
    Power BI 傻瓜入门 8. 制作数据模型
    弹性布局flex或者grid元素平分,实际会被内容撑大,问题剖析
  • 原文地址:https://blog.csdn.net/qq_40032778/article/details/133128702