tsx81428 发表于 2025-4-3 08:50:53

SpringBoot+Vue 中 WebSocket 的利用

        WebSocket 是一种在单个 TCP 毗连上进行全双工通信的协议,它使得客户端和服务器之间可以进行实时数据传输,打破了传统 HTTP 协议请求 - 响应模式的限制。
        下面我会展示在 SpringBoot + Vue 中,利用WebSocket进行前后端通信。
后端

1、引入 jar 包

<dependency><!-- 引入 websocket 库,该库提供了对 WebSocket 协议的支持-->
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
    <version>2.7.14</version>
</dependency>
<dependency><!-- 引入 org.json 库,该库为 Java 提供了处理 JSON 数据的功能-->
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20090211</version>
</dependency> 2、WebSocket 设置类

package com.zecApi.config;

import com.zecApi.config.zecInstantMessaging.ZecInstantMessagingWebSocketHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;

/**
* WebSocketConfig 类是一个配置类,用于配置 Spring 框架的 WebSocket 功能。
* 该类实现了 WebSocketConfigurer 接口,并重写了 registerWebSocketHandlers 方法,
* 用于注册 WebSocket 处理器和配置相关的连接信息。
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    static {
      System.out.println("----------------------------------");
      System.out.println("------   WebSocket服务启动   -------");
      System.out.println("----------------------------------");
    }

    /**
   * 配置 WebSocket 容器的参数
   * @return ServletServerContainerFactoryBean 实例
   */
    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
      ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
      // 设置最大文本消息缓冲区大小为 8192 字节
      container.setMaxTextMessageBufferSize(8192);
      // 设置最大二进制消息缓冲区大小为 8192 字节
      container.setMaxBinaryMessageBufferSize(8192);
      return container;
    }

    /**
   * 重写 WebSocketConfigurer 接口的 registerWebSocketHandlers 方法,
   * 该方法用于注册 WebSocket 处理器并配置连接的相关信息。
   *
   * @param registry 用于注册 WebSocket 处理器的注册表对象
   */
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
      // 注册WebSocket处理器,并设置允许的来源
      registry.addHandler(zecInstantMessagingWebSocketHandler(), "/ZecInstantMessaging/ZecInstantMessagingWebSocketHandler/{account}")
                .setAllowedOrigins("*"); // 允许所有来源,生产环境建议指定具体的域
    }

    /**
   * 定义一个名为 ZecInstantMessagingWebSocketHandler 的 Bean,该 Bean 是一个自定义的 WebSocket 处理器。
   * Spring 会将该 Bean 注入到应用程序中,以便在 WebSocket 连接时使用。
   *
   * @return 返回一个 ZecInstantMessagingWebSocketHandler 实例
   */
    @Bean
    public ZecInstantMessagingWebSocketHandler zecInstantMessagingWebSocketHandler() {
      return new ZecInstantMessagingWebSocketHandler();
    }

} 3、WebSocket 处理器类 

package com.zecApi.config.zecInstantMessaging;

import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
* 自定义的 WebSocket 处理器类,继承自 TextWebSocketHandler,用于处理 WebSocket 连接、消息收发和连接关闭等操作。
*/
//@Component
@ServerEndpoint("/ZecInstantMessaging/ZecInstantMessagingWebSocketHandler/{account}")
public class ZecInstantMessagingWebSocketHandler extends TextWebSocketHandler {

    /**
   * 用于存储账号和 WebSocketSession 映射关系的并发哈希表。
   * 键为账号,值为对应的 WebSocketSession 对象,方便根据账号查找对应的会话。
   * 采用 ConcurrentHashMap 保证在多线程环境下的线程安全。
   */
    private static final ConcurrentHashMap<String, WebSocketSession> sessionPool = new ConcurrentHashMap<>();

    /**
   * 用于存储所有 WebSocketSession 的并发列表。
   * 该列表用于存储所有当前活跃的 WebSocket 会话,方便进行广播等操作。
   * 采用 CopyOnWriteArrayList 保证在多线程环境下的线程安全。
   */
    private static final CopyOnWriteArrayList<WebSocketSession> sessions = new CopyOnWriteArrayList<>();

