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 文件引入
动态路由也要关闭