WebSocket 前端使用vue3+ts+elementplus 实现连接

1.配置连接

websocket.ts文件如下

import { ElMessage } from "element-plus";

interface WebSocketProps {
  url: string; // websocket地址
  heartTime?: number; // 心跳时间间隔,默认为 50000 ms
  heartMsg?: string; // 心跳信息,默认为'ping'
  reconnectCount?: number; // 重连次数,默认为 5
  reconnectTime?: number; // 重连时间间隔,默认为 10000 ms
  message: (ev: MessageEvent) => any; // 接收消息的回调
  open?: (ev: Event) => any; // 连接成功的回调
  close?: (ev: CloseEvent) => any; // 关闭的回调
  error?: (ev: Event) => any; // 错误的回调
}

// webSocket 对象
let webSocket: WebSocket | null = null;
// webSocket定时器id
let setIntervalId: NodeJS.Timeout | null = null;

export const initWebSocket = (config: WebSocketProps) => {
  if (typeof WebSocket === "undefined") {
    ElMessage.error("您的浏览器不支持Websocket通信协议,请使用Chrome或者其他高版本的浏览器!");
    return;
  }
  if (webSocket != null && webSocket.readyState === webSocket.OPEN) {
    return webSocket;
  }
  createWebSocket(config);
  return webSocket;
};

/**
 * 创建WebSocket
 * @param config
 */
const createWebSocket = (config: WebSocketProps) => {
  // 初始化 WebSocket
  webSocket = new WebSocket(config.url);
  webSocket.onopen = (ev: Event) => {
    config.open && config.open(ev);
    /**
     * 发送心跳
     * 使用Nginx代理WebSocket的时候,客户端与服务器握手成功后,如果在60秒内没有数据交互,就会自动断开连接。
     * Nginx默认的断开链接时间为60秒
     */
    sendPing(config.heartTime ?? 50000, config.heartMsg ?? "ping");
  };
  webSocket.onmessage = (ev: MessageEvent) => config.message(ev);
  webSocket.onerror = (ev: Event) => error(config, ev);
  webSocket.onclose = (ev: CloseEvent) => close(config, ev);
};

/**
 * 发送心跳
 * @param {number} heartTime 心跳间隔毫秒 默认50000
 * @param {string} heartMsg 心跳名称 默认字符串ping
 */
const sendPing = (heartTime: number, heartMsg: string) => {
  webSocket?.send(heartMsg);
  setIntervalId = setInterval(() => {
    webSocket?.send(heartMsg);
  }, heartTime);
};

/**
 * WebSocket 关闭的回调方法
 * @param config
 */
const close = (config: WebSocketProps, ev: CloseEvent) => {
  config.close && config.close(ev);
  clearInterval(Number(setIntervalId));
};

let falg = false;
// 重连次数
let reconnectCount = 0;
// 重连定时器id
let reconnectId: NodeJS.Timeout | null = null;

/**
 * WebSocket 关闭的回调方法
 * @param config
 */
const error = (config: WebSocketProps, ev: Event) => {
  config.error && config.error(ev);
  if (falg) return;
  reconnectId = setInterval(() => {
    falg = true;
    reconnectCount++;
    console.log("正在重新连接,次数:" + reconnectCount);
    let socket = initWebSocket(config);
    if (socket?.readyState === socket?.OPEN) {
      reconnectCount = 0;
      falg = false;
      clearInterval(Number(reconnectId));
    }
    if (reconnectCount >= 5) {
      clearInterval(Number(reconnectId));
    }
  }, config.reconnectTime ?? 10000);
};

2. 创建链接

新建 websocket.vue文件

<template>
  <div></div>
</template>

