首页前端组件库前端 Websocket 公共方法封装

前端 Websocket 公共方法封装

分类前端组件库时间2025-05-30 14:31:39发布RustStream浏览44
摘要:代码简单使用 function initSocket(userId { const { socket, destroy } = useSocketWithoutUrl(`/ws/websocket/${userId}`, { isInitConnect: false } ; socket.init( ; return { socket, destroy }; } __上面使用代码放在需要初始化WS地方即可,但是如果用...

代码简单使用

function initSocket(userId) {
  const { socket, destroy } = useSocketWithoutUrl(`/ws/websocket/${userId}`, { isInitConnect: false });
  socket.init();
  return { socket, destroy };
}

上面使用代码放在需要初始化WS地方即可,但是如果用360浏览器的话,需要特别处理一下。

360浏览器对WebSocket URL的严格校验
问题表现:360浏览器要求WebSocket URL必须是完整格式(包含协议和域名)

代码兼容

function initSocket(userId, ws_url) {
  // 构建完整WebSocket URL
  const buildFullUrl = (path) => {
    if (path.startsWith('ws://') || path.startsWith('wss://')) {
      return path;
    }
    const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
    return `${protocol}${window.location.host}${path}`;
  };

  const finalUrl = userId 
    ? buildFullUrl(`/ws/websocket/${userId}`)
    : buildFullUrl(ws_url);

  const { socket, destroy } = useSocketWithoutUrl(finalUrl, { isInitConnect: false });
  socket.init();
  return { socket, destroy };
}

实现代码如下

import { onUnmounted } from 'vue';


/**
 * WebSocket 工具类
 * 用于管理 WebSocket 连接、发送消息、接收消息等功能
  * 支持自动重连、心跳检测等功能
  * 使用单例模式确保全局只有一个 WebSocket 实例
  * 使用 Vue 的生命周期钩子函数来管理 WebSocket 的连接和销毁
  * 使用事件监听机制来处理 WebSocket 的各种事件
  * 使用默认的 WebSocket URL 和可选的配置参数
  * @example
  * const { socket, send, on, off, destroy } = useSocket('/ws/websocket/123', { heartbeatInterval: 30000 });
  * socket.on('open', () => console.log('WebSocket opened'));
  * socket.on('message', (data) => console.log('Received message:', data));
  * send(JSON.stringify({ type: 'message', content: 'Hello, WebSocket!' }));
  * on('close', () => console.log('WebSocket closed'));
  * off('message'); // 移除消息监听
  * destroy(); // 销毁 WebSocket 实例
  * @returns {Object} 包含 WebSocket 实例、发送消息方法、事件监听方法和销毁方法
  * @property {WebSocket} socket - WebSocket 实例
  * @property {Function} send - 发送消息方法
  * @property {Function} on - 添加事件监听方法
  * @property {Function} off - 移除事件监听方法
  * @property {Function} destroy - 销毁 WebSocket 实例方法
  * opts - 可选配置参数
  * @property {number} opts.heartbeatInterval - 心跳检测间隔,单位毫秒,默认 30000
  * @property {number} opts.reconnectInterval - 重连间隔,单位毫秒,默认 5000
  * @property {number} opts.maxReconnectAttempts - 最大重连次数,默认 5
  * @property {boolean} opts.isInitConnect - 是否在实例化时自动连接,默认 true
  ...
 */

const Socket = (() => {
  let instance = null;

  class Socket {
    url;
    ws = null;
    opts = {};
    reconnectAttempts = 0;
    listeners = {};
    heartbeatInterval = null;
    isConnecting = false;

    constructor(url, opts = {}) {
      this.url = url;
      this.opts = {
        heartbeatInterval: 30000,
        reconnectInterval: 5000,
        maxReconnectAttempts: 5,
        isInitConnect: true,
        ...opts
      };

      this.opts.isInitConnect && this.init();
    }

    static getInstance(url, opts) {
      if (!instance) {
        instance = new Socket(url, opts);
      }
      return instance;
    }

    init() {
      if (this.isConnecting) return;
      
      this.isConnecting = true;
      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) {
      // console.log('WebSocket opened:', event);
      this.isConnecting = false;
      this.reconnectAttempts = 0;
      this.startHeartbeat();
      this.emit('open', event);
    }

    onMessage(event) {
      // console.log('WebSocket message received:', event.data);
      this.emit('message', event.data);
    }

    onError(event) {
      console.error('WebSocket error:', event);
      this.isConnecting = false;
      this.emit('error', event);
    }

    onClose(event) {
      console.log('WebSocket closed:', event);
      this.isConnecting = false;
      this.stopHeartbeat();
      this.emit('close', event);
      
      if (this.reconnectAttempts < this.opts.maxReconnectAttempts) {
        setTimeout(() => {
          this.reconnectAttempts++;
          this.init();
        }, this.opts.reconnectInterval);
      }
    }

    startHeartbeat() {
      if (!this.opts.heartbeatInterval) return;
      
      this.heartbeatInterval = setInterval(() => {
        if (this.ws?.readyState === WebSocket.OPEN) {
          this.ws.send(JSON.stringify({ type: 'heartbeat', timestamp: Date.now() }));
        }
      }, 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);
        // 自动重连并发送
        if (!this.isConnecting) {
          this.init();
        }
      }
    }

    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));
    }
    
    // 销毁实例方法
    static destroyInstance() {
      if (instance) {
        instance.stopHeartbeat();
        instance.ws?.close();
        instance = null;
      }
    }
  }

  return Socket;
})();

// 
export function useSocket(url, opts = {}) {
  const socket = Socket.getInstance(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),
    destroy: Socket.destroyInstance.bind(Socket) // 添加销毁方法
  };
}

// Export the default URL socket
export function useSocketWithoutUrl(url, opts = {}) {
  return useSocket(url, opts);
}

本文链接:https://blog.smallhao.fun/?id=30 转载需授权!

分享到:

Chen’Blog版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

JavaScript
根据传入的文本宽度计算平均宽度并返回(用于el-table中的设置宽度) 后台管理系统消息通知模块

游客 回复需填写必要信息
召唤伊斯特瓦尔