    /**
   * 当与客户端的 WebSocket 连接建立成功后,此方法会被调用。
   *
   * @param session 代表与客户端建立的 WebSocket 会话对象。
   */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
      try {
            // 从 URI 中获取 account 参数
            String account = extractAccountFromSession(session);
            // 检查是否成功获取到账号信息
            if (account == null) {
                // 若未获取到账号信息,打印错误信息并关闭连接
                System.out.println("客户端连接失败,未获取到账号信息");
                session.close();
                return;
            }
            // 打印客户端连接成功信息,包含账号信息
            System.out.println("账号 " + account + " 已上线");
            // 将账号和对应的 WebSocketSession 存入 sessionPool 中
            sessionPool.put(account, session);
            // 将该 WebSocketSession 存入 sessions 列表中
            sessions.add(session);
            // 遍历 sessionPool 中的所有账号,打印在线账号信息
            for (String key : sessionPool.keySet()) {
                System.out.println("在线账号: " + key);
            }
            // 打印当前在线人数
            System.out.println("在线人数:" + sessionPool.size());
      } catch (IOException e) {
            // 处理关闭连接时可能出现的异常
            System.out.println("处理连接建立时出现 I/O 异常: " + e.getMessage());
      }
    }

    /**
   * 当接收到客户端发送的文本消息时,此方法会被调用。
   *
   * @param session 代表与客户端建立的 WebSocket 会话对象。
   * @param message 客户端发送的文本消息对象。
   */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) {
      try {
            // 从接收到的 TextMessage 对象中提取消息的具体内容
            String payload = message.getPayload();
            // 打印接收到的消息内容,方便调试和监控
            System.out.println("收到客户端发来的消息: " + payload);

            // 解析 JSON 数据
            JSONObject jsonObject = new JSONObject(payload);
            // 提取消息 ID
            long messageId = jsonObject.optLong("id", -1);
            if (messageId == -1) {
                System.out.println("未找到有效的消息 ID");
            }

            // 提取真正的消息内容
            StringBuilder content = new StringBuilder();
            for (int i = 0; ; i++) {
                String charStr = jsonObject.optString(String.valueOf(i));
                if (charStr.isEmpty()) {
                  break;
                }
                content.append(charStr);
            }

            // 真正的信息
            String msg = content.toString();
            //把发来的信息按照 / 分成数组
            String[] splitMsg = msg.split("/");
            switch (splitMsg){
                case"chat":
                  System.out.println("聊天来了");
                  break;
                default:
                  System.out.println("默认");
            }

            // 打印提取的消息内容
            System.out.println("提取的消息内容: " + msg);
            // 向发送消息的客户端回送一条确认消息,告知服务器已成功收到消息
            // 创建一个 JSONObject 对象
            JSONObject response = new JSONObject();
            // 向 JSONObject 中添加键值对,键为 "friendAccount",值为 "friendAccount"(这里的值用的是前端传回来的)
            response.put("friendAccount", splitMsg);
            // 将 messageId 添加到响应中(因为前端在发送信息后会校验返回数据的id,从而结束监听计时器,如果没有 id,连接计时器不会停止,导致超时报错)
            response.put("id", messageId);
            // 将 JSONObject 转换为 JSON 格式的字符串
            String jsonString = response.toString();
            // 把信息发送给前端
            session.sendMessage(new TextMessage(jsonString));
      } catch (IOException e) {
            // 若在处理消息或发送确认消息过程中出现 I/O 异常,捕获该异常
            // 并打印错误信息,包含异常的具体描述,便于后续排查问题
            System.out.println("处理消息时出现异常: " + e.getMessage());
      } catch (JSONException e) {
            throw new RuntimeException(e);
      }
    }

    /**
   * 当与客户端的 WebSocket 连接关闭时,此方法会被调用。
   *
   * @param session 代表与客户端建立的 WebSocket 会话对象。
   * @param status 表示连接关闭的状态信息。
   */
    @Override
    public void afterConnectionClosed(WebSocketSession session, org.springframework.web.socket.CloseStatus status) {
      // 从 WebSocketSession 的 URI 中提取账号信息
      String account = extractAccountFromSession(session);
      // 检查是否成功获取到账号信息
      if (account != null) {
            // 从 sessionPool 中移除该账号对应的 WebSocketSession
            sessionPool.remove(account);
            // 从 sessions 列表中移除该 WebSocketSession
            sessions.remove(session);
            // 打印客户端断开连接信息,包含账号信息和当前在线人数
            System.out.println("账号 " + account + " 已下线" );
            // 打印当前在线人数
            System.out.println("在线人数:" + sessionPool.size());
      }
    }

    /**
   * 从 WebSocketSession 的 URI 中提取账号信息。
   *
   * @param session 代表与客户端建立的 WebSocket 会话对象。
   * @return 提取到的账号信息,如果未找到则返回 null。
   */
    private String extractAccountFromSession(WebSocketSession session) {
      if (session.getUri() == null) {
            return null;
      }
      // 获取 WebSocketSession 的 URI 并转换为字符串
      String uri = session.getUri().toString();
      // 查找 URI 中最后一个斜杠的位置
      int index = uri.lastIndexOf("/");
      // 检查是否找到斜杠且斜杠后面还有字符
      if (index != -1 && index < uri.length() - 1) {
            // 提取斜杠后面的部分作为账号信息
            return uri.substring(index + 1);
      }
      // 若未找到合适的账号信息,返回 null
      return null;
    }
} 前端

