|
|
@@ -0,0 +1,212 @@
|
|
|
+class WebSocketService {
|
|
|
+ constructor() {
|
|
|
+ this.socket = null;
|
|
|
+ this.reconnectAttempts = 0;
|
|
|
+ this.maxReconnectAttempts = 5;
|
|
|
+ this.reconnectInterval = 3000; // 3秒
|
|
|
+ this.messageHandlers = new Map();
|
|
|
+ this.isConnected = false;
|
|
|
+ this.url = null;
|
|
|
+ this.reconnectTimer = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 连接 WebSocket
|
|
|
+ * @param {Object} options 连接选项
|
|
|
+ */
|
|
|
+ connect(options = {}) {
|
|
|
+ if (this.socket && this.isConnected) {
|
|
|
+ console.warn('WebSocket 已经连接');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const {
|
|
|
+ protocols = [],
|
|
|
+ onOpen = null,
|
|
|
+ onClose = null,
|
|
|
+ onError = null,
|
|
|
+ autoReconnect = true,
|
|
|
+ maxReconnectAttempts = 5,
|
|
|
+ reconnectInterval = 3000,
|
|
|
+ baseURL = "",
|
|
|
+ url = "",
|
|
|
+ } = options;
|
|
|
+
|
|
|
+ this.url = `${baseURL || process.env.VITE_APP_WS_URL}${url}`;
|
|
|
+
|
|
|
+ this.autoReconnect = autoReconnect;
|
|
|
+ this.maxReconnectAttempts = maxReconnectAttempts;
|
|
|
+ this.reconnectInterval = reconnectInterval;
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.socket = new WebSocket(url, protocols);
|
|
|
+
|
|
|
+ this.socket.onopen = (event) => {
|
|
|
+ console.log('WebSocket 连接成功');
|
|
|
+ this.isConnected = true;
|
|
|
+ this.reconnectAttempts = 0;
|
|
|
+
|
|
|
+ if (onOpen) onOpen(event);
|
|
|
+ this.trigger('open', event);
|
|
|
+ };
|
|
|
+
|
|
|
+ this.socket.onmessage = (event) => {
|
|
|
+ try {
|
|
|
+ const data = JSON.parse(event.data);
|
|
|
+ this.trigger('message', data);
|
|
|
+ } catch (error) {
|
|
|
+ // 如果不是 JSON 数据,直接传递原始数据
|
|
|
+ this.trigger('message', event.data);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ this.socket.onclose = (event) => {
|
|
|
+ console.log('WebSocket 连接关闭', event.code, event.reason);
|
|
|
+ this.isConnected = false;
|
|
|
+
|
|
|
+ if (onClose) onClose(event);
|
|
|
+ this.trigger('close', event);
|
|
|
+
|
|
|
+ // 自动重连
|
|
|
+ if (this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
|
+ this.scheduleReconnect();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ this.socket.onerror = (error) => {
|
|
|
+ console.error('WebSocket 错误:', error);
|
|
|
+
|
|
|
+ if (onError) onError(error);
|
|
|
+ this.trigger('error', error);
|
|
|
+ };
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('创建 WebSocket 连接失败:', error);
|
|
|
+ this.trigger('error', error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送消息
|
|
|
+ * @param {*} data 要发送的数据
|
|
|
+ */
|
|
|
+ send(data) {
|
|
|
+ if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
|
|
+ console.error('WebSocket 未连接,无法发送消息');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const message = typeof data === 'string' ? data : JSON.stringify(data);
|
|
|
+ this.socket.send(message);
|
|
|
+ return true;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('发送消息失败:', error);
|
|
|
+ this.trigger('error', error);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 关闭连接
|
|
|
+ * @param {number} code 关闭代码
|
|
|
+ * @param {string} reason 关闭原因
|
|
|
+ */
|
|
|
+ close(code = 1000, reason = '正常关闭') {
|
|
|
+ this.autoReconnect = false;
|
|
|
+
|
|
|
+ if (this.reconnectTimer) {
|
|
|
+ clearTimeout(this.reconnectTimer);
|
|
|
+ this.reconnectTimer = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.socket) {
|
|
|
+ this.socket.close(code, reason);
|
|
|
+ this.socket = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重新连接
|
|
|
+ */
|
|
|
+ reconnect() {
|
|
|
+ if (this.url) {
|
|
|
+ this.close();
|
|
|
+ this.connect(this.url, {
|
|
|
+ autoReconnect: this.autoReconnect,
|
|
|
+ maxReconnectAttempts: this.maxReconnectAttempts,
|
|
|
+ reconnectInterval: this.reconnectInterval
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 安排重连
|
|
|
+ */
|
|
|
+ scheduleReconnect() {
|
|
|
+ if (this.reconnectTimer) {
|
|
|
+ clearTimeout(this.reconnectTimer);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.reconnectAttempts++;
|
|
|
+ const delay = this.reconnectInterval * Math.pow(2, this.reconnectAttempts - 1); // 指数退避
|
|
|
+
|
|
|
+ console.log(`将在 ${delay}ms 后尝试重连 (尝试 ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
|
|
|
+
|
|
|
+ this.reconnectTimer = setTimeout(() => {
|
|
|
+ this.reconnect();
|
|
|
+ }, delay);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 注册事件处理器
|
|
|
+ * @param {string} event 事件名称
|
|
|
+ * @param {Function} handler 处理函数
|
|
|
+ */
|
|
|
+ on(event, handler) {
|
|
|
+ if (!this.messageHandlers.has(event)) {
|
|
|
+ this.messageHandlers.set(event, new Set());
|
|
|
+ }
|
|
|
+ this.messageHandlers.get(event).add(handler);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 取消事件处理器
|
|
|
+ * @param {string} event 事件名称
|
|
|
+ * @param {Function} handler 处理函数
|
|
|
+ */
|
|
|
+ off(event, handler) {
|
|
|
+ if (this.messageHandlers.has(event)) {
|
|
|
+ this.messageHandlers.get(event).delete(handler);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 触发事件
|
|
|
+ * @param {string} event 事件名称
|
|
|
+ * @param {*} data 事件数据
|
|
|
+ */
|
|
|
+ trigger(event, data) {
|
|
|
+ if (this.messageHandlers.has(event)) {
|
|
|
+ this.messageHandlers.get(event).forEach(handler => {
|
|
|
+ try {
|
|
|
+ handler(data);
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`事件 ${event} 的处理函数执行错误:`, error);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取连接状态
|
|
|
+ */
|
|
|
+ getReadyState() {
|
|
|
+ return this.socket ? this.socket.readyState : WebSocket.CLOSED;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 创建单例实例
|
|
|
+const webSocketService = new WebSocketService();
|
|
|
+
|
|
|
+export default webSocketService;
|