import { ref, reactive, onUnmounted, nextTick } from "vue"; import { onHide } from "@dcloudio/uni-app"; /** * useWebSocket - 封装适用于 uni-app App 平台的 WebSocket Hook * 支持自动重连、心跳检测、事件监听、消息记录等 * * @param {string} url WebSocket 服务器地址(如 wss://example.com/ws) * @param {object} options 可选配置项 * @returns {object} 提供连接状态、消息、发送、断开等方法 */ export default function useWebSocket(url, options = {}) { // 默认配置项 const defaultOptions = { protocols: [], // 子协议 autoConnect: true, // 是否自动连接 reconnect: true, // 是否断线重连 reconnectInterval: 3000, // 重连间隔(ms) maxReconnectAttempts: 3, // 最大重连次数 heartbeat: true, // 是否启用心跳 heartbeatInterval: 30000, // 心跳间隔(ms) heartbeatMessage: "ping", // 心跳消息内容 ...options, }; // 响应式状态 const socketTask = ref(null); // WebSocket 实例 const isConnected = ref(false); // 当前是否已连接 const isConnecting = ref(false); // 当前是否正在连接 const reconnectCount = ref(0); // 当前重连次数 const lastMessage = ref(null); // 最后一条接收到的消息 const messageHistory = ref([]); // 所有接收到的历史消息(上限100) // 事件监听器 const listeners = reactive({ open: [], message: [], error: [], close: [], }); // 控制标志与定时器 let reconnectTimer = null; let heartbeatTimer = null; let heartbeatTimeoutTimer = null; let isManuallyClosed = false; // 是否为手动断开(用于判断是否执行自动重连) /** * 创建 WebSocket 连接 */ const connect = () => { if (isConnected.value || isConnecting.value) { console.warn("WebSocket 已连接或正在连接中"); return; } isConnecting.value = true; isManuallyClosed = false; // 发起连接 socketTask.value = uni.connectSocket({ url, protocols: defaultOptions.protocols, success: () => { console.log("WebSocket 连接请求已发出"); nextTick(() => { setupListeners(); }); }, fail: (err) => { isConnecting.value = false; console.error("WebSocket 连接请求失败:", err); handleError(err); }, }); }; /** * 绑定 socket 事件监听器 */ const setupListeners = () => { console.log("socketTask.value", socketTask.value); if (!socketTask.value) return; socketTask.value.onOpen((res) => { console.log("WebSocket 已连接"); isConnected.value = true; isConnecting.value = false; reconnectCount.value = 0; // 启动心跳机制 if (defaultOptions.heartbeat) { startHeartbeat(); } listeners.open.forEach((cb) => cb(res)); }); socketTask.value.onMessage((res) => { const message = { data: res.data, timestamp: Date.now(), }; lastMessage.value = message; messageHistory.value.push(message); // 限制消息历史上限为100条 if (messageHistory.value.length > 100) { messageHistory.value.shift(); } listeners.message.forEach((cb) => cb(message)); }); socketTask.value.onError((err) => { console.error("WebSocket 发生错误:", err); handleError(err); }); socketTask.value.onClose((res) => { console.log("WebSocket 已关闭", res); isConnected.value = false; isConnecting.value = false; stopHeartbeat(); listeners.close.forEach((cb) => cb(res)); // 自动重连(仅当未手动断开时) if ( !isManuallyClosed && defaultOptions.reconnect && reconnectCount.value < defaultOptions.maxReconnectAttempts ) { attemptReconnect(); } }); }; /** * 主动断开连接 */ const disconnect = () => { isManuallyClosed = true; clearReconnectTimer(); stopHeartbeat(); if (socketTask.value) { socketTask.value.close({ code: 1000, reason: "客户端主动断开", }); } isConnected.value = false; isConnecting.value = false; reconnectCount.value = 0; socketTask.value = null; }; /** * 发送消息 * @param {string|object} data 发送的数据(支持对象自动转 JSON) * @returns {Promise} */ const send = (data) => { // console.log('isConnected', isConnected.value) if (!isConnected.value || !socketTask.value) { return Promise.reject(new Error("WebSocket 未连接")); } return new Promise((resolve, reject) => { try { const msg = typeof data === "object" ? JSON.stringify(data) : data; socketTask.value.send({ data: msg, success: resolve, fail: reject, }); } catch (err) { console.error("发送数据格式错误:", err); reject(err); } }); }; /** * 尝试自动重连 */ const attemptReconnect = () => { if (reconnectCount.value >= defaultOptions.maxReconnectAttempts) { console.warn("已达到最大重连次数,停止重连"); return; } reconnectCount.value++; console.log(`尝试第 ${reconnectCount.value} 次重连...`); reconnectTimer = setTimeout(() => { connect(); }, defaultOptions.reconnectInterval); }; /** * 清除重连定时器 */ const clearReconnectTimer = () => { if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; } }; /** * 启动心跳检测 */ const startHeartbeat = () => { stopHeartbeat(); heartbeatTimer = setInterval(() => { if (!isConnected.value) return; send(defaultOptions.heartbeatMessage) .then(() => { clearTimeout(heartbeatTimeoutTimer); heartbeatTimeoutTimer = setTimeout(() => { console.warn("心跳无响应,断开连接"); socketTask.value?.close(); }, defaultOptions.heartbeatInterval + 2000); }) .catch((err) => { console.error("心跳发送失败:", err); }); }, defaultOptions.heartbeatInterval); }; /** * 停止心跳定时器 */ const stopHeartbeat = () => { if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; } if (heartbeatTimeoutTimer) { clearTimeout(heartbeatTimeoutTimer); heartbeatTimeoutTimer = null; } }; /** * 分发错误事件 * @param {any} error */ const handleError = (error) => { listeners.error.forEach((cb) => cb(error)); }; /** * 注册事件监听器 * @param {string} event 事件名 open/message/error/close * @param {Function} callback */ const on = (event, callback) => { if (listeners[event]) { listeners[event].push(callback); } }; /** * 移除事件监听器 * @param {string} event * @param {Function} callback */ const off = (event, callback) => { if (listeners[event]) { const index = listeners[event].indexOf(callback); if (index > -1) { listeners[event].splice(index, 1); } } }; /** * 清除消息历史 */ const clearHistory = () => { messageHistory.value = []; lastMessage.value = null; }; /** * 手动执行重连(带清理) */ const reconnect = () => { disconnect(); nextTick(() => { reconnectCount.value = 0; isManuallyClosed = false; connect(); }); }; // 页面加载时自动连接 if (defaultOptions.autoConnect) { connect(); } // 页面离开时时断开连接 // onHide(() => { // disconnect() // }) // 对外暴露状态与操作方法 return { isConnected, isConnecting, reconnectCount, lastMessage, messageHistory, connect, disconnect, send, reconnect, clearHistory, on, off, }; }