import { ref, onMounted, onUnmounted } from 'vue'; import { LRUCache } from 'lru-cache'; import dayjs from 'dayjs' import { COMMAND } from '~/utils/commandTypes' let heartbeatTimer: NodeJS.Timeout; // 定义 WebSocket Composable 的返回类型 interface WebSocketInstance { ws: Ref status: Ref<'connecting' | 'open' | 'error' | 'closed'> messages: Ref<{ data: string; timestamp: number }[]> send: (name: string, json: object, token: string, cmdid: string) => void onMessage: (callback: (data: string) => void) => () => void connect: () => void close: () => void } let instance: WebSocketInstance | null = null; export function useWebSocket() { if (instance) { return instance; } const ws = ref(null); const messages = ref<{ data: string; timestamp: number }[]>([]); const eventCallbacks: Array<(data: string) => void> = [] // 存储onMessage 回调 const status = ref<'connecting' | 'open' | 'error' | 'closed'>('closed'); // const pendingMessages = ref([]); // 存储 { msgId: timestamp } // const receivedMessages = new Map(); // 使用 Map 更方便记录时间 // 清理超过 5 分钟的消息 ID // const CLEANUP_INTERVAL = 5 * 60 * 1000; // 5 分钟 const EXPIRE_TIME = 5 * 60 * 1000;// 5 分钟 const reconnectAttempts = ref(0); // const maxReconnectAttempts = 5; const isConnected = computed(() => { return status.value === 'open' }); const isConnecting = computed(() => { return status.value === 'connecting' }); const config = useRuntimeConfig(); const connectionTimeout = config.public.apiTimeout || 5000; // 5秒连接超时 const cache = new LRUCache({ max: 1000, // 最多 1000 个条目 ttl: EXPIRE_TIME // 5 分钟过期 }); // 初始化连接 const connect = () => { if (isConnected.value || isConnecting.value) return; status.value = 'connecting'; ws.value = new WebSocket(config.public.wsUrl, "json"); ws.value.onopen = () => { status.value = 'open'; reconnectAttempts.value = 0; // retryPendingMessages(); }; ws.value.onmessage = (event) => { if (!event.data) { return; } const json = JSON.parse(event.data); const evtType = json.type; // 连接响应不做处理 if (evtType === COMMAND.PONG || evtType === COMMAND.CONNECTED) { if (evtType === COMMAND.CONNECTED) { } return; } const msgId = `${evtType}-${json.dateTime}`; if (evtType !== COMMAND.SELECT_HANDLE) { // console.log("Event Data:", event.data); } // const now = Date.now(); // 重复ID不做处理(排除选择事件) if (cache.has(msgId) && (evtType !== COMMAND.SELECT_HANDLE && evtType !== COMMAND.DRAG_ELEMENT && evtType !== COMMAND.MAP_RANGE_REAL)) { console.warn('Duplicate message ignored:', msgId, json); return; } cache.set(msgId, true); eventCallbacks.forEach(cb => cb(event.data)); if (messages.value.length > 100) messages.value.shift(); messages.value.push({ data: event.data, timestamp: Date.now() }); }; ws.value.onerror = (error) => { status.value = 'error'; // console.error('WebSocket发生错误:', error); // // 重试逻辑 // if (reconnectAttempts.value < maxReconnectAttempts) { // setTimeout(connect, 1000 * reconnectAttempts.value); // reconnectAttempts.value++; // } console.error('🚨 WebSocket 错误:', error) ws.value?.close() }; ws.value.onclose = (event) => { status.value = 'closed'; if (!event.wasClean) { // 非正常关闭时尝试重连 setTimeout(connect, 3000); } console.log("连接关闭:", event) }; } // 等待连接就绪 const waitForConnection = () => { return new Promise((resolve, reject) => { if (isConnected.value) { resolve(true); return; } console.log("Timeout:", connectionTimeout); // 设置连接超时 const timeout = setTimeout(() => { reject(new Error('连接超时')); }, connectionTimeout); // 监听连接状态变化 const watchHandle = watch(isConnected, (newVal) => { if (newVal) { clearTimeout(timeout); watchHandle(); // 取消监听 resolve(true); } }, { immediate: true }); }); }; // 发送消息 const send = async (name: string, json: object, token: string = '', cmdid: string = '') => { const msg = { type: name, drawerToken: token || localStorage.getItem(cmdid) || '', data: json, cmdid: cmdid } try { // 等待连接就绪 await waitForConnection(); // const ts = dayjs().unix(); // console.log("发送命令1:", msg); // const msgId = `${name}-${msg.drawerToken}-${ts}`; // if (cache.has(msgId)) { // return; // } // console.log("发送命令2:", msg); if (name !== COMMAND.PING && name !== COMMAND.MOUSE_MOVE) { // console.log("发送消息:", msg); } // cache.set(msgId, ts); ws.value.send(JSON.stringify(msg)); } catch (error) { console.error('消息发送失败:', error); // 添加到待发队列 // pendingMessages.value.push(JSON.stringify({ type: name, data: json })); // throw error; } } // 重发待处理消息 const retryPendingMessages = () => { // while (pendingMessages.value.length > 0) { // const message = pendingMessages.value.shift(); // let jsonData = JSON.parse(message); // send(jsonData.type, jsonData.data); // } }; const startHeartbeat = () => { heartbeatTimer = setInterval(() => { if (ws.value?.readyState === WebSocket.OPEN) { send(COMMAND.PING, {}); } }, 10000) } const onMessage = (callback: any) => { eventCallbacks.push(callback); return () => { const index = eventCallbacks.indexOf(callback); if (index > -1) { eventCallbacks.splice(index, 1); } } } // setInterval(() => { // const now = Date.now(); // for (const [msgId, timestamp] of receivedMessages) { // if (now - timestamp > EXPIRE_TIME) { // receivedMessages.delete(msgId); // } // } // }, CLEANUP_INTERVAL); // const reconnect = ()=>{ // setTimeout(()=>{ // connect(); // },50000) // } // 手动关闭 const close = () => { if (ws.value) { ws.value.close() } } onMounted(() => { if (status.value === 'closed') { connect(); } startHeartbeat(); }); onUnmounted(() => { // if (heartbeatTimer) { // clearInterval(heartbeatTimer); // }; // socket.value?.close(); }); // return { message, status, send, connect, waitForConnection } instance = { ws, status, messages, send, onMessage, connect, close } // 初始连接 if (typeof window !== 'undefined') { window.addEventListener('load', connect) } return instance; }