websocket 具体内容参考了 原文
import {ref, onUnmounted} from 'vue';
import dayjs from "dayjs";
class Socket {
url;
ws = null;
opts;
reconnectAttempts = 0;
listeners = {};
heartbeatInterval = null;
constructor(url, opts = {}) {
this.url = url;
this.opts = {
// 心跳检测
heartbeatInterval: 30000,
// 重新连接时间间隔
reconnectInterval: 5000,
// 重连的次数
maxReconnectAttempts: 3,
...opts
};
this.init();
}
init() {
this.ws = new WebSocket(this.url);
this.ws.onopen = this.onOpen.bind(this);
this.ws.onmessage = this.onMessage.bind(this);
this.ws.onerror = this.onError.bind(this);
this.ws.onclose = this.onClose.bind(this);
}
onOpen(event) {
if (event) {
console.log(`%c websocket 已建立连接 ${dayjs(new Date()).format("HH:mm:ss")}`, 'color: green');
this.reconnectAttempts = 0;
this.startHeartbeat();
this.emit('open', event);
}
}
onMessage(event) {
this.emit('message', event.data);
}
onError(event) {
console.error('WebSocket error:', event);
this.emit('error', event);
}
onClose(event) {
console.log(`%c websocket 已断开连接 ${dayjs(new Date()).format("HH:mm:ss")}`, 'color: red');
this.stopHeartbeat();
this.emit('close', event);
if (!window.wsIsClosed) {
if (this.reconnectAttempts < this.opts.maxReconnectAttempts
) {
setTimeout(() => {
this.reconnectAttempts++;
this.init();
}, this.opts.reconnectInterval);
}
}
}
startHeartbeat() {
if (!this.opts.heartbeatInterval) return;
this.heartbeatInterval = window.setInterval(() => {
if (this.ws?.readyState === WebSocket.OPEN) {
console.log(`%c ❤ 检测 ${dayjs(new Date()).format("HH:mm:ss")}`, 'color: yellow');
this.ws.send('ping');
}
}, this.opts.heartbeatInterval);
}
stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
}
send(data) {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(data);
} else {
console.error('WebSocket is not open. Cannot send:', data);
}
}
on(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
}
off(event) {
if (this.listeners[event]) {
delete this.listeners[event];
}
}
emit(event, data) {
this.listeners[event]?.forEach(callback => callback(data));
}
}
export function useSocket(url, opts) {
const socket = new Socket(url, opts);
onUnmounted(() => {
socket.off('open');
socket.off('message');
socket.off('error');
socket.off('close');
});
return {
socket,
send: socket.send.bind(socket),
on: socket.on.bind(socket),
off: socket.off.bind(socket)
};
}
然后为了方便在 项目中进行使用,我们自定义一些 Hooks
import {useSocket} from "@/utils/websocket";
import {showNotify} from "@/utils/notifies";
export function useWebSocket(userId, store) {
const startWS = () => {
const {
socket,
on, off
} = useSocket(`${process.env.VUE_APP_BASE_API.replace("https://", "wss://").replace("http://", "ws://")}/websocket/${userId}`);
window.socketTarget = socket
window.wsIsClosed = false;
let messageQueue = []; // 用于存储消息的队列
on('message', data => {
if (data !== "ping") {
const targetObjData = JSON.parse(data);
if (targetObjData["msg_front_from"] && targetObjData) {
// 邮件消息
messageQueue.push(targetObjData); // 将消息添加到队列中
} else {
// 通知消息
store.commit("message/setNewData", data);
store.dispatch("message/getUnReadMessageListApisDefault", {pageNo: 1, pageSize: 999})
}
}
});
// 在 3 秒后处理消息队列并触发 showNotify
let timer = null;
const processMessageQueue = () => {
if (messageQueue.length > 0) {
setTimeout(() => {
store.commit("message/setEmailData", messageQueue);
showNotify(messageQueue.length, messageQueue[0], () => {
localStorage.setItem('activeMenu', '/email/inbox')
router.push('/email/inbox')
});
messageQueue = []; // 清空消息队列
clearInterval(timer)
}, 3000);
}
};
timer = setInterval(processMessageQueue, 3000); // 每 3 秒处理一次消息队列
};
const stopWS = () => {
if (window.socketTarget) {
window.socketTarget.ws.close();
window.wsIsClosed = true;
}
}
return {
startWS,
stopWS,
};
}
showNotify 的自定义
import {ElNotification} from "element-plus";
export function showNotify(messageNumber, item, fun) {
if (item) {
const {msg_front_from, msgTxt} = item
const replaceEmail = msg_front_from.replace(/^<|>$/g, "")
ElNotification({
position: 'bottom-right',
duration: 3000,
dangerouslyUseHTMLString: true,
onClick() {
fun()
},
message: `<div class="notify_box">
<div class="notify_left">
<div class="notify_inner" style="background-color:#5272e5;padding: 0px 14px;border-radius: 8px">
<svg t="1708593116687" className="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="5124" width="32" height="32">
<path
d="M891.904 145.92H128.512c-55.808 0-101.376 45.568-101.376 101.376v573.952c0 55.808 45.568 101.376 101.376 101.376h763.392c55.808 0 101.376-45.568 101.376-101.376V247.296c0-55.808-45.568-101.376-101.376-101.376z m-10.24 68.096l-352.768 285.184c-1.024 0.512-2.048 1.536-2.56 2.56-8.704 8.704-23.04 8.704-31.744 0l-2.56-2.56-352.768-285.184h742.4z m10.24 640.512H128.512c-18.432 0-32.768-14.848-32.768-32.768V266.752l351.744 284.672c17.408 16.896 39.936 25.088 62.464 25.088 22.528 0 45.056-8.192 62.464-25.088l351.744-284.672v554.496c0.512 18.432-13.824 33.28-32.256 33.28z"
p-id="5125" fill="#fff"></path>
</svg>
</div>
<div class="badge">
${messageNumber}
</div>
</div>
<div class="notify_right" style="width: 200px">
<div>
<span class="userInfo">${replaceEmail}</span>
</div>
<div style="width: 185px;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;">
${msgTxt}
</div>
</div>
</div>`
})
}
}
Hooks 的使用
import {useWebSocket} from "@/Hooks";
const {startWS, stopWS} = useWebSocket(userId, store);
//在合适的地方调用 startWS() 或 stopWS() 即可