<script setup lang="ts" name="WebSocket">
import { useUserStore } from "@/stores/modules/user";
import { DEV_WS_URL_HEAD, DEV_WS_URL_TAIL, PRO_WS_URL_HEAD, PRO_WS_URL_TAIL } from "@/api/config/websocketUrl";
import { WebSocketMsg, EventKeyEnum } from "@/api/interface/webSocketMsg/index";
import { initWebSocket } from "@/utils/websocket";
import { ElMessageBox, ElNotification } from "element-plus";
import mittBus from "@/utils/mittBus";
import { LOGIN_URL } from "@/config";//export const LOGIN_URL: string = "/login";这是登录的路径
const userStore = useUserStore();//   userStore.setToken(data.tokenValue);登录的时候存 token
const router = useRouter();
const webSocket = initWebSocket({
  url:
    (import.meta.env.VITE_WS_FLAG == "production" ? PRO_WS_URL_HEAD + PRO_WS_URL_TAIL : DEV_WS_URL_HEAD + DEV_WS_URL_TAIL) +
    "/webSocketService/" +
    userStore.token,
  open: () => {
    console.info("连接WebSocket成功");
  },
  message: (event: MessageEvent) => {
    const webSocketMsg: WebSocketMsg = JSON.parse(event.data);
    console.log("[webSocketMsg] data: " + event.data);
    switch (webSocketMsg.eventKey) {
      case EventKeyEnum.CONNECTION_SUCCESS:
        mittBus.emit("init_seat");
        break;
      case EventKeyEnum.MSG_COMMON:
        mittBus.emit(EventKeyEnum.MSG_COMMON, event.data);
        break;
      case EventKeyEnum.SATOKEN:
        mittBus.emit(EventKeyEnum.SATOKEN, event.data);
        break;
    }
  },
  close: () => {
    console.log("close");
  },
  error: () => {
    console.log("error");
  }
});

userStore.setWebSocket(webSocket ?? null);
// 后端推送消息,执行相关操作
mittBus.on(EventKeyEnum.SATOKEN, (val: any) => {
  let msgData = JSON.parse(val);
  let eventKey = msgData.eventKey;
  let msgContent = msgData.msgContent;
  // 清除 Token
  userStore.setToken("");
  // 清除用户信息
  userStore.setUserInfo("");
  // 清除所有数据
  userStore?.webSocket?.close();
  userStore.setWebSocket(null);
  // 3.重定向到登陆页
  router.replace(LOGIN_URL);
  if (eventKey == "SATOKEN") {
    ElMessageBox.confirm(msgContent, "提示", {
      confirmButtonText: "确认",
      type: "error",
      showCancelButton: false
    });
  }
  // 当页面关闭的时候,去销毁这个事务线程 ---> 解决mitt多次触发
  mittBus.all.delete(EventKeyEnum.SATOKEN);
});
mittBus.on(EventKeyEnum.MSG_COMMON, (val: any) => {
  let msgData = JSON.parse(val);
  let eventKey = msgData.eventKey;
  let msgContent = msgData.msgContent;
  let sendTime = msgData.sendTime;
  if (eventKey == "MSG_COMMON") {
    ElNotification({
      title: "管理员消息",
      dangerouslyUseHTMLString: true,
      position: "bottom-right",
      duration: 0,
      customClass: "msg",
      message: `<span style="color:gray">${sendTime}<span><br/><pre>${msgContent}</pre>`
    });
  }
  // 当页面关闭的时候,去销毁这个事务线程 ---> 解决mitt多次触发
  // mittBus.all.delete(EventKeyEnum.MSG_COMMON);
});
</script>

<style scoped lang="scss"></style>

下面的文件都是上面第二步用到的文件

引用到的 user 文件

import { defineStore } from "pinia";
import { UserState } from "@/stores/interface";
//UserState用到的类型如下
//export interface UserState {
  token: string;
  tokenName: string;
  userInfo: any;
  webSocket: WebSocket | null;
}

import piniaPersistConfig from "@/stores/helper/persist";

