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