《SpringBoot+Vue整合WebSocket实现前后端消息推送》

SpringBoot+Vue整合WebSocket实现前后端消息推送

1. 了解

  • HTTP和WebSocket区别联系:

    • HTTP协议:

      HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。

      这种通信模型有一个弊端:HTTP协议无法实现服务器主动向客户端发起消息。

      这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步 JavaScript 和 XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。

    • WebSocket

      WebSocket 就是这样发明的。
      WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。
      WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。

2. 后端SpringBoot集成

  • pom文件加入依赖:

    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    
  • 然后新建一个WebSocket的配置类,用来开启WebSocket的支持

    @Configuration
    public class WebSocketConfig {
     
        @Bean
        public ServerEndpointExporter serverEndpointExporter(){
            return  new ServerEndpointExporter();
        }
    }
    
  • 然后新建一个客户端实体类【WebSocketClient】用来存储连接的Session和Uri

    @Data
    public class WebSocketClient {
        //与某个客户端的连接会话,需要通过它来给客户端发送数据
        private Session session;
        //连接的uri
        private String uri;
    }
    
  • 然后新建WebSocketService,用来创建和处理连接

    import lombok.Data;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang.StringUtils;
    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.concurrent.ConcurrentHashMap;
    
    @Data
    @ServerEndpoint("/websocket/{username}")
    @Component
    @Slf4j
    public class WebSocketService {
    
        //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
        private static int onlineCount = 0;
    
        private static ConcurrentHashMap<String, WebSocketClient> webSocketMap = new ConcurrentHashMap<>();
    
        /**
         * 与某个客户端的连接会话,需要通过它来给客户端发送数据
         */
        private Session session;
        /**
         * 接收userName
         */
        private String userName = "";
    
        /**
         *  连接建立成功调用的方法
         */
        @OnOpen
        public void onOpen(Session session, @PathParam("userName") String userName) {
            if (!webSocketMap.containsKey(userName)) {
                // 在线数 +1
                addOnlineCount();
            }
            this.session = session;
            this.userName = userName;
            WebSocketClient client = new WebSocketClient();
            client.setSession(session);
            client.setUri(session.getRequestURI().toString());
            webSocketMap.put(userName, client);
            log.info("----------------------------------------------------------------------------");
            log.info("用户连接:" + userName + ",当前在线人数为:" + getOnlineCount());
            try {
                sendMessage("来自后台的反馈:连接成功");
            } catch (IOException e) {
                log.error("用户:" + userName + ",网络异常!!!!!!");
            }
        }
    
        /**
         *  连接关闭调用的方法
         */
        @OnClose
        public void onClose() {
            if (webSocketMap.containsKey(userName)) {
                webSocketMap.remove(userName);
                subOnlineCount();
            }
            log.info("----------------------------------------------------------------------------");
            log.info(userName + "用户退出,当前在线人数为:" + getOnlineCount());
    
        }
    
        /**
         * 收到客户端消息后调用的方法
         *
         * @param message 客户端发送过来的消息
         */
        @OnMessage
        public void onMessage(String message, Session session) {
            log.info("收到用户消息:" + userName + ",报文:" + message);
            //可以群发消息
            //消息保存到数据库、redis
            if (StringUtils.isNotBlank(message)) {
                //…………
            }
        }
    
        /**
         * @param session
         * @param error
         */
        @OnError
        public void onError(Session session, Throwable error) {
            log.error("用户错误:" + this.userName + ",原因:" + error.getMessage());
            error.printStackTrace();
        }
    
        /**
         * 连接服务器成功后主动推送
         */
        public void sendMessage(String message) throws IOException {
            synchronized (session) {
                this.session.getBasicRemote().sendText(message);
            }
        }
    
        /**
         * 向指定客户端发送消息
         *
         * @param userName
         * @param message
         */
        public static void sendMessage(String userName, String message) {
            try {
                WebSocketClient webSocketClient = webSocketMap.get(userName);
                if (webSocketClient != null) {
                    webSocketClient.getSession().getBasicRemote().sendText(message);
                }
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException(e.getMessage());
            }
        }
    
    
        /**
         * 群发自定义消息
         */
        public static void sendInfo(String message, @PathParam("userName") String userName) {
            for (String un : webSocketMap.keySet()) {
                //这里可以设定只推送给这个sid的,为null则全部推送
                if (userName == null) {
                    sendMessage(un, message);
                } else if (un.equals(userName)) {
                    sendMessage(un, message);
                }
            }
        }
    
        public static synchronized int getOnlineCount() {
            return onlineCount;
        }
    
        public static synchronized void addOnlineCount() {
            WebSocketService.onlineCount++;
        }
    
        public static synchronized void subOnlineCount() {
            WebSocketService.onlineCount--;
        }
    
        public static void setOnlineCount(int onlineCount) {
            WebSocketService.onlineCount = onlineCount;
        }
    
        public static ConcurrentHashMap<String, WebSocketClient> getWebSocketMap() {
            return webSocketMap;
        }
    
        public static void setWebSocketMap(ConcurrentHashMap<String, WebSocketClient> webSocketMap) {
            WebSocketService.webSocketMap = webSocketMap;
        }
    
        public Session getSession() {
            return session;
        }
    
        public void setSession(Session session) {
            this.session = session;
        }
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    }
    

    这里的WebSocketService服务可以有多个,用来代表一类场景下的的Socket服务。

    比如 H5WebSocketService(web端)、WxWebSocketService(微信端)

    • 解释:
      • 这里引用的WebSocketClient是上面自己新建的用来存储连接相关信息的实体类。
      • @ServerEndPoint("/websocket/{userName}") -> 指websocket连接的地址,后面的{username}是用来接受前端传递到参数->用作不同标识,进而做不同的处理。
      • @OnOpen() -> 代表连接成功后调用的方法,这里做了统计在线用户(可以根据自己的业务需求要去处理)。
      • @OnClose() -> 代表连接关闭后调用的方法。
      • @OnMessage() -> 代表接收到客户端发来消息时的回调方法(里面根据自己需要去处理数据)
      • sendMessage() 方法 -> 代表在客户端连接到服务器时使用当前会话来给客户端推送一个反馈消息
      • sendMessage(String userName, String message) 方法 -> 向指定客户端userName发送message。
  • 新建一个Controller接口用来测试消息的推送。

    @RestController
    @RequestMapping("/websocket")
    public class WebSocketController {
    
        @GetMapping
        public void test(){
            WebSocketService.sendMessage("h5","后端ljeabmreos");
        }
    }
    

    如果是普通的SpringBoot项目则后台到此就可以了,
    如果是使用了安全框架(如:shiro、SpringSecurity)搭建的后台,需要将ws的url和接口的url放开权限认证。

    img