1、WebSocket 工具JS

        这个是用来毗连 webSocket 的。
// 定义 WebSocket 实例变量,用于存储当前的 WebSocket 连接
let webSocket;
// 我这里本来是用 sessionStorage 来获取登录账号的,这边演示的话我就直接写死了。
// const account = getAccountBySessionStorage();
const account = "987654321";

/**
* WebSocket还有一个readyState属性,可以用来获取当前连接的状态。
* readyState有四个可能的值:0(连接尚未建立)、1(连接已建立,可以通信)、2(连接正在关闭)、3(连接已关闭)
*/

/**
* 登录时连接webSocket
* @param account
* 这个是我这边登录后进行 websocket 连接,这里演示没用到,下面刷新也可以进行连接
*/
// export function loginConnectWebSocket(account) {
//   if (account === null){
//         return;
//   }else {
//         if (!webSocket || webSocket.readyState === WebSocket.CLOSED){
//             return connectWebSocket(account);
//         }
//   }
// }

/**
* 监听页面刷新,重新进行 WebSocket 连接
*/
window.onload = function() {
    console.log("刷新了");
    if (account === null){
      return;
    }else {
      if (!webSocket || webSocket.readyState === WebSocket.CLOSED){
            connectWebSocket(account);
      }
    }
};

/**
* 监听页面关闭事件,页面刷新前也会触发(页面刷新会自动断开websocket连接,这个暂时不需要)
*/
// window.onbeforeunload = function (){
//   disconnectWebSocket();
// };

/**
* 获取当前的 WebSocket 实例
* @returns {WebSocket|null} 当前的 WebSocket 实例或 null
*/
export function getWebSocket() {
    return webSocket;
}

/**
* 主动断开 WebSocket 连接
*/
export function disconnectWebSocket() {
    if (webSocket && webSocket.readyState !== WebSocket.CLOSED) {
      webSocket.close();
      webSocket = null;
    }
}

/**
* 封装一个连接 webSocket 的操作
*/
export function connectWebSocket(account){
    return new Promise((resolve, reject) => {
      // 检查是否已经存在有效的 WebSocket 连接
      if (webSocket && webSocket.readyState === WebSocket.OPEN) {
            return resolve(webSocket);
      }
      // 如果存在连接但已经关闭,先断开
      if (webSocket && webSocket.readyState !== WebSocket.CLOSED) {
            // console.log("断开了");
            webSocket.close();
      }
      webSocket = new WebSocket(`ws://localhost:8088/ZecInstantMessaging/ZecInstantMessagingWebSocketHandler/${account}`);
      // 连接成功时的处理
      webSocket.onopen = function () {
            // console.log('WebSocket 连接已建立');
            resolve(webSocket);
      };
      // 连接错误时的处理
      webSocket.onerror = function (error) {
            // console.error('WebSocket 连接错误:', error);
            reject(error);
      };
      // 连接关闭时的处理
      webSocket.onclose = function () {
            // console.log('WebSocket 连接已关闭');
      };
    });
} 2、VUE 页面

