Vue3使用@vueuse/core集成Websocket实战及携带身份信息的3种方式

引言

在前一篇文章中,实现了Spring Boot中集成Websocket服务,本篇文章简单介绍Vue3生态中,利用优秀的工具库@vueuse/core提供的useWebSocket组合式API,轻松集成WebSocket功能,并介绍3种前端携带token身份信息后端进行身份认证的方式。

Vue3项目集成Websocket

项目代码包不做过多阐述,直接使用npm create vue@latest 指令创建一个基本的项目,安装@vueuse/core,直接在组件中引入useWebsocket即可,useWebSocket文档地址。组件代码如下,基本代码是deepseek所写。

TypeScript 复制代码
<template>
  <div class="websocket-demo">
    <div class="status-bar">
      <span>连接状态:</span>
      <span :class="statusClass">{{ statusText }}</span>
      <button @click="toggleConnection" :disabled="connecting">
        {{ isConnected ? "断开连接" : "连接" }}
      </button>
    </div>

    <div class="message-area">
      <h3>接收消息</h3>
      <div class="message-list">
        <div
          v-for="(msg, index) in receivedMessages"
          :key="index"
          class="message-item"
        >
          {{ msg }}
        </div>
        <div v-if="receivedMessages.length === 0" class="empty-text">
          暂无消息
        </div>
      </div>
    </div>

    <div class="send-area">
      <input
        v-model="message"
        @keyup.enter="sendMessage"
        placeholder="输入消息..."
        :disabled="!isConnected"
      />
      <button @click="sendMessage" :disabled="!isConnected">发送</button>
    </div>

    <div class="logs">
      <h3>日志</h3>
      <div class="log-list">
        <div v-for="(log, index) in logs" :key="index" class="log-item">
          {{ log }}
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from "vue";
import { useWebSocket } from "@vueuse/core";

// 建立websocket连接
const { data, close, open, send } = useWebSocket(
  "ws://localhost:8089/ws",
  {
    autoConnect: false,
    autoReconnect: false,
    autoClose: true,
    immediate: false,
    heartbeat: {
      message: "ping",
      interval: 5000,
      pongTimeout: 10000,
    },
    onMessage(ws, event) {
      console.log("Received message:", event.data);
      addLog(`收到消息: ${event.data}`);
    },

    onError(ws, event) {
      console.error("WebSocket error:", event);
      addLog("WebSocket 发生错误");
    },

    onConnected() {
      isConnected.value = true;
      addLog("WebSocket 连接成功");
    },

    onDisconnected() {
      isConnected.value = false;
      addLog("WebSocket 已断开连接");
    }
  },
);

// 响应式数据
const isConnected = ref(false);
const connecting = ref(false);
const receivedMessages = ref<string[]>([]);
const message = ref("");
const logs = ref<string[]>([]);

// 计算属性
const statusText = computed(() => {
  if (connecting.value) return "连接中...";
  return isConnected.value ? "已连接" : "未连接";
});

const statusClass = computed(() => ({
  "status-connected": isConnected.value,
  "status-disconnected": !isConnected.value && !connecting.value,
  "status-connecting": connecting.value,
}));

// 添加日志
const addLog = (message: string) => {
  const time = new Date().toLocaleTimeString();
  logs.value.push(`[${time}] ${message}`);
  // 限制日志数量
  if (logs.value.length > 50) {
    logs.value.pop();
  }
};

// 切换连接状态
const toggleConnection = () => {
  if (isConnected.value) {
    close();
    addLog("正在断开连接...");
  } else {
    open();
    addLog("正在连接...");
  }
};

// 发送消息
const sendMessage = () => {
  addLog(`发送消息: ${message.value}`);
  send(message.value);
  message.value = "";
};

// 组件挂载时自动连接
onMounted(() => {
  // 可选:自动连接
  // connecting.value = true
  // initWebSocket()
});

// 组件卸载时断开连接
onUnmounted(() => {});
</script>

<style scoped>
.websocket-demo {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
  font-family: Arial, sans-serif;
}

.status-bar {
  padding: 15px;
  background: #f5f5f5;
  border-radius: 8px;
  margin-bottom: 20px;
  display: flex;
  align-items: center;
  gap: 15px;
}

.status-connected {
  color: green;
  font-weight: bold;
}

.status-disconnected {
  color: red;
  font-weight: bold;
}

.status-connecting {
  color: orange;
  font-weight: bold;
}

button {
  padding: 8px 16px;
  background: #4caf50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover:not(:disabled) {
  background: #45a049;
}

button:disabled {
  background: #cccccc;
  cursor: not-allowed;
}

.message-area,
.send-area,
.logs {
  margin-bottom: 20px;
}

.message-area h3,
.logs h3 {
  margin-bottom: 10px;
  color: #333;
}

.message-list,
.log-list {
  border: 1px solid #ddd;
  border-radius: 4px;
  height: 200px;
  overflow-y: auto;
  padding: 10px;
  background: #fafafa;
}

