卢春龙
2026-04-07 8a0057c91a7e1fdebbedbd29f64dd4b6b73d58fa
src/utils/websocket.ts
@@ -15,7 +15,9 @@
  private maxReconnectAttempts: number = 5;
  private reconnectTimer: any = null;
  private heartbeatTimer: any = null;
  private heartbeatInterval: number = 30000; // 30秒心跳
  private heartbeatTimeoutTimer: any = null; // 用于检测心跳超时的定时器
  private heartbeatInterval: number = 10000; // 10秒心跳
  private heartbeatTimeout: number = 3000;   // 5秒收不到响应即视为掉线
  private listeners: Map<string, MessageCallback[]> = new Map();
  private globalEventsBound: boolean = false;
@@ -94,6 +96,7 @@
      this.socketTask.onOpen(async () => {
        console.log('WebSocket 连接已打开');
        this.isConnected = true;
        this.isConnecting = false;
        this.reconnectAttempts = 0;
        this.startHeartbeat();
        const uid = await getStorage(_UID) || '-1';
@@ -103,6 +106,7 @@
      this.socketTask.onMessage((res) => {
        try {
          this.resetHeartbeatTimeout();
          const data = JSON.parse(res.data as string);
          console.log('收到 WebSocket 消息:', res.data);
          const content = JSON.parse(data.content || '{}');
@@ -120,6 +124,7 @@
      this.socketTask.onClose((res) => {
        console.log('WebSocket 连接已关闭', res);
        this.isConnected = false;
        this.isConnecting = false;
        this.stopHeartbeat();
        this.socketTask = null;
@@ -132,6 +137,7 @@
      this.socketTask.onError((err) => {
        console.error('WebSocket 错误:', err);
        this.isConnected = false;
        this.isConnecting = false;
        this.notifyListeners('error', err);
      });
      return;
@@ -143,6 +149,7 @@
    Taro.onSocketOpen(async () => {
      console.log('WebSocket 连接已打开');
      this.isConnected = true;
      this.isConnecting = false;
      this.reconnectAttempts = 0;
      this.startHeartbeat();
      const uid = await getStorage(_UID) || '-1';
@@ -152,6 +159,7 @@
    Taro.onSocketMessage((res) => {
      try {
        this.resetHeartbeatTimeout();
        const data = JSON.parse(res.data as string);
        console.log('收到 WebSocket 消息:', res.data);
        const content = JSON.parse(data.content || '{}');
@@ -169,6 +177,7 @@
    Taro.onSocketClose((res) => {
      console.log('WebSocket 连接已关闭', res);
      this.isConnected = false;
      this.isConnecting = false;
      this.stopHeartbeat();
      this.socketTask = null;
@@ -181,6 +190,7 @@
    Taro.onSocketError((err) => {
      console.error('WebSocket 错误:', err);
      this.isConnected = false;
      this.isConnecting = false;
      this.notifyListeners('error', err);
    });
  }
@@ -263,6 +273,15 @@
    this.heartbeatTimer = setInterval(() => {
      if (this.isConnected) {
        this.send({ type: 'ping', timestamp: Date.now() });
        // 发送 ping 后,启动一个超时检测
        // 如果在配置的时间内没有收到任何消息(pong 或业务消息),则认为连接已假死
        if (this.heartbeatTimeoutTimer) {
          clearTimeout(this.heartbeatTimeoutTimer);
        }
        this.heartbeatTimeoutTimer = setTimeout(() => {
          console.warn('心跳超时,未收到服务器响应,主动断开 WebSocket 连接');
          this.closeAndReconnect();
        }, this.heartbeatTimeout);
      }
    }, this.heartbeatInterval);
  }
@@ -275,6 +294,46 @@
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
    if (this.heartbeatTimeoutTimer) {
      clearTimeout(this.heartbeatTimeoutTimer);
      this.heartbeatTimeoutTimer = null;
    }
  }
  /**
   * 收到消息时重置心跳超时检测
   */
  private resetHeartbeatTimeout() {
    if (this.heartbeatTimeoutTimer) {
      clearTimeout(this.heartbeatTimeoutTimer);
      this.heartbeatTimeoutTimer = null;
    }
  }
  /**
   * 主动关闭并重连 (处理假死状态)
   */
  private closeAndReconnect() {
    // 强制关闭当前连接
    if (this.socketTask) {
      try {
        this.socketTask.close({
          code: 1000,
          reason: '心跳超时主动关闭'
        });
      } catch (e) {
        console.error('强制关闭 Socket 失败:', e);
      }
      this.socketTask = null;
    }
    this.isConnected = false;
    this.isConnecting = false;
    this.stopHeartbeat();
    // 立即触发重连
    if (!this.isManualClose) {
      this.handleReconnect();
    }
  }
  /**