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技术栈