<template>
<div style="width: 100%;height: 120px;">
    <div>
      <textarea v-model="message" style="height: 50px;resize: none; width: 97%;font-family: 'Arial'; font-size: 14px;padding: 5px"></textarea>
    </div>
    <div>
      <el-button style="margin-top: 5px;position: fixed; right: 12px;" @click="sendMessage">发送</el-button>
    </div>
</div>
</template>

<script>
import {ref} from "vue";
import {sendMessageJS} from "@/module/zec-instant-messaging/api/MessagePage";

export default {
name: "DataScreen",
setup(){
    let message = ref("");
    // 聊天页面记录的好友的账号(我这里单独写一个页面就直接写死账号了,主要用来演示 websocket,你们用的话可以替换成自己的)
    let friendAccount = ref("123456789");
    // 我的账号
    let myAccount = ref("987654321");
    // 发送消息
    const sendMessage = async () => {
      const msg = "chat" + "/" + myAccount.value + "/" + friendAccount.value + "/" + message.value + "/注释:标识、我的账号、好友的账号、信息";
      // 开始发送
      try {
      // 这里进行发送
      const response = await sendMessageJS(msg);
      // 如果发送成功,才会执行下面的语句
      console.log('信息发送成功,并接收到后端返回的信息:', response);
      console.log("id:"+response.id);
      console.log("friendAccount::"+response.friendAccount);
      // 执行后续代码
      } catch (error) {
      // 如果信息发送失败,就执行下面的语句
      console.error('信息发送失败:', error);
      }
    };

    return{
      sendMessage,
      message
    }
}
}
</script>

<style scoped>

</style> 3、页面 JS

import {getWebSocket} from "@/common/webSocketUtil";

// 发送信息的函数
export async function sendMessageJS(msg) {
    const webSocket = getWebSocket();
    return new Promise((resolve, reject) => {
      // 检查WebSocket连接状态
      if (webSocket.readyState !== WebSocket.OPEN) {
            reject(new Error('WebSocket 连接未打开!'));
            return;
      }
      // 生成唯一的消息ID
      const messageId = Date.now();
      const message = { ...msg, id: messageId };

      // 设置超时时间
      const timeoutId = setTimeout(() => {
            reject(new Error('信息发送超时'));
            webSocket.removeEventListener('message', handleMessage);
            webSocket.removeEventListener('error', handleError);
      }, 5000); // 5秒超时

      // 定义处理消息响应的函数
      const handleMessage = (event) => {
            const response = JSON.parse(event.data);
            if (response.id === messageId) {
                clearTimeout(timeoutId);
                resolve(response);
                // 移除事件监听器
                webSocket.removeEventListener('message', handleMessage);
                webSocket.removeEventListener('error', handleError);
            }
      };

      // 定义处理错误的函数
      const handleError = (error) => {
            clearTimeout(timeoutId);
            reject(error);
            // 移除事件监听器
            webSocket.removeEventListener('message', handleMessage);
            webSocket.removeEventListener('error', handleError);
      };

      // 添加事件监听器
      webSocket.addEventListener('message', handleMessage);
      webSocket.addEventListener('error', handleError);

      // 发送消息
      webSocket.send(JSON.stringify(message));
    });
} 测试

后端打印

https://i-blog.csdnimg.cn/direct/dd25722b5a274221b54a4d696e6df3ae.png
         这是后端接收到前端的信息。
 前端打印

https://i-blog.csdnimg.cn/direct/e54c2e463318443390cf6024839a8ce6.png
        这是发送信息到后端,并接收后端返返来的数据。

代码具体功能我都写有解释,假如有题目可以联系我进行调整。
这是我单独拎出来写的,假如有题目可以联系我进行调整。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: SpringBoot+Vue 中 WebSocket 的利用