// socket.js - 优化后的全局唯一socket管理模块(支付宝小程序兼容版) // 事件总线实现 class EventEmitter { constructor() { this.events = {}; } on(event, callback) { if (!this.events[event]) { this.events[event] = []; } this.events[event].push(callback); return () => this.off(event, callback); } off(event, callback) { if (!this.events[event]) return; if (callback) { this.events[event] = this.events[event].filter(cb => cb !== callback); } else { this.events[event] = []; } } emit(event, data) { if (!this.events[event]) return; this.events[event].forEach(callback => callback(data)); } } // 创建全局事件总线 const eventBus = new EventEmitter(); // 唯一的socket实例 let socketTask = null; let connectTimer = null; let heartbeatTimer = null; let reconnectCount = 0; let isConnected = false; let lastUrl = ''; let isConnecting = false; let isAlipayMiniProgram = false; // 支付宝小程序特有状态 let globalEventHandlersRegistered = false; // 初始化时检测环境 (function detectEnvironment() { try { const systemInfo = uni.getSystemInfoSync(); isAlipayMiniProgram = systemInfo.platform === 'alipay' || systemInfo.environment === 'alipay' || systemInfo .app == 'alipay'; console.log('当前运行平台===', systemInfo); console.log(`当前运行环境: ${isAlipayMiniProgram ? '支付宝小程序' : '其他平台'}`); } catch (e) { console.error('检测运行环境失败:', e); isAlipayMiniProgram = false; } })(); const MAX_RECONNECT_COUNT = 5; const RECONNECT_DELAY = 3000; // 3秒重连间隔 const HEARTBEAT_INTERVAL = 10000; // 10秒心跳间隔 // 连接socket function connectSocket(url = 'wss://store.sxczgkj.com/shopser') { // 如果已经连接到相同URL,直接返回 if (isConnected && lastUrl === url) { console.log('socket已连接到:', url); return Promise.resolve(); } // 如果正在连接中,返回pending状态的Promise if (isConnecting) { console.log('socket连接中,等待连接完成...'); return new Promise((resolve, reject) => { const unsubscribeConnected = eventBus.on('connected', () => { unsubscribeConnected(); unsubscribeError(); resolve(); }); const unsubscribeError = eventBus.on('error', (err) => { unsubscribeConnected(); unsubscribeError(); reject(err); }); }); } // 如果已有不同URL的连接,先关闭 if ((socketTask || globalEventHandlersRegistered) && lastUrl !== url) { console.log('关闭旧连接,准备连接新URL:', url); closeSocket(); } // 记录当前URL lastUrl = url; isConnecting = true; reconnectCount = 0; // 重置重连计数 console.log('开始连接socket:', url); return new Promise((resolve, reject) => { // 支付宝小程序特殊处理 if (isAlipayMiniProgram) { // 确保只注册一次全局事件处理程序 if (!globalEventHandlersRegistered) { registerGlobalEventHandlers(); globalEventHandlersRegistered = true; } // 调用连接方法 uni.connectSocket({ url, success: () => { console.log('socket连接请求已发送'); }, fail: (err) => { console.error('socket连接失败:', err); isConnecting = false; // 特殊处理:如果是"WebSocket已存在"错误,尝试重用现有连接 if (err.errMsg.includes('WebSocket 已存在')) { console.warn('检测到重复连接,尝试重用现有连接'); // 触发已连接事件(模拟连接成功) handleSocketOpen(null); resolve(); return; } eventBus.emit('error', err); handleReconnect(url); reject(err); } }); } else { // H5和其他平台使用socketTask对象方法 socketTask = uni.connectSocket({ url, success: () => { console.log('socket连接请求已发送'); }, fail: (err) => { console.error('socket连接失败:', err); isConnecting = false; eventBus.emit('error', err); handleReconnect(url); reject(err); } }); // 检查socketTask是否正确返回 if (!socketTask) { console.error('uni.connectSocket() 未返回 socketTask 对象'); isConnecting = false; eventBus.emit('error', new Error('无法创建socket连接')); handleReconnect(url); reject(new Error('无法创建socket连接')); return; } // 监听socket连接成功 if (socketTask.onOpen) { socketTask.onOpen((res) => { handleSocketOpen(res, resolve); }); } else { console.error('socketTask对象缺少onOpen方法'); isConnecting = false; eventBus.emit('error', new Error('socketTask对象不完整')); handleReconnect(url); reject(new Error('socketTask对象不完整')); } // 监听socket消息 if (socketTask.onMessage) { socketTask.onMessage((res) => { handleSocketMessage(res); }); } // 监听socket关闭 if (socketTask.onClose) { socketTask.onClose((res) => { handleSocketClose(res); }); } // 监听socket错误 if (socketTask.onError) { socketTask.onError((err) => { handleSocketError(err, reject); }); } } }); } // 注册支付宝小程序全局事件监听 function registerGlobalEventHandlers() { console.log('注册支付宝小程序全局socket事件监听'); // 连接成功 uni.onSocketOpen((res) => { console.log('支付宝小程序: socket连接成功'); handleSocketOpen(res); }); // 接收消息 uni.onSocketMessage((res) => { handleSocketMessage(res); }); // 连接关闭 uni.onSocketClose((res) => { handleSocketClose(res); }); // 连接错误 uni.onSocketError((err) => { handleSocketError(err); }); } // 处理socket连接成功 function handleSocketOpen(res, resolve) { console.log('socket连接成功:', res); reconnectCount = 0; isConnected = true; isConnecting = false; // 标记socket已创建(支付宝小程序无socketTask对象) if (isAlipayMiniProgram) { socketTask = true; // 使用布尔值标记连接状态 } eventBus.emit('connected'); const userInfo = uni.getStorageSync('cache_shop_user_info_key'); if (userInfo.id) { sendMessage({ operate_type: 'init', type: 'user_msg', user_id: userInfo.id, }); } startHeartbeat(); if (resolve) { resolve(); } } // 处理socket消息 function handleSocketMessage(res) { try { let data = JSON.parse(res.data); console.log('收到socket消息:', data); eventBus.emit('message', data); } catch (e) { console.error('解析socket消息失败:', e); eventBus.emit('error', e); } } // 处理socket关闭 function handleSocketClose(res) { console.log('socket已关闭:', res); isConnected = false; eventBus.emit('closed', res); stopHeartbeat(); // 仅在非支付宝小程序环境下重置socketTask if (!isAlipayMiniProgram) { socketTask = null; } handleReconnect(lastUrl); } // 处理socket错误 function handleSocketError(err, reject) { console.error('socket发生错误:', err); isConnected = false; isConnecting = false; eventBus.emit('error', err); stopHeartbeat(); // 仅在非支付宝小程序环境下重置socketTask if (!isAlipayMiniProgram) { socketTask = null; } handleReconnect(lastUrl); if (reject) { reject(err); } } // 发送消息 function sendMessage(data) { if (!isConnected) { console.error('socket未连接,无法发送消息'); // 尝试重连 connectSocket().then(() => { sendMessage(data); }).catch(err => { console.error('重连失败,无法发送消息:', err); }); return; } try { const messageData = JSON.stringify(data); if (isAlipayMiniProgram) { uni.sendSocketMessage({ data: messageData, success: () => { console.log('消息发送成功'); }, fail: (err) => { console.error('消息发送失败:', err); eventBus.emit('error', err); } }); } else { if (!socketTask) { console.error('socketTask不存在,无法发送消息'); return; } // 检查socketTask是否有send方法 if (socketTask.send) { socketTask.send({ data: messageData, success: () => { console.log('消息发送成功'); }, fail: (err) => { console.error('消息发送失败:', err); eventBus.emit('error', err); } }); } else { console.error('socketTask缺少send方法'); eventBus.emit('error', new Error('socketTask对象不完整')); } } } catch (e) { console.error('消息序列化失败:', e); eventBus.emit('error', e); } } // 发送心跳 function sendHeartbeat() { sendMessage({ type: 'ping_interval' }); } // 开始心跳 function startHeartbeat() { stopHeartbeat(); heartbeatTimer = setInterval(sendHeartbeat, HEARTBEAT_INTERVAL); } // 停止心跳 function stopHeartbeat() { if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; } } // 处理重连 function handleReconnect(url) { if (reconnectCount >= MAX_RECONNECT_COUNT) { console.error('达到最大重连次数,停止重连'); return; } // 检查是否需要重连(避免重复触发连接) if (isConnecting || isConnected) { return; } reconnectCount++; console.log(`准备第${reconnectCount}次重连,${RECONNECT_DELAY/1000}秒后...`); if (connectTimer) { clearTimeout(connectTimer); } connectTimer = setTimeout(() => { connectSocket(url); }, RECONNECT_DELAY); } // 关闭socket function closeSocket() { console.log('正在关闭socket连接...'); try { if (isAlipayMiniProgram) { uni.closeSocket({ success: () => { console.log('主动关闭socket成功'); }, fail: (err) => { console.error('主动关闭socket失败:', err); } }); } else { if (!socketTask) return; if (socketTask.close) { socketTask.close({ success: () => { console.log('主动关闭socket成功'); }, fail: (err) => { console.error('主动关闭socket失败:', err); } }); } else { console.error('socketTask缺少close方法'); isConnected = false; } } } catch (e) { console.error('关闭socket时发生异常:', e); isConnected = false; } stopHeartbeat(); if (connectTimer) { clearTimeout(connectTimer); connectTimer = null; } isConnected = false; isConnecting = false; // 仅在非支付宝小程序环境下重置socketTask if (!isAlipayMiniProgram) { socketTask = null; } // 重置支付宝小程序的全局事件标记 globalEventHandlersRegistered = false; } // 监听消息 function onMessage(callback) { return eventBus.on('message', callback); } // 监听连接状态 function onConnected(callback) { return eventBus.on('connected', callback); } // 监听断开状态 function onClosed(callback) { return eventBus.on('closed', callback); } // 监听错误 function onError(callback) { return eventBus.on('error', callback); } // 提供一个自动管理生命周期的组件选项 function createSocketMixin() { return { data() { return { socketUnsubscribes: [] }; }, beforeDestroy() { // 自动取消所有订阅 this.socketUnsubscribes.forEach(unsubscribe => unsubscribe()); this.socketUnsubscribes = []; }, methods: { // 订阅消息并自动管理生命周期 socketOnMessage(callback) { const unsubscribe = onMessage(callback); this.socketUnsubscribes.push(unsubscribe); return unsubscribe; }, socketOnConnected(callback) { const unsubscribe = onConnected(callback); this.socketUnsubscribes.push(unsubscribe); return unsubscribe; }, socketOnClosed(callback) { const unsubscribe = onClosed(callback); this.socketUnsubscribes.push(unsubscribe); return unsubscribe; }, socketOnError(callback) { const unsubscribe = onError(callback); this.socketUnsubscribes.push(unsubscribe); return unsubscribe; } } }; } // 导出状态检查函数 function isSocketConnected() { return isConnected; } // 导出连接状态 function getConnectionStatus() { return { isConnected, isConnecting, lastUrl, reconnectCount, platform: isAlipayMiniProgram ? 'alipay' : 'other' }; } export default { connectSocket, sendMessage, closeSocket, onMessage, onConnected, onClosed, onError, createSocketMixin, isSocketConnected, getConnectionStatus };