From 97352b97d02e8459ea6625f768bd2d31be5ed7ef Mon Sep 17 00:00:00 2001 From: wujingjing <gersonwu@qq.com> Date: 星期一, 13 一月 2025 15:29:58 +0800 Subject: [PATCH] sse测试 --- src/utils/sse/index.ts | 59 ++++++++++++++ vite.config.ts | 6 + src/utils/sse/SSEClient.ts | 175 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 240 insertions(+), 0 deletions(-) diff --git a/src/utils/sse/SSEClient.ts b/src/utils/sse/SSEClient.ts new file mode 100644 index 0000000..095a9ec --- /dev/null +++ b/src/utils/sse/SSEClient.ts @@ -0,0 +1,175 @@ +import type { AxiosRequestConfig } from 'axios'; + +export interface SSEOptions { + /** 閲嶈瘯娆℃暟 */ + retries?: number; + /** 閲嶈瘯寤惰繜(ms) */ + retryDelay?: number; + /** 瓒呮椂鏃堕棿(ms) */ + timeout?: number; + /** 璇锋眰閰嶇疆 */ + // requestConfig?: AxiosRequestConfig; + /** 鏄惁鑷姩閲嶈繛 */ + autoReconnect?: boolean; +} + +export interface SSEEventCallbacks { + /** 娑堟伅鍥炶皟 */ + onMessage?: (data: any) => void; + /** 閿欒鍥炶皟 */ + onError?: (error: Error) => void; + /** 杩炴帴鎵撳紑鍥炶皟 */ + onOpen?: () => void; + /** 杩炴帴鍏抽棴鍥炶皟 */ + onClose?: () => void; + /** 閲嶈瘯鍥炶皟 */ + onRetry?: (retryCount: number) => void; +} + +export class SSEClient { + private eventSource: EventSource | null = null; + private retryCount = 0; + private isConnecting = false; + private reconnectTimeout: number | null = null; + private abortController: AbortController | null = null; + + constructor(private url: string, private options: SSEOptions = {}, private callbacks: SSEEventCallbacks = {}) { + // 璁剧疆榛樿鍊� + this.options = { + retries: 3, + retryDelay: 1000, + timeout: 30000, + autoReconnect: true, + ...options, + }; + } + + /** + * 寤虹珛杩炴帴 + */ + async connect(params?: Record<string, any>): Promise<void> { + if (this.isConnecting) return; + this.isConnecting = true; + + try { + // 鏋勫缓 URL 鍜屽弬鏁� + const queryString = params ? `?${new URLSearchParams(params).toString()}` : ''; + const fullUrl = `${this.url}${queryString}`; + + // 鍒涘缓 AbortController 鐢ㄤ簬瓒呮椂鎺у埗 + this.abortController = new AbortController(); + + // 璁剧疆瓒呮椂 + const timeoutId = setTimeout(() => { + this.abortController?.abort(); + throw new Error('Connection timeout'); + }, this.options.timeout); + + // 鍒涘缓 EventSource + this.eventSource = new EventSource(fullUrl); + + // 娓呴櫎瓒呮椂 + clearTimeout(timeoutId); + + // 缁戝畾浜嬩欢澶勭悊 + this.bindEvents(); + + this.isConnecting = false; + } catch (error) { + this.isConnecting = false; + await this.handleError(error); + } + } + + /** + * 缁戝畾浜嬩欢澶勭悊 + */ + private bindEvents(): void { + if (!this.eventSource) return; + + this.eventSource.onopen = () => { + this.callbacks.onOpen?.(); + }; + + this.eventSource.onmessage = (event) => { + try { + const data = JSON.parse(event.data); + // 妫�鏌ユ槸鍚︽槸缁撴潫鏍囪 + if (data === '[DONE]') { + this.disconnect(); + return; + } + this.callbacks.onMessage?.(data); + } catch (error) { + console.error('Failed to parse SSE data:', error); + this.callbacks.onError?.(new Error('Failed to parse SSE data')); + } + }; + + this.eventSource.onerror = async (error) => { + await this.handleError(error); + }; + } + + /** + * 閿欒澶勭悊 + */ + private async handleError(error: any): Promise<void> { + this.callbacks.onError?.(error); + + if (this.options.autoReconnect && this.retryCount < (this.options.retries || 0)) { + // debugger; + this.retryCount++; + this.callbacks.onRetry?.(this.retryCount); + + // 寤惰繜閲嶈瘯 + await new Promise((resolve) => { + this.reconnectTimeout = setTimeout(resolve, this.options.retryDelay); + }); + + // 閲嶆柊杩炴帴 + await this.connect(); + } else { + this.disconnect(); + } + } + + /** + * 鏂紑杩炴帴 + */ + disconnect(): void { + if (this.reconnectTimeout) { + clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = null; + } + + if (this.eventSource) { + this.eventSource.close(); + this.eventSource = null; + } + + if (this.abortController) { + this.abortController.abort(); + this.abortController = null; + } + + this.isConnecting = false; + this.callbacks.onClose?.(); + this.retryCount = 0; + } + + /** + * 鏄惁宸茶繛鎺� + */ + isConnected(): boolean { + return this.eventSource?.readyState === EventSource.OPEN; + } + + /** + * 閲嶆柊杩炴帴 + */ + async reconnect(): Promise<void> { + this.disconnect(); + await this.connect(); + } +} diff --git a/src/utils/sse/index.ts b/src/utils/sse/index.ts new file mode 100644 index 0000000..b908e12 --- /dev/null +++ b/src/utils/sse/index.ts @@ -0,0 +1,59 @@ +import { onMounted, onUnmounted } from 'vue'; +import { SSEClient } from './SSEClient'; + +// 鍒涘缓瀹炰緥 +const sseClient = new SSEClient( + '/events', + { + retries: 3, + retryDelay: 1000, + timeout: 30000, + autoReconnect: true, + + }, + { + onMessage: (data) => { + console.log('Received message:', data); + }, + onError: (error) => { + console.error('SSE error:', error); + }, + onOpen: () => { + console.log('SSE connection opened'); + }, + onClose: () => { + console.log('SSE connection closed'); + }, + onRetry: (retryCount) => { + console.log(`Retrying connection (${retryCount})`); + }, + } +); + +// 鍦� Vue 缁勪欢涓娇鐢� +export function useSSE() { + // 寤虹珛杩炴帴 + sseClient.connect({ + userId: 'xxx', + sessionId: 'xxx', + }); + + // onMounted(() => { + // // 寤虹珛杩炴帴 + // sseClient.connect({ + // userId: 'xxx', + // sessionId: 'xxx', + // }); + // }); + + // onUnmounted(() => { + // // 鏂紑杩炴帴 + // sseClient.disconnect(); + // }); + + return { + isConnected: () => sseClient.isConnected(), + reconnect: () => sseClient.reconnect(), + disconnect: () => sseClient.disconnect(), + }; +} diff --git a/vite.config.ts b/vite.config.ts index badea13..27dae42 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -59,6 +59,12 @@ port: env.VITE_PORT as unknown as number, open: JSON.parse(env.VITE_OPEN), hmr: true, + proxy: { + '/events': { + target: 'http://localhost:3000', + changeOrigin: true + } + } }, build: { // outDir: 'dist/' + mode.mode, -- Gitblit v1.9.3