3. Vue项目继承:

  • 在components目录下新建一个WebSocket组件,

    • 声明一些变量

      data() {
      	return {
      		// ws是否启动
      			wsIsRun: false,
      		// 定义ws对象
                  webSocket: null,
      		// ws请求链接(类似于ws后台地址)
      			ws: '',
      		// ws定时器
      			wsTimer: null,
      		}
      },
      
    • 在mounted函数中执行初始化websocket连接的方法。

      async mounted() {
           this.wsIsRun = true
           this.wsInit()
      },
      

      wsInit():

      进行websocket连接并传递h5作为后端接受的usernName参数,
      并且设置回调处理方法。
      最后设置了一个3秒的定时器去定时检查websocket的连接状态。

      /**
           * 初始化ws
           */
          wsInit() {
            const wsuri = "ws://127.0.0.1:8000/websocket/h5";
            this.ws = wsuri;
            if (!this.wsIsRun) return; 
            // 销毁ws
            this.wsDestroy(); 
            // 初始化ws
            this.webSocket = new WebSocket(this.ws); 
            // ws连接建立时触发
            this.webSocket.addEventListener("open", this.wsOpenHanler); 
             // ws服务端给客户端推送消息
            this.webSocket.addEventListener("message", this.wsMessageHanler);
            // ws通信发生错误时触发
            this.webSocket.addEventListener("error", this.wsErrorHanler); 
            // ws关闭时触发
            this.webSocket.addEventListener("close", this.wsCloseHanler); 
            // 检查ws连接状态,readyState值为0表示尚未连接,1表示建立连接,2正在关闭连接,3已经关闭或无法打开
            clearInterval(this.wsTimer);
            this.wsTimer = setInterval(() => {
              if (this.webSocket.readyState === 1) {
                clearInterval(this.wsTimer);
              } else {
                console.log("ws建立连接失败");
                this.wsInit();
              }
            }, 3000);
          },
      
    • 添加一个按钮并设置其点击事件用来给服务端推送消息

      methods: {
        sendDataToServer() {
          if (this.webSocket.readyState === 1) {
            this.webSocket.send("来自前端的数据:生命不必每时每刻都要冲刺。低沉时,就当给自己放了一个悠长的假期");
          } else {
            throw Error("服务未连接");
          }
      },
      
    • 完整的WebSocket组件代码:

      <!--
          websocket后期功能拓展使用预留。
       -->
      <template>
          <el-button @click="sendDataToServer">测试:给后端发送消息。</el-button>
      </template>
      <script>
      export default {
        name: "WebSocket",
        data() {
          return {
            // ws是否启动
            wsIsRun: false,
            // 定义ws对象 
            webSocket: null, 
            // ws请求链接(类似于ws后台地址)
            ws: "", 
            // ws定时器对象
            wsTimer: null,
          };
        },
        async mounted() {
          this.wsIsRun = true;
          this.wsInit();
        },
        methods: {
          sendDataToServer() {
            if (this.webSocket.readyState === 1) {
              this.webSocket.send("来自前端的数据:生命不必每时每刻都要冲刺。低沉时,就当给自己放了一个悠长的假期");
            } else {
              throw Error("服务未连接");
            }
          },
          /**
           * 初始化ws
           */
          wsInit() {
            const wsuri = "ws://127.0.0.1:8000/websocket/h5";
            this.ws = wsuri;
            if (!this.wsIsRun) return; 
            // 销毁ws
            this.wsDestroy(); 
            // 初始化ws
            this.webSocket = new WebSocket(this.ws); 
            // ws连接建立时触发
            this.webSocket.addEventListener("open", this.wsOpenHanler); 
             // ws服务端给客户端推送消息
            this.webSocket.addEventListener("message", this.wsMessageHanler);
            // ws通信发生错误时触发
            this.webSocket.addEventListener("error", this.wsErrorHanler); 
            // ws关闭时触发
            this.webSocket.addEventListener("close", this.wsCloseHanler); 
            // 检查ws连接状态,readyState值为0表示尚未连接,1表示建立连接,2正在关闭连接,3已经关闭或无法打开
            clearInterval(this.wsTimer);
            this.wsTimer = setInterval(() => {
              if (this.webSocket.readyState === 1) {
                clearInterval(this.wsTimer);
              } else {
                console.log("ws建立连接失败");
                this.wsInit();
              }
            }, 3000);
          },
          wsOpenHanler(event) {
            console.log("ws建立连接成功");
          },
          wsMessageHanler(e) {
            console.log("wsMessageHanler");
            console.log(e); //const redata = JSON.parse(e.data) //console.log(redata)
          }
          /**
           * ws通信发生错误
           */,
          wsErrorHanler(event) {
            console.log(event, "通信发生错误");
            this.wsInit();
          }
          /**
           * ws关闭
           */,
          wsCloseHanler(event) {
            console.log(event, "ws关闭");
            this.wsInit();
          }
          /**
           * 销毁ws
           */,
          wsDestroy() {
            if (this.webSocket !== null) {
              this.webSocket.removeEventListener("open", this.wsOpenHanler);
              this.webSocket.removeEventListener("message", this.wsMessageHanler);
              this.webSocket.removeEventListener("error", this.wsErrorHanler);
              this.webSocket.removeEventListener("close", this.wsCloseHanler);
              this.webSocket.close();
              this.webSocket = null;
              clearInterval(this.wsTimer);
            }
          },
        },
      };
      </script>
       
      <style scoped>
      </style>
      

4. 测试:

后端:

在这里插入图片描述

前端:

在这里插入图片描述

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
实现基于Spring Boot、VueWebSocket的聊天室并持久化消息,您需要完成以下步骤: 1. 创建Spring Boot项目 使用Spring Initializr创建一个基于Maven或Gradle的Spring Boot项目。 2. 配置WebSocketSpring Boot项目中,您需要配置WebSocket。可以使用Spring的`@EnableWebSocket`注解来开启WebSocket。 3. 编写WebSocket处理程序 创建一个WebSocket处理程序来处理WebSocket连接和消息。这个处理程序应该继承`TextWebSocketHandler`类,并实现`handleTextMessage()`方法来处理WebSocket消息。在处理程序中,您可以将接收到的消息存储到数据库中,以便在断开连接后仍然可以访问它们。 4. 创建Vue项目 使用Vue CLI创建一个新的Vue项目。 5. 集成VueWebSocketVue项目中,使用`vue-socket.io`库来集成WebSocket。这个库提供了一个`socket`对象,您可以使用它来发送和接收WebSocket消息。在Vue组件中,您可以使用`socket`对象来连接WebSocket服务器,并处理接收到的消息。 6. 编写聊天室界面 在Vue项目中,创建一个聊天室界面。这个界面应该包括一个输入框和一个消息列表。当用户在输入框中输入消息时,使用`socket`对象将该消息发送到WebSocket服务器。当接收到新消息时,将它们添加到消息列表中。 7. 持久化消息Spring Boot项目中,您可以使用JPA和Hibernate等ORM框架来将消息存储到数据库中。当处理程序接收到新消息时,将它们保存到数据库中。当用户重新连接到聊天室时,您可以从数据库中检索以前的消息并将它们添加到消息列表中。 完成以上步骤后,您应该能够创建一个基于Spring Boot、VueWebSocket的聊天室,并持久化消息
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值