WebSocket 的封装

发布于:2024-05-07 ⋅ 阅读:(23) ⋅ 点赞:(0)

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() 即可