.message-item,
.log-item {
  padding: 5px;
  border-bottom: 1px solid #eee;
  font-size: 14px;
  word-break: break-all;
}

.message-item:last-child,
.log-item:last-child {
  border-bottom: none;
}

.empty-text {
  text-align: center;
  color: #999;
  padding: 20px;
}

.send-area {
  display: flex;
  gap: 10px;
}

.send-area input {
  flex: 1;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 14px;
}

.send-area input:focus {
  outline: none;
  border-color: #4caf50;
}

.log-list {
  height: 150px;
  font-family: monospace;
  font-size: 12px;
}

.log-item {
  color: #666;
}
</style>

携带身份信息的三种方式

一:使用URL参数(简单场景)

这是最直接的方式,客户端在连接URL中附上token,服务端从握手请求中解析参数。

Spring Boot服务端实现:

java 复制代码
@Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
                                   Map<String, Object> attributes) throws Exception {
        HttpServletRequest httpRequest = ((ServletServerHttpRequest) request).getServletRequest();
        String token = httpRequest.getParameter("token");
        // 验证token逻辑...
        if (isValid(token)) {
            // 此处可直接根据token获取用户信息,并保存在属性中
            attributes.put("user", token);
            return true;
        }else {
            log.error("token验证失败");
            return false;
        }
    }

二:使用子协议(Subprotocol)

WebSocket支持在连接时指定子协议,客户端可在子协议中携带认证信息,服务端通过子协议处理器来验证。

HTTP Sec-WebSocket-Protocol 请求头和响应头用于 WebSocket 开启握手,以协商在通信中使用的子协议。这可以是一个广为人知的协议,例如 SOAP 或 WAMP,也可以是客户端和服务器理解的自定义协议。

在请求中,该头按偏好顺序指定一个或多个 WebSocke 子协议,web 应用程序希望使用这些协议。这些协议值可以作为多个头中的协议值添加,也可以作为逗号分隔的值添加到单个头中。

在响应中,它指定服务器选择的子协议。这必须是服务器从请求头提供的列表中支持的第一个子协议。

请求头由浏览器使用应用程序在 WebSocket() 的 protocols 参数中指定的值自动添加和填充。服务器选择的子协议在 WebSocket.protocol 中提供给 web 应用程序。

Spring Boot服务端实现:如果前端通过websocket连接时指定了Sec-WebSocket-Protocol,后端接收到连接后,必须原封不动的将Sec-WebSocket-Protocol头信息返回给前端,否则连接会抛出异常。

java 复制代码
@Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
                                   Map<String, Object> attributes) throws Exception {
        HttpServletRequest httpRequest = ((ServletServerHttpRequest) request).getServletRequest();
        String token = httpRequest.getHeader("Sec-WebSocket-Protocol");
       HttpServletResponse httpResponse = ((ServletServerHttpResponse) response).getServletResponse();
       httpResponse.addHeader("Sec-WebSocket-Protocol", token);
       // 可根据token进行登录信息查找 放入属性中
       attributes.put("queryString", token);
       return true;
    }

三:使用特定的消息

在连接建立后,发送一个特定的消息,消息携带token,后端解析消息,进行身份的识别。

Spring Boot服务端实现:

java 复制代码
@Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String payload = message.getPayload();
        JSONObject entries = JSONUtil.parseObj(payload);
        if (entries.containsKey("type") && entries.getStr("type").equals("auth")){
            String token = entries.getStr("token");
            session.getAttributes().put("token", token);
            return;
        }
        log.info("收到消息:{}", payload);

        // 发送回复消息
        String replyMessage = "服务器收到消息:" + payload;
        session.sendMessage(new TextMessage(replyMessage));
    }

总结

本文简要介绍了如何在Vue3中搭建Websocket服务,并介绍了3中与后端进行身份信息传输的方式。WebSocket 的身份验证核心在于握手机制------利用连接建立前的 HTTP 请求完成凭证传递。

相关推荐
独断万古他化2 小时前
【Java 实战项目】多用户网页版聊天室:项目总览与用户 & 好友管理模块实现
java·spring boot·后端·websocket·mybatis
zl_dfq3 小时前
计算机网络 之 【http协议】(http的无状态性、Cookie与Session的简介)
网络协议·计算机网络·http
添砖java‘’3 小时前
应用层协议HTTP
网络·网络协议·http
英俊潇洒美少年4 小时前
前端六种通信 API
网络·websocket·网络协议
荣仔灬5 小时前
怎么查询SSL证书的信息?
网络·网络协议·ssl
发光小北5 小时前
欧姆龙串口转网口可以怎么应用呢?
网络协议
moton20175 小时前
TLS会话恢复机制深度解析:Session ID、Ticket 与 TLS1.3 PSK架构
数据库·网络协议·安全·架构·ssl·物联网架构
Narv工程师5 小时前
无人机通信利器:MAVLink协议全解析
网络协议