• springBoot与Vue共同搭建webSocket环境


    欢迎使用Markdown编辑器

    你好! 这片文章将教会你从后端springCloud到前端VueEleementAdmin如何搭建Websocket

    前端

    1. 创建websocket的配置文件在utils文件夹下websocket.js
    // my-js-file.js
    import { Notification } from 'element-ui'
    // 暴露自定义websocket对象
    export const socket = {
      // 后台请求路径
      url: '',
      websocketCount: -1,
      // websocket对象
      websocket: null,
      // websocket状态
      websocketState: false,
      // 重新连接次数
      reconnectNum: 0,
      // 重连锁状态,保证重连按顺序执行
      lockReconnect: false,
      // 定时器信息
      timeout: null,
      clientTimeout: null,
      serverTimeout: null,
      // 初始化方法,根据url创建websocket对象封装基本连接方法,并重置心跳检测
      initWebSocket(newUrl) {
        socket.url = newUrl
        socket.websocket = new WebSocket(socket.url)
        socket.websocket.onopen = socket.websocketOnOpen
        socket.websocket.onerror = socket.websocketOnError
        socket.websocket.onmessage = socket.webonmessage
        socket.websocket.onclose = socket.websocketOnClose
        this.resetHeartbeat()
      },
      reconnect() {
        // 判断连接状态
        console.log('判断连接状态')
        if (socket.lockReconnect) return
        socket.reconnectNum += 1
        // 重新连接三次还未成功调用连接关闭方法
        if (socket.reconnectNum === 3) {
          socket.reconnectNum = 0
          socket.websocket.onclose()
          return
        }
        // 等待本次重连完成后再进行下一次
        socket.lockReconnect = true
        // 5s后进行重新连接
        socket.timeout = setTimeout(() => {
          socket.initWebSocket(socket.url)
          socket.lockReconnect = false
        }, 5000)
      },
      // 重置心跳检测
      resetHeartbeat() {
        socket.heartbeat()
      },
      // 心跳检测
      heartbeat() {
        socket.clientTimeout = setTimeout(() => {
          if (socket.websocket) {
            // 向后台发送消息进行心跳检测
            socket.websocket.send(JSON.stringify({ type: 'heartbeat' }))
            socket.websocketState = false
            // 一分钟内服务器不响应则关闭连接
            socket.serverTimeout = setTimeout(() => {
              if (!socket.websocketState) {
                socket.websocket.onclose()
                console.log('一分钟内服务器不响应则关闭连接')
              } else {
                this.resetHeartbeat()
              }
            }, 60 * 1000)
          }
        }, 3 * 1000)
      },
      // 发送消息
      sendMsg(message) {
        socket.websocket.send(message)
      },
      websocketOnOpen(event) {
        // 连接开启后向后台发送消息进行一次心跳检测
        socket.sendMsg(JSON.stringify({ type: 'heartbeat' }))
      },
      // 初始化websocket对象
      // window.location.host获取ip和端口,
      // process.env.VUE_APP_WEBSOCKET_BASE_API获取请求前缀
      // 绑定接收消息方法
      webonmessage(event) {
        // 初始化界面时,主动向后台发送一次消息,获取数据
        this.websocketCount += 1
        if (this.websocketCount === 0) {
          const queryCondition = {
            type: 'message'
          }
          socket.sendMsg(JSON.stringify(queryCondition))
          console.log('初始化界面时,主动向后台发送一次消息,获取数据')
        }
        const info = JSON.parse(event.data)
        switch (info.type) {
          case 'heartbeat':
            socket.websocketState = true
            console.log(JSON.stringify(info))
            break
          case 'message':
            if (info.message === '物资管理模块-导入成功!') {
              Notification({
                title: '消息',
                message: '物资管理模块-导入成功,请手动刷新页面查看数据!',
                type: 'success',
                duration: 0,
                position: 'top-righ'
              })
            } else {
              Notification({
                title: '消息',
                message: '错了:' + info.message,
                type: 'error',
                duration: 0,
                position: 'top-righ'
              })
            }
    
            break
          case 'error':
            console.log('websocket:error')
            break
        }
      },
      websocketOnError(error) {
        console.log(error)
        console.log('websocket报错了' + error)
        socket.reconnect()
      },
      websocketOnClose() {
        console.log('websocke他关闭了')
        socket.websocket.close()
      }
    }
    
    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    2. 在main.js中引入配置文件,使websocket全局都能使用

    import { socket } from './utils/websocket'
    Vue.prototype.$socket = socket

    3. 设置登陆时开启websocket连接

    this. s o c k e t . i n i t W e b S o c k e t ( ‘ w s : socket.initWebSocket( `ws: socket.initWebSocket(ws:{process.env.VUE_APP_WEBSOCKET_BASE_API}/websocket/` + this.loginForm.username
    ) 这句是开启websocket连接的。

    // 登录方法
    handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true
          this.$store.dispatch('user/login', this.loginForm)
            .then(() => {
              this.$router.push({ path: this.redirect || '/', query: this.otherQuery })
              this.$store.dispatch('user/addInfomation', this.infoMation)
              this.$socket.initWebSocket(
                `ws:${process.env.VUE_APP_WEBSOCKET_BASE_API}/websocket/` + this.loginForm.username
              )
              this.loading = false
            })
            .catch(() => {
              this.loading = false
            })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    4. 设置路由跳转时判断websocket连接是否断开,断开重连(在router的router.beforeEach函数中)
     if (socket.websocketState === false) {
          socket.initWebSocket(
            `ws:${process.env.VUE_APP_WEBSOCKET_BASE_API}/websocket/` + localStorage.getItem('USERNAME')
          )
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    5. 设置退出时关闭websocket连接

    this.$socket.websocketOnClose()这句是关闭websocket连接的

     logout() {
          await this.$store.dispatch('user/logout')
          // 重点
          this.$socket.websocketOnClose()
          this.$router.push(`/login?redirect=${this.$route.fullPath}`)
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    重点和需要配置的地方都在websocket.js里比如接收消息方法webonmessage

    后端

    1.引依赖
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-websocketartifactId>
            dependency>
            
    
    • 1
    • 2
    • 3
    • 4
    • 5
    2.写配置
    package com.szc.material.analysisService.confg.WebSocket;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.socket.server.standard.ServerEndpointExporter;
    
    @Configuration
    public class WebSocketConfig   {
        /**
         * 	注入ServerEndpointExporter,
         * 	这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
         */
        @Bean
        public ServerEndpointExporter serverEndpointExporter() {
            return new ServerEndpointExporter();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    3.创建Websocket服务类(依据自己的业务去改@OnMessage方法)
    package com.szc.material.analysisService.confg.WebSocket;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import org.springframework.web.socket.TextMessage;
    import org.springframework.web.socket.WebSocketSession;
    import org.springframework.web.socket.handler.TextWebSocketHandler;
    
    import javax.security.auth.message.MessageInfo;
    import javax.websocket.*;
    import javax.websocket.server.PathParam;
    import javax.websocket.server.ServerEndpoint;
    import java.io.IOException;
    import java.util.*;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.CopyOnWriteArraySet;
    
    /**
     * @author zhj
     * @ServerEndpoint:将目前的类定义成一个websocket服务器端,注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
     * @OnOpen:当WebSocket建立连接成功后会触发这个注解修饰的方法。
     * @OnClose:当WebSocket建立的连接断开后会触发这个注解修饰的方法。
     * @OnMessage:当客户端发送消息到服务端时,会触发这个注解修改的方法。
     * @OnError:当WebSocket建立连接时出现异常会触发这个注解修饰的方法。
     * ————————————————
     * 版权声明:本文为CSDN博主「人人都在发奋」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
     * 原文链接:https://blog.csdn.net/qq991658923/article/details/127022522
     */
    @Component
    @Slf4j
    @ServerEndpoint("/websocket/{userId}")
    public class MyWebSocketHandler extends TextWebSocketHandler {
    
        /**
         * 线程安全的无序的集合
         */
        private static final CopyOnWriteArraySet<Session> SESSIONS = new CopyOnWriteArraySet<>();
    
        /**
         * 存储在线连接数
         */
        private static final Map<String, Session> SESSION_POOL = new HashMap<>();
    
        @OnOpen
        public void onOpen(Session session, @PathParam(value = "userId") String userId) {
            try {
    
                SESSIONS.add(session);
                SESSION_POOL.put(userId, session);
                log.info("【WebSocket消息】有新的连接,总数为:" + SESSIONS.size());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @OnClose
        public void onClose(Session session,@PathParam(value = "userId") String userId) {
            try {
    
                SESSIONS.remove(session);
                SESSION_POOL.remove(userId);
                log.info("【WebSocket消息】连接断开,总数为:" + SESSION_POOL.size());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @OnMessage
        public void onMessage(String message, @PathParam(value = "userId") String userId) {
            JSONObject jsonObject = JSON.parseObject(message);
            if("heartbeat".equals(jsonObject.get("type").toString())){
                Map<String,String> messageInfor=new HashMap<>();
                messageInfor.put("type","heartbeat");
                messageInfor.put("message","我收到了你的心跳");
                sendOneMessage( userId,messageInfor);
                log.info("【WebSocket消息】收到客户端消息:" + message);
            }
    
        }
    
        /**
         * 此为广播消息
         *
         * @param message 消息
         */
        public void sendAllMessage(String message) {
            log.info("【WebSocket消息】广播消息:" + message);
            for (Session session : SESSIONS) {
                try {
                    if (session.isOpen()) {
                        session.getAsyncRemote().sendText(message);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 此为单点消息
         *
         * @param userId  用户编号
         * @param message 消息
         */
        public void sendOneMessage(String userId, Map<String,String> message) {
            Session session = SESSION_POOL.get(userId);
            if (session != null && session.isOpen()) {
                try {
                    synchronized (session) {
                        log.info("【WebSocket消息】单点消息:" + message.get("message"));
                        session.getAsyncRemote().sendText(JSON.toJSONString(message));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 此为单点消息(多人)
         *
         * @param userIds 用户编号列表
         * @param message 消息
         */
        public void sendMoreMessage(String[] userIds, String message) {
            for (String userId : userIds) {
                Session session = SESSION_POOL.get(userId);
                if (session != null && session.isOpen()) {
                    try {
                        log.info("【WebSocket消息】单点消息:" + message);
                        session.getAsyncRemote().sendText(message);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        }
    
    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    4.在需要的地方进行调用

    (1)先注入websocket服务类

        @Autowired
        MyWebSocketHandler myWebSocketHandler;
    
    • 1
    • 2

    (2)在方法中调用给前端发消息的方法
    在这里插入图片描述

          Map<String,String> messageInfor=new HashMap<>();
            messageInfor.put("type","message");
            messageInfor.put("message","数据录入模块-导入成功!");
            myWebSocketHandler.sendOneMessage(userId,messageInfor);
    
    • 1
    • 2
    • 3
    • 4

    问题:

    1.如果你在controller层调用了service层中身为异步的方法出现了HttpServeletrequst空指针你需要在controller层调用异步方法前加入下面的代码。

    ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            RequestContextHolder.getRequestAttributes().setAttribute("request", request, RequestAttributes.SCOPE_REQUEST);
            RequestContextHolder.setRequestAttributes(servletRequestAttributes,true);//设置子线程共享
     
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    调用requst中的参数时必须使用下面的方法

     HttpServletRequest request2 = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
      if( request2.getHeader(UserContext.USER_NAME)!=null){
                return Optional.of(request2.getHeader(UserContext.USER_NAME));
            }
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    算法练习(11):牛客在线编程07 动态规划
    【毕业设计】前后端分离——解决cookies跨域
    RabbitMQ初步到精通-第十一章-RabbitMQ之常见问题汇总
    如何将 Bootstrap CSS 和 JS 添加到 Thymeleaf
    Spring框架(五):SpringAop底层原理和注解配置
    【数据结构】树与二叉树(九):二叉树的后序遍历(非递归算法NPO)
    Spring--BeanUtils工具类--使用/实例
    【性能测试】中间件优化
    WebSocket介绍与应用
    LINUX定时解压缩方案
  • 原文地址:https://blog.csdn.net/weixin_47615289/article/details/134011939