import { getWebSocketBaseUrl } from '../api' /** * WebSocket 管理器 * 支持多个 WebSocket 连接,自动管理鉴权、心跳、重连 */ class SocketManager { constructor(options = {}) { this.serviceName = options.serviceName || 'unknown' this.baseUrl = 'ws://127.0.0.1:8080' // 占位符,等 connect() 时再获取真实地址 this.token = null this.socket = null this.heartbeatTimer = null this.reconnectTimer = null this.reconnectInterval = options.reconnectInterval || 3000 this.heartbeatInterval = options.heartbeatInterval || 30000 this.maxReconnectAttempts = options.maxReconnectAttempts || 5 this.reconnectAttempts = 0 // 状态 this.isConnected = false this.isAuthed = false this.isClosing = false // 标记是否主动关闭 // 事件处理器 this.eventHandlers = { 'connect': [], 'disconnect': [], 'auth_success': [], 'auth_fail': [], 'error': [], 'message': [] // 通用消息处理 } // 子类可覆盖的消息类型处理 this.messageHandlers = {} } /** * 连接到 WebSocket 服务器 */ async connect(token, path) { this.token = token this.path = path this.reconnectAttempts = 0 this.isClosing = false // 重置关闭状态,允许重连 // 异步获取真实 WebSocket 地址(等待环境检测完成) this.baseUrl = await getWebSocketBaseUrl() console.log(`[${this.serviceName}] WebSocket base URL: ${this.baseUrl}`) this._doConnect() } _doConnect() { // 如果已有连接且已连接,不重复连接 if (this.socket && this.isConnected) { console.log(`[${this.serviceName}] Already connected (${this.isConnected}), skip reconnect`) return } console.log(`[${this.serviceName}] _doConnect called, clearing old socket`) // 清理旧连接 if (this.socket) { this.socket = null } this.isConnected = false const url = `${this.baseUrl}${this.path}?token=Bearer_${this.token}` console.log(`[${this.serviceName}] Connecting to ${url}`) // UniApp: connectSocket 是异步的,需要处理错误 try { this.socket = uni.connectSocket({ url, fail: (err) => { console.error(`[${this.serviceName}] connectSocket fail:`, err) this._emit('error', { code: 'CONNECT_FAILED', message: err.errMsg || '连接失败' }) } }) if (!this.socket) { console.error(`[${this.serviceName}] socket is null`) return } console.log(`[${this.serviceName}] SocketTask created, checking methods:`, Object.keys(this.socket)) this._setupListeners() } catch (err) { console.error(`[${this.serviceName}] Exception during connect:`, err) } } _setupListeners() { // 检查 socket 是否有效 if (!this.socket) { console.error(`[${this.serviceName}] socket is null in _setupListeners`) return } // UniApp SocketTask API: onOpen/onClose/onError/onMessage 是设置回调的方法 // 也可能是事件名是 'open', 'close', 'error', 'message' const socket = this.socket const self = this // 连接打开 if (typeof socket.onOpen === 'function') { socket.onOpen(function() { console.log(`[${self.serviceName}] WebSocket connected`) self.isConnected = true // 清除重连计时器 if (self.reconnectTimer) { clearTimeout(self.reconnectTimer) self.reconnectTimer = null } self.reconnectAttempts = 0 self._emit('connect') }) } else if (typeof socket.onopen === 'function') { // 标准 WebSocket 风格 socket.onopen(function() { console.log(`[${self.serviceName}] WebSocket connected`) self.isConnected = true // 清除重连计时器 if (self.reconnectTimer) { clearTimeout(self.reconnectTimer) self.reconnectTimer = null } self.reconnectAttempts = 0 self._emit('connect') }) } else { console.warn(`[${self.serviceName}] Unknown socket API, socket keys:`, Object.keys(socket)) } // 接收消息 if (typeof socket.onMessage === 'function') { socket.onMessage(function(event) { const data = JSON.parse(event.data) self._handleMessage(data) }) } else if (typeof socket.onmessage === 'function') { socket.onmessage(function(event) { const data = JSON.parse(event.data) self._handleMessage(data) }) } // 连接关闭 if (typeof socket.onClose === 'function') { socket.onClose(function() { console.log(`[${self.serviceName}] WebSocket closed`) self._cleanup() self._emit('disconnect') self._tryReconnect() }) } else if (typeof socket.onclose === 'function') { socket.onclose(function() { console.log(`[${self.serviceName}] WebSocket closed`) self._cleanup() self._emit('disconnect') self._tryReconnect() }) } // 连接错误 if (typeof socket.onError === 'function') { socket.onError(function(err) { console.error(`[${self.serviceName}] WebSocket error:`, err) self._emit('error', err) }) } else if (typeof socket.onerror === 'function') { socket.onerror(function(err) { console.error(`[${self.serviceName}] WebSocket error:`, err) self._emit('error', err) }) } } _handleMessage(data) { // 触发通用消息事件 this._emit('message', data) // 根据消息类型处理 const { type, action } = data // 1. 鉴权响应(通用) if (type === 'auth_response') { if (data.success) { this.isAuthed = true this._emit('auth_success', data) this._startHeartbeat() } else { this.isAuthed = false this._emit('auth_fail', data) this.close() } return } // 2. 心跳响应(通用) if (type === 'pong') { console.log(`[${this.serviceName}] Heartbeat received`) return } // 3. 错误响应(通用) if (type === 'error') { this._emit('error', data) return } // 4. 服务特定消息类型处理 const handler = this.messageHandlers[type] || this.messageHandlers[action] if (handler) { handler(data) } } /** * 发送消息 */ send(data) { if (!this.socket || !this.isConnected) { console.warn(`[${this.serviceName}] Socket not connected`) return false } // UniApp: socket.send({ data, success, fail }) if (typeof this.socket.send === 'function') { this.socket.send({ data: JSON.stringify(data), fail: (err) => { console.error(`[${this.serviceName}] send fail:`, err) } }) } else { console.warn(`[${this.serviceName}] socket.send is not a function`) } return true } /** * 发送心跳 */ _startHeartbeat() { this._stopHeartbeat() this.heartbeatTimer = setInterval(() => { if (this.isConnected) { this.send({ action: 'ping' }) } }, this.heartbeatInterval) } _stopHeartbeat() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer) this.heartbeatTimer = null } } /** * 重连机制 */ _tryReconnect() { if (this.isClosing) { console.log(`[${this.serviceName}] Closing intentionally, skip reconnect`) return } if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.warn(`[${this.serviceName}] Max reconnect attempts reached`) this._emit('error', { code: 'RECONNECT_FAILED', message: '重连次数已达上限' }) return } this.reconnectAttempts++ console.log(`[${this.serviceName}] Auto reconnecting... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`) this.reconnectTimer = setTimeout(() => { this._doConnect() }, this.reconnectInterval) } _cleanup() { this._stopHeartbeat() if (this.reconnectTimer) { clearTimeout(this.reconnectTimer) this.reconnectTimer = null } this.isConnected = false this.isAuthed = false } /** * 关闭连接 */ close() { this.isClosing = true this._cleanup() if (this.socket) { // UniApp: socket.close() 可能不存在,用 complete 代替 if (typeof this.socket.close === 'function') { this.socket.close() } else if (typeof this.socket.complete === 'function') { this.socket.complete() } this.socket = null } } // ===== 事件系统 ===== on(event, handler) { if (this.eventHandlers[event]) { this.eventHandlers[event].push(handler) } return () => this.off(event, handler) // 返回取消订阅函数 } off(event, handler) { if (this.eventHandlers[event]) { this.eventHandlers[event] = this.eventHandlers[event].filter(h => h !== handler) } } _emit(event, data) { if (this.eventHandlers[event]) { this.eventHandlers[event].forEach(handler => handler(data)) } } // ===== 订阅特定消息类型 ===== /** * 注册特定消息类型的处理器 * @param {string} type 消息类型或 action * @param {function} handler 处理函数 */ registerHandler(type, handler) { this.messageHandlers[type] = handler } } /** 导出 */ export default SocketManager