收藏

SSE推送消息

2025-05-13  本文已影响0人  啵崽崽

第一步:

main.ts文件或者是@/layout/index.vue根据实际需要改

import { initSSE } from '@/utils/sse';

onMounted(() => {
  // 初始化 SSE
  initSSE(import.meta.env.VITE_APP_BASE_API + '/resource/sse');
});

第二步:

sse.ts

import { getToken } from '@/utils/auth';
import { useSSE } from '@/utils/useSSE';

// 初始化
export const initSSE = (url: any) => {
  if (import.meta.env.VITE_APP_SSE === 'false') {
    return;
  }
  url = url + '?Authorization=Bearer ' + getToken();
  const { data, error } = useSSE({
    getUrl: () => url,
    maxRetries: -1, // 无限重试
    reconnectDelay: 3000
  });

  watch(error, () => {
    console.log('SSE connection error:', error.value);
    error.value = null;
  });

  watch(data, () => {
    if (!data.value) return;
    // 这里会拿到推送的消息
    console.log(data.value);
    data.value = null;
  });
};

第三步:

useSSE.ts

import { ref, onMounted, onUnmounted } from 'vue';

interface UseSSEOptions {
  maxRetries?: number;  // 重试次数
  reconnectDelay?: number;
  getUrl: () => string;
}

export function useSSE(options: UseSSEOptions) {
  const { maxRetries = -1, reconnectDelay = 3000, getUrl } = options;

  const data = ref<string | null>(null);
  const error = ref<Event | null>(null);
  const isConnected = ref(false);

  let eventSource: EventSource | null = null;
  let retries = 0;
  let reconnectTimer: number | null = null;
  let heartbeatTimer: number | null = null;
  let isUnmounted = false;

  const HEARTBEAT_TIMEOUT = 60000; // 60秒无消息视为断连
  let lastActive = Date.now(); // 记录上次活跃时间

  const cleanup = () => {
    if (eventSource) {
      eventSource.close();
      eventSource = null;
    }
    if (reconnectTimer) {
      clearTimeout(reconnectTimer);
      reconnectTimer = null;
    }
    if (heartbeatTimer) {
      clearInterval(heartbeatTimer);
      heartbeatTimer = null;
    }
    isConnected.value = false;
  };

  const scheduleReconnect = () => {
    if (isUnmounted) return;
    if (maxRetries !== -1 && retries >= maxRetries) {
      console.warn(`[SSE] 达到最大重连次数 (${maxRetries})`);
      return;
    }
    retries++;
    reconnectTimer = window.setTimeout(() => {
      console.log(`[SSE] 重连尝试 ${retries}`);
      connect();
    }, reconnectDelay);
  };

  const connect = () => {
    const fullUrl = getUrl();
    console.log('[SSE] 正在连接:');
    cleanup(); // 清理旧连接(如果有)

    try {
      eventSource = new EventSource(fullUrl);

      eventSource.onopen = () => {
        isConnected.value = true;
        retries = 0;
        lastActive = Date.now();
        console.log('[SSE] 连接成功');
      };

      eventSource.onmessage = (event) => {
        lastActive = Date.now();
        data.value = event.data;
      };

      eventSource.onerror = (evt) => {
        console.warn('[SSE] 出现错误,准备重连...');
        error.value = evt;
        cleanup();
        scheduleReconnect();
      };

      // 启动心跳检测定时器
      if (heartbeatTimer) clearInterval(heartbeatTimer);
      heartbeatTimer = window.setInterval(() => {
        if (Date.now() - lastActive > HEARTBEAT_TIMEOUT) {
          console.warn('[SSE] 超过心跳时间未收到数据,主动重连');
          cleanup();
          scheduleReconnect();
        }
      }, HEARTBEAT_TIMEOUT);
    } catch (err) {
      console.error('[SSE] 连接失败:', err);
      scheduleReconnect();
    }
  };

  const onOnline = () => {
    if (!isConnected.value) {
      console.log('[SSE] 网络恢复,尝试重新连接...');
      connect();
    }
  };

  // 启动连接
  isUnmounted = false;
  connect();
  window.addEventListener('online', onOnline);
  // 在组件销毁时清理连接
  onUnmounted(() => {
    isUnmounted = true;
    cleanup();
    window.removeEventListener('online', onOnline);
  });

  return {
    data,
    error,
    isConnected
  };
}

.env.development、.env.production需要设置一个开关,在不同环境中使用

# sse 开关
VITE_APP_SSE = true

目前是用vue3+TS技术栈

上一篇 下一篇

猜你喜欢

热点阅读