1.配置连接
websocket.ts文件如下
import { ElMessage } from "element-plus";interface WebSocketProps {url: string; // websocket地址heartTime?: number; // 心跳时间间隔,默认为 50000 msheartMsg?: string; // 心跳信息,默认为'ping'reconnectCount?: number; // 重连次数,默认为 5reconnectTime?: number; // 重连时间间隔,默认为 10000 msmessage: (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) => {// 初始化 WebSocketwebSocket = 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;// 清除 TokenuserStore.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 TokensetToken(token: string) {this.token = token;},setTokenName(tokenName: string) {this.tokenName = tokenName;},// Set setUserInfosetUserInfo(userInfo: any) {this.userInfo = userInfo;},// setWebSocketsetWebSocket(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 文件引入
动态路由也要关闭