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