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 文件引入

动态路由也要关闭

相关推荐
科技探秘人5 分钟前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人6 分钟前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR11 分钟前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香13 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q24985969316 分钟前
前端预览word、excel、ppt
前端·word·excel
小华同学ai21 分钟前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
Gavin_91530 分钟前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼2 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
逐·風6 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
Devil枫6 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试