import { createPersistStore } from "@/store/createPersistStore"; import { Toast } from "antd-mobile"; import { useUserStore } from "./user"; // WebSocket消息类型 export interface WebSocketMessage { id: string; type: string; content: any; timestamp: number; sender?: string; receiver?: string; } // WebSocket连接状态 export enum WebSocketStatus { DISCONNECTED = "disconnected", CONNECTING = "connecting", CONNECTED = "connected", RECONNECTING = "reconnecting", ERROR = "error", } // WebSocket配置 interface WebSocketConfig { url: string; client: string; accountId: string; accessToken: string; autoReconnect: boolean; reconnectInterval: number; maxReconnectAttempts: number; } interface WebSocketState { // 连接状态 status: WebSocketStatus; ws: WebSocket | null; // 配置信息 config: WebSocketConfig | null; // 消息相关 messages: WebSocketMessage[]; unreadCount: number; // 重连相关 reconnectAttempts: number; reconnectTimer: NodeJS.Timeout | null; // 方法 connect: (config: Partial) => void; disconnect: () => void; sendMessage: (message: Omit) => void; sendCommand: (cmdType: string, data?: any) => void; clearMessages: () => void; markAsRead: () => void; reconnect: () => void; // 内部方法 _handleOpen: () => void; _handleMessage: (event: MessageEvent) => void; _handleClose: (event: CloseEvent) => void; _handleError: (event: Event) => void; _startReconnectTimer: () => void; _stopReconnectTimer: () => void; } // 默认配置 const DEFAULT_CONFIG: WebSocketConfig = { url: (import.meta as any).env?.VITE_API_WS_URL || "ws://localhost:8080", client: "kefu-client", accountId: "", accessToken: "", autoReconnect: true, reconnectInterval: 3000, maxReconnectAttempts: 5, }; export const useWebSocketStore = createPersistStore( (set, get) => ({ status: WebSocketStatus.DISCONNECTED, ws: null, config: null, messages: [], unreadCount: 0, reconnectAttempts: 0, reconnectTimer: null, // 连接WebSocket connect: (config: Partial) => { const currentState = get(); // 如果已经连接,先断开 if (currentState.ws) { currentState.disconnect(); } // 合并配置 const fullConfig: WebSocketConfig = { ...DEFAULT_CONFIG, ...config, }; // 获取用户信息 const { token, token2, user } = useUserStore.getState(); const accessToken = fullConfig.accessToken || token2 || token; if (!accessToken) { Toast.show({ content: "未找到有效的访问令牌", position: "top" }); return; } // 构建WebSocket URL const params = { client: fullConfig.client, accountId: fullConfig.accountId || user?.s2_accountId || "", accessToken: accessToken, t: Date.now().toString(), }; const wsUrl = fullConfig.url + "?" + new URLSearchParams(params).toString(); set({ status: WebSocketStatus.CONNECTING, config: fullConfig, }); try { const ws = new WebSocket(wsUrl); // 绑定事件处理器 ws.onopen = () => get()._handleOpen(); ws.onmessage = event => get()._handleMessage(event); ws.onclose = event => get()._handleClose(event); ws.onerror = event => get()._handleError(event); set({ ws }); console.log("WebSocket连接创建成功", wsUrl); } catch (error) { console.error("WebSocket连接失败:", error); set({ status: WebSocketStatus.ERROR }); Toast.show({ content: "WebSocket连接失败", position: "top" }); } }, // 断开连接 disconnect: () => { const currentState = get(); if (currentState.ws) { currentState.ws.close(); } currentState._stopReconnectTimer(); set({ status: WebSocketStatus.DISCONNECTED, ws: null, reconnectAttempts: 0, }); console.log("WebSocket连接已断开"); }, // 发送消息 sendMessage: (message: Omit) => { const currentState = get(); if ( currentState.status !== WebSocketStatus.CONNECTED || !currentState.ws ) { Toast.show({ content: "WebSocket未连接", position: "top" }); return; } const fullMessage: WebSocketMessage = { ...message, id: Date.now().toString(), timestamp: Date.now(), }; try { currentState.ws.send(JSON.stringify(fullMessage)); console.log("消息发送成功:", fullMessage); } catch (error) { console.error("消息发送失败:", error); Toast.show({ content: "消息发送失败", position: "top" }); } }, // 发送命令 sendCommand: (cmdType: string, data?: any) => { const currentState = get(); if ( currentState.status !== WebSocketStatus.CONNECTED || !currentState.ws ) { Toast.show({ content: "WebSocket未连接", position: "top" }); return; } const { user } = useUserStore.getState(); const { token, token2 } = useUserStore.getState(); const accessToken = token2 || token; const command = { accessToken: accessToken, accountId: user?.s2_accountId, client: currentState.config?.client || "kefu-client", cmdType: cmdType, seq: Date.now(), ...data, }; try { currentState.ws.send(JSON.stringify(command)); console.log("命令发送成功:", command); } catch (error) { console.error("命令发送失败:", error); Toast.show({ content: "命令发送失败", position: "top" }); } }, // 清除消息 clearMessages: () => { set({ messages: [], unreadCount: 0 }); }, // 标记为已读 markAsRead: () => { set({ unreadCount: 0 }); }, // 重连 reconnect: () => { const currentState = get(); if (currentState.config) { currentState.connect(currentState.config); } }, // 内部方法:处理连接打开 _handleOpen: () => { const currentState = get(); set({ status: WebSocketStatus.CONNECTED, reconnectAttempts: 0, }); console.log("WebSocket连接成功"); // 发送登录命令 if (currentState.config) { currentState.sendCommand("CmdSignIn"); } Toast.show({ content: "WebSocket连接成功", position: "top" }); }, // 内部方法:处理消息接收 _handleMessage: (event: MessageEvent) => { try { const data = JSON.parse(event.data); console.log("收到WebSocket消息:", data); const currentState = get(); const newMessage: WebSocketMessage = { id: Date.now().toString(), type: data.type || "message", content: data, timestamp: Date.now(), sender: data.sender, receiver: data.receiver, }; set({ messages: [...currentState.messages, newMessage], unreadCount: currentState.unreadCount + 1, }); // 可以在这里添加消息处理逻辑 // 比如播放提示音、显示通知等 } catch (error) { console.error("解析WebSocket消息失败:", error); } }, // 内部方法:处理连接关闭 _handleClose: (event: CloseEvent) => { const currentState = get(); console.log("WebSocket连接关闭:", event.code, event.reason); set({ status: WebSocketStatus.DISCONNECTED, ws: null, }); // 自动重连逻辑 if ( currentState.config?.autoReconnect && currentState.reconnectAttempts < (currentState.config?.maxReconnectAttempts || 5) ) { currentState._startReconnectTimer(); } }, // 内部方法:处理连接错误 _handleError: (event: Event) => { console.error("WebSocket连接错误:", event); set({ status: WebSocketStatus.ERROR }); Toast.show({ content: "WebSocket连接错误", position: "top" }); }, // 内部方法:启动重连定时器 _startReconnectTimer: () => { const currentState = get(); currentState._stopReconnectTimer(); set({ status: WebSocketStatus.RECONNECTING, reconnectAttempts: currentState.reconnectAttempts + 1, }); const timer = setTimeout(() => { console.log( `尝试重连 (${currentState.reconnectAttempts + 1}/${currentState.config?.maxReconnectAttempts})`, ); currentState.reconnect(); }, currentState.config?.reconnectInterval || 3000); set({ reconnectTimer: timer }); }, // 内部方法:停止重连定时器 _stopReconnectTimer: () => { const currentState = get(); if (currentState.reconnectTimer) { clearTimeout(currentState.reconnectTimer); set({ reconnectTimer: null }); } }, }), { name: "websocket-store", partialize: state => ({ // 只持久化必要的状态,不持久化WebSocket实例 status: state.status, config: state.config, messages: state.messages.slice(-100), // 只保留最近100条消息 unreadCount: state.unreadCount, reconnectAttempts: state.reconnectAttempts, }), onRehydrateStorage: () => state => { // 页面刷新后,如果之前是连接状态,尝试重新连接 if (state && state.status === WebSocketStatus.CONNECTED && state.config) { // 延迟一下再重连,确保页面完全加载 setTimeout(() => { state.connect(state.config); }, 1000); } }, }, );