export const useUserStore = defineStore({
  id: "geeker-user",
  state: (): UserState => ({
    token: "",
    tokenName: "",
    userInfo: "",
    webSocket: null
  }),
  getters: {},
  actions: {
    // Set Token
    setToken(token: string) {
      this.token = token;
    },
    setTokenName(tokenName: string) {
      this.tokenName = tokenName;
    },
    // Set setUserInfo
    setUserInfo(userInfo: any) {
      this.userInfo = userInfo;
    },
    // setWebSocket
    setWebSocket(webSocket: WebSocket | null) {
      this.webSocket = webSocket;
    }
  },
  persist: piniaPersistConfig("geeker-user")
});

持久化文件 pinia

persist.ts

import { PersistedStateOptions } from "pinia-plugin-persistedstate";

/**
 * @description pinia 持久化参数配置
 * @param {String} key 存储到持久化的 name
 * @param {Array} paths 需要持久化的 state name
 * @return persist
 * */
const piniaPersistConfig = (key: string, paths?: string[]) => {
  const persist: PersistedStateOptions = {
    key,
    storage: localStorage,
    // storage: sessionStorage,
    paths
  };
  return persist;
};

export default piniaPersistConfig;

websocketUrl文件

websocketUrl.ts

/**
 * 连接WebSocket服务地址的网关IP端口 -- 开发环境
 * (解决扫描漏洞:IP地址泄露)
 */

// 头部
//示例"ws://199.166.0."
export const DEV_WS_URL_HEAD = "";

// 尾部
//示例"11:1111"
export const DEV_WS_URL_TAIL = "";

/**
 * 连接WebSocket服务地址的网关IP端口 -- 正式环境
 * (解决扫描漏洞:IP地址泄露)
 */

// 头部
//示例"ws://00.111."
export const PRO_WS_URL_HEAD = "";

// 尾部
//示例"111.11:1111"
export const PRO_WS_URL_TAIL = "";

@/api/interface/webSocketMsg/index.ts 文件如下

/**
 * WebSocket 消息类型
 */
export interface WebSocketMsg {
  /**
   * 事件标识
   **/
  eventKey: EventKeyEnum | "";

  /**
   * 用户id
   **/
  userId: string;

  /**
   * 用户所属团队id
   **/
  userTeamId?: string;

  /**
   * 用户token
   **/
  token?: string;
  /**
   * 消息内容
   *
   **/
  msgContent: string;

  /**
   * 消息发送时间(yyyy-MM-dd HH:mm:ss)
   *
   **/
  sendTime: string;
  /**
   * 是否发送给所有人
   *
   **/
  everyone: boolean;
}

export enum EventKeyEnum {
  /**
   * WebSocket连接成功标识,根据后台定义
   */
  CONNECTION_SUCCESS = "",
  /**
   * 提醒消息推送
   */
  MSG_COMMON = "",

  /**
   * 用户登录认证相关消息
   */
  SATOKEN = ""
}

mitt 使用

mittBus.ts文件

import mitt from "mitt";

const mittBus = mitt();

export default mittBus;

番外

在响应拦截器要关闭连接

退出登录也关闭连接

在框架main 文件引入

动态路由也要关闭

相关推荐
ekskef_sef12 分钟前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
群联云防护小杜20 分钟前
如何给负载均衡平台做好安全防御
运维·服务器·网络·网络协议·安全·负载均衡
ihengshuai20 分钟前
HTTP协议及安全防范
网络协议·安全·http
sunshine64137 分钟前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻1 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云1 小时前
npm淘宝镜像
前端·npm·node.js
dz88i81 小时前
修改npm镜像源
前端·npm·node.js
Jiaberrr1 小时前
解锁 GitBook 的奥秘:从入门到精通之旅
前端·gitbook
顾平安2 小时前
Promise/A+ 规范 - 中文版本
前端
聚名网2 小时前
域名和服务器是什么?域名和服务器是什么关系?
服务器·前端