import {checkDev} from "@/utils/utils"; export class WebSocketManager { constructor(url) { this.url = url; this.socket = null; this.isConnected = false; this.isAuthenticated = false; // 是否已认证 this.reconnectAttempts = 0; this.maxReconnectAttempts = 5; this.reconnectDelay = 1000; this.messageHandlers = []; this.pingInterval = null; this.cachedToken = null; // 缓存的token } /** * 获取当前用户的token * @returns {string|null} */ getToken() { // 从本地存储获取token(不使用缓存,确保获取最新token) let token = uni.getStorageSync('token'); if (token) { // 移除Bearer前缀(如果有) token = token.replace('Bearer ', ''); this.cachedToken = token; } return token || null; } /** * 清除缓存的token(登录后需要调用,以获取最新token) */ clearCachedToken() { this.cachedToken = null; } /** * 重新绑定用户(登录后调用) * 会先清理旧连接,再绑定新连接 */ rebindUser() { const userId = uni.getStorageSync('user_id'); if (!userId) { console.warn('重新绑定用户失败:user_id不存在'); return false; } // 清除缓存的token,确保获取最新token this.clearCachedToken(); if (this.isConnected) { // 如果已连接,直接bind(Go后端会自动清理旧连接) // 注意:不在这里调用unbind,因为unbind会关闭当前连接 return this.bindUser(userId, true); } else { // 如果未连接,先连接再绑定 this.connect(); // 连接成功后会在 onOpen 中自动绑定(会自动清理旧连接) return true; } } connect() { if (this.isConnected) return; console.log('正在连接WebSocket...'); this.socket = uni.connectSocket({ url: this.url, success: () => { console.log('WebSocket连接成功'); }, fail: (err) => { console.error('WebSocket连接失败:', err); this.handleReconnect(); } }); this.socket.onOpen(() => { console.log('WebSocket连接已打开'); this.isConnected = true; this.reconnectAttempts = 0; // 开始心跳检测 this.startHeartbeat(); // 绑定当前用户(使用正确的key:user_id) const userId = uni.getStorageSync('user_id'); if (userId) { // 连接打开后立即绑定,Go后端会自动清理旧连接 this.bindUser(userId, true); } }); this.socket.onMessage((res) => { try { const data = JSON.parse(res.data); console.log('收到消息:', data); this.handleMessage(data); } catch (e) { console.error('解析消息失败:', e); } }); this.socket.onClose((res) => { console.log('WebSocket连接关闭:', res); this.isConnected = false; this.stopHeartbeat(); this.handleReconnect(); }); this.socket.onError((err) => { console.error('WebSocket错误:', err); }); } /** * 解绑用户(清理旧连接) * 注意:此方法会关闭当前连接,只应在需要完全断开时使用 * @param {string} userId 用户ID(格式:user-xxx 或 数字) * @returns {boolean} 是否发送成功 */ unbindUser(userId) { const token = this.getToken(); if (!token) { console.warn('解绑用户失败:token不存在,跳过解绑'); return false; } // 确保userId格式正确(如果是数字,添加user-前缀) const normalizedUserId = userId.toString().startsWith('user-') ? userId : `user-${userId}`; console.log('发送unbind请求,清理旧连接:', normalizedUserId); return this.send({ request_type: 'unbind', user_type: 'user', sender_user_id: normalizedUserId, token: token }, false); // unbind不需要再次添加token } /** * 绑定用户(携带token进行认证) * 在绑定前会自动清理旧连接(通过Go后端的clean_old_connections功能) * @param {string} userId 用户ID(可以是数字或user-xxx格式) * @param {boolean} cleanOldConnections 是否清理旧连接(默认true) */ bindUser(userId, cleanOldConnections = true) { const token = this.getToken(); if (!token) { console.error('绑定用户失败:token不存在'); return false; } // 确保userId格式正确(统一为user-xxx格式) // 如果userId已经是user-xxx格式,保持不变;如果是数字,添加user-前缀 const normalizedUserId = userId.toString().startsWith('user-') ? userId : `user-${userId}`; // 注意:不在bind前发送unbind,因为unbind会关闭当前连接 // 清理旧连接的工作交给Go后端的clean_old_connections功能处理 // 发送bind请求,添加clean_old_connections标志告诉Go后端清理旧连接 console.log('发送bind请求,绑定新连接:', normalizedUserId, 'cleanOldConnections:', cleanOldConnections); return this.send({ request_type: 'bind', user_type: 'user', sender_user_id: normalizedUserId, token: token, clean_old_connections: cleanOldConnections // 告诉Go后端清理旧连接 }); } /** * 发送消息(自动携带token) * @param {Object} message 消息对象 * @param {boolean} requireAuth 是否需要认证(默认true) * @returns {boolean} 是否发送成功 */ send(message, requireAuth = true) { if (!this.isConnected) { console.error('WebSocket未连接'); return false; } try { // 如果需要认证且消息中没有token,自动添加token if (requireAuth && !message.token && message.request_type !== 'ping') { const token = this.getToken(); if (token) { message.token = token; } } const messageStr = JSON.stringify(message); this.socket.send({ data: messageStr, success: () => { console.log('消息发送成功:', message); }, fail: (err) => { console.error('消息发送失败:', err); } }); return true; } catch (err) { console.error('发送消息出错:', err); return false; } } addMessageHandler(handler) { this.messageHandlers.push(handler); } removeMessageHandler(handler) { const index = this.messageHandlers.indexOf(handler); if (index !== -1) { this.messageHandlers.splice(index, 1); } } /** * 获取用户ID并绑定客户端(携带token) * @returns {string|null} 用户ID */ getUserIdAndBindClientId() { const userId = uni.getStorageSync('user_id'); // 实际项目中从存储获取 const token = this.getToken(); if (userId == null || userId === 'user-' || userId === '') { // 等待10秒后重试 setTimeout(() => { this.getUserIdAndBindClientId(); }, 10000) } else if (!token) { // token不存在,等待后重试 console.warn('token不存在,等待后重试绑定'); setTimeout(() => { this.getUserIdAndBindClientId(); }, 5000) } else { // 直接发送bind请求,Go后端会自动清理旧连接 // 注意:不在这里调用unbind,因为unbind会关闭当前连接 const bindMessage = { request_type: 'bind', user_type: 'user', sender_user_id: `user-${userId}`, token: token, clean_old_connections: true // 告诉Go后端清理旧连接 }; this.send(bindMessage); } return userId; } handleMessage(data) { // 处理认证响应 if (data.auth_status !== undefined) { if (data.auth_status === 'success') { console.log('WebSocket认证成功'); this.isAuthenticated = true; } else if (data.auth_status === 'failed') { console.error('WebSocket认证失败:', data.message); this.isAuthenticated = false; // 清除缓存的token this.cachedToken = null; } else if (data.auth_status === 'token_expired') { console.warn('Token已过期,需要重新认证'); this.isAuthenticated = false; this.cachedToken = null; // 通知应用层token过期 uni.$emit('ws-token-expired'); // 尝试重新绑定(会自动清理旧连接) this.getUserIdAndBindClientId(); } return; } // 如果对象中有clientId则给websocket发送绑定请求 if (data.clientId) { this.getUserIdAndBindClientId(data.clientId); } // 处理聊天消息或其他消息 this.messageHandlers.forEach(handler => { try { handler(data); } catch (e) { console.error('消息处理出错:', e); } }); } startHeartbeat() { this.pingInterval = setInterval(() => { if (this.isConnected) { this.send({type: 'ping'}); } }, 300000); } stopHeartbeat() { if (this.pingInterval) { clearInterval(this.pingInterval); this.pingInterval = null; } } handleReconnect() { if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.log('已达到最大重连次数'); return; } setTimeout(() => { this.reconnectAttempts++; this.reconnectDelay = Math.min(this.reconnectDelay * 2, 30000); console.log(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`); this.connect(); }, this.reconnectDelay); } disconnect() { if (this.socket) { this.socket.close(); this.socket = null; } this.isConnected = false; this.stopHeartbeat(); } } // 全局WebSocket实例 // export const webSocketManager = new WebSocketManager('ws://127.0.0.1:12080/ws'); // const wsUrl = checkDev('dev')? 'ws://127.0.0.1:12080/ws':'wss://api.ws.g.xiaokang88.com/ws'; const wsUrl = checkDev('dev')? 'ws://127.0.0.1:12080/ws':'wss://xk.ws.nailaoyun.cn/ws'; export const webSocketManager = new WebSocketManager(wsUrl);