/** * WebSocket客户端服务 * 提供WebSocket连接管理、心跳机制、消息处理等功能 * 集成JWT token解析,支持自动获取用户登录名 * * @author hongawen * @version 2.0 */ import { ElMessage } from "element-plus"; import { jwtUtil } from "./jwtUtil"; // ============================================================================ // 类型定义 (Types & Interfaces) // ============================================================================ /** * WebSocket消息接口定义(对应后端WebSocketVO结构) */ interface WebSocketMessage { type: string; // 消息类型 requestId?: string; // 请求ID operateCode?: string; // 操作代码 code?: number; // 状态码 desc?: string; // 描述信息 data?: T; // 泛型数据 } /** * 回调函数类型定义 */ type CallbackFunction = (message: WebSocketMessage) => void; /** * WebSocket配置接口 */ interface SocketConfig { url: string; // WebSocket服务器地址 heartbeatInterval?: number; // 心跳间隔时间(ms) reconnectDelay?: number; // 重连延迟时间(ms) maxReconnectAttempts?: number; // 最大重连次数 timeout?: number; // 超时时间(ms) } /** * 连接状态枚举 */ enum ConnectionStatus { DISCONNECTED = 'disconnected', // 未连接 CONNECTING = 'connecting', // 连接中 CONNECTED = 'connected', // 已连接 RECONNECTING = 'reconnecting', // 重连中 ERROR = 'error' // 连接错误 } /** * 常用的WebSocket消息类型定义命名空间 */ namespace WebSocketMessageTypes { /** * 预检测相关消息 */ export interface PreTestMessage { deviceId?: string; status?: string; progress?: number; errorInfo?: string; } /** * 系数校准相关消息 */ export interface CoefficientMessage { deviceId: string; channel: number; voltage?: string; current?: string; calibrationResult?: boolean; } /** * 正式检测相关消息 */ export interface TestMessage { deviceId: string; testType: string; testResult?: 'success' | 'failed' | 'processing'; testData?: any; } /** * 通用响应消息 */ export interface CommonResponse { success: boolean; message?: string; timestamp?: number; } } // ============================================================================ // 导出类型 // ============================================================================ export type { WebSocketMessage, CallbackFunction, SocketConfig, WebSocketMessageTypes }; export { ConnectionStatus }; // ============================================================================ // 主要服务类 // ============================================================================ /** * WebSocket服务类 * 单例模式实现,提供完整的WebSocket连接管理功能 */ export default class SocketService { // ======================================================================== // 静态属性和方法 (Static) // ======================================================================== /** * 单例实例 */ private static instance: SocketService | null = null; /** * 获取单例实例 * @returns SocketService实例 */ static get Instance(): SocketService { if (!this.instance) { this.instance = new SocketService(); } return this.instance; } // ======================================================================== // 实例属性 (Properties) // ======================================================================== /** * WebSocket连接实例 */ private ws: WebSocket | null = null; /** * 消息回调函数映射表 */ private callBackMapping: Record> = {}; /** * 当前连接状态 */ private connectionStatus: ConnectionStatus = ConnectionStatus.DISCONNECTED; /** * 发送消息重试计数器 */ private sendRetryCount: number = 0; /** * 连接重试计数器 */ private connectRetryCount: number = 0; /** * 心跳Worker实例 */ private heartbeatWorker: Worker | null = null; /** * Worker脚本的Blob URL */ private workerBlobUrl: string | null = null; /** * 最后一次收到心跳响应的时间戳 */ private lastResponseHeartTime: number = Date.now(); /** * WebSocket连接配置 */ private config: SocketConfig = { // url: 'ws://127.0.0.1:7777/hello', url: 'ws://192.168.1.124:7777/hello', heartbeatInterval: 9000, // 9秒心跳间隔 reconnectDelay: 5000, // 5秒重连延迟 maxReconnectAttempts: 5, // 最多重连5次 timeout: 30000 // 30秒超时 }; // ======================================================================== // 构造函数 (Constructor) // ======================================================================== /** * 私有构造函数,防止外部直接实例化 */ private constructor() { this.initializeProperties(); } /** * 初始化属性 */ private initializeProperties(): void { this.lastResponseHeartTime = Date.now(); } // ======================================================================== // Getter属性 (Computed) // ======================================================================== /** * 获取连接状态 * @returns 是否已连接 */ get connected(): boolean { return this.connectionStatus === ConnectionStatus.CONNECTED; } // ======================================================================== // 公共方法 (Public Methods) // ======================================================================== /** * 配置WebSocket连接参数 * @param config 部分配置对象 */ public configure(config: Partial): void { this.config = { ...this.config, ...config }; } /** * 连接WebSocket服务器(同步方式,保持向后兼容) */ public connect(): Promise | void { // 检查浏览器支持 if (!window.WebSocket) { console.log('您的浏览器不支持WebSocket'); return; } // 防止重复连接 if (this.connectionStatus === ConnectionStatus.CONNECTING || this.connected) { console.warn('WebSocket已连接或正在连接中'); return; } this.connectionStatus = ConnectionStatus.CONNECTING; try { this.ws = new WebSocket(this.buildWebSocketUrl()); this.setupEventHandlersLegacy(); } catch (error) { this.connectionStatus = ConnectionStatus.ERROR; console.error('WebSocket连接失败:', error); } } /** * 异步连接WebSocket服务器 * @returns Promise */ public connectAsync(): Promise { return new Promise((resolve, reject) => { // 检查浏览器支持 if (!window.WebSocket) { const error = '您的浏览器不支持WebSocket'; console.error(error); reject(new Error(error)); return; } // 防止重复连接 if (this.connectionStatus === ConnectionStatus.CONNECTING || this.connected) { console.warn('WebSocket已连接或正在连接中'); resolve(); return; } this.connectionStatus = ConnectionStatus.CONNECTING; try { this.ws = new WebSocket(this.buildWebSocketUrl()); this.setupEventHandlers(resolve, reject); } catch (error) { this.connectionStatus = ConnectionStatus.ERROR; reject(error); } }); } /** * 注册消息回调函数(支持泛型) * @param messageType 消息类型 * @param callback 回调函数 */ public registerCallBack(messageType: string, callback: CallbackFunction): void { if (!messageType || typeof callback !== 'function') { console.error('注册回调函数参数无效'); return; } this.callBackMapping[messageType] = callback; console.log(`注册消息处理器: ${messageType}`); } /** * 注销消息回调函数 * @param messageType 消息类型 */ public unRegisterCallBack(messageType: string): void { if (this.callBackMapping[messageType]) { delete this.callBackMapping[messageType]; console.log(`注销消息处理器: ${messageType}`); } } /** * 发送数据到WebSocket服务器 * @param data 要发送的数据 * @returns Promise */ public send(data: any): Promise { return new Promise((resolve, reject) => { if (!this.connected || !this.ws) { // 未连接时的重试机制 if (this.sendRetryCount < 3) { this.sendRetryCount++; setTimeout(() => { this.send(data).then(resolve).catch(reject); }, this.sendRetryCount * 500); return; } else { reject(new Error('WebSocket未连接且重试失败')); return; } } try { // 重置重试计数 this.sendRetryCount = 0; // 尝试发送JSON数据,失败则发送原始数据 const message = typeof data === 'string' ? data : JSON.stringify(data); this.ws.send(message); console.log('发送消息:', message); resolve(); } catch (error) { console.error('发送消息失败:', error); reject(error); } }); } /** * 关闭WebSocket连接 */ public closeWs(): void { console.log('正在关闭WebSocket连接...'); // 清理心跳 this.clearHeartbeat(); // 关闭连接 if (this.ws) { this.ws.close(1000, '主动关闭连接'); this.ws = null; } // 更新状态 this.connectionStatus = ConnectionStatus.DISCONNECTED; this.connectRetryCount = 0; this.sendRetryCount = 0; console.log('WebSocket连接已关闭'); } /** * 获取当前连接状态 * @returns 连接状态枚举值 */ public getConnectionStatus(): ConnectionStatus { return this.connectionStatus; } /** * 获取连接统计信息 * @returns 连接统计对象 */ public getConnectionStats(): { status: ConnectionStatus; connectRetryCount: number; lastResponseHeartTime: number; } { return { status: this.connectionStatus, connectRetryCount: this.connectRetryCount, lastResponseHeartTime: this.lastResponseHeartTime }; } // ======================================================================== // 私有方法 (Private Methods) // ======================================================================== /** * 构建完整的WebSocket URL * 自动从JWT token中获取loginName作为name参数 * @returns 完整的WebSocket URL */ private buildWebSocketUrl(): string { const { url } = this.config; // 直接从JWT token中获取loginName作为name参数 const loginName = jwtUtil.getLoginName(); if (loginName) { const separator = url.includes('?') ? '&' : '?'; return `${url}${separator}name=${encodeURIComponent(loginName)}`; } // 如果无法获取loginName,返回原始URL并输出警告 console.warn('无法从JWT token中获取loginName,WebSocket连接可能会失败'); return url; } /** * 设置WebSocket事件处理器(异步版本) * @param resolve Promise resolve回调 * @param reject Promise reject回调 */ private setupEventHandlers(resolve: () => void, reject: (error: Error) => void): void { if (!this.ws) return; // 连接成功事件 this.ws.onopen = () => { ElMessage.success("WebSocket连接服务端成功"); console.log('WebSocket连接成功'); this.connectionStatus = ConnectionStatus.CONNECTED; this.connectRetryCount = 0; this.startHeartbeat(); resolve(); }; // 连接关闭事件 this.ws.onclose = (event: CloseEvent) => { console.log('WebSocket连接关闭', event.code, event.reason); this.connectionStatus = ConnectionStatus.DISCONNECTED; this.clearHeartbeat(); // 非正常关闭且未超过最大重连次数,尝试重连 if (event.code !== 1000 && this.connectRetryCount < this.config.maxReconnectAttempts!) { this.attemptReconnect(); } }; // 连接错误事件 this.ws.onerror = (error: Event) => { console.error('WebSocket连接错误:', error); ElMessage.error("WebSocket连接异常"); this.connectionStatus = ConnectionStatus.ERROR; reject(new Error('WebSocket连接失败')); }; // 消息接收事件 this.ws.onmessage = (event: MessageEvent) => { this.handleMessage(event); }; } /** * 设置WebSocket事件处理器(兼容版本) */ private setupEventHandlersLegacy(): void { if (!this.ws) return; // 连接成功事件 this.ws.onopen = () => { ElMessage.success("webSocket连接服务端成功了"); console.log('连接服务端成功了'); this.connectionStatus = ConnectionStatus.CONNECTED; this.connectRetryCount = 0; this.startHeartbeat(); }; // 连接关闭事件 this.ws.onclose = (event: CloseEvent) => { console.log('连接webSocket服务端关闭'); this.connectionStatus = ConnectionStatus.DISCONNECTED; this.clearHeartbeat(); // 保持原有的重连逻辑(被注释掉的) // this.connectRetryCount++; /* setTimeout(() => { this.connect(); }, 500 * this.connectRetryCount);*/ }; // 连接错误事件 this.ws.onerror = () => { ElMessage.error("webSocket连接异常!"); this.connectionStatus = ConnectionStatus.ERROR; }; // 消息接收事件 this.ws.onmessage = (event: MessageEvent) => { this.handleMessage(event); }; } /** * 处理接收到的消息 * 支持心跳响应、JSON消息和普通文本消息 * @param event WebSocket消息事件 */ private handleMessage(event: MessageEvent): void { // console.log('Received message:', event.data); // 心跳响应处理 if (event.data === 'over') { console.log(`${new Date().toLocaleTimeString()} - 收到心跳响应`); this.lastResponseHeartTime = Date.now(); return; } // 检查消息是否为空或无效 if (!event.data || event.data.trim() === '') { console.warn('收到空消息,跳过处理'); return; } // 业务消息处理 try { // 检查是否为JSON格式 if (typeof event.data === 'string' && (event.data.startsWith('{') || event.data.startsWith('['))) { const message: WebSocketMessage = JSON.parse(event.data); if (message?.type && this.callBackMapping[message.type]) { this.callBackMapping[message.type](message); } else { console.warn('未找到对应的消息处理器:', message.type); } } else { // 非JSON格式的消息,作为普通文本处理 console.log('收到非JSON格式消息:', event.data); // 可以添加文本消息的处理逻辑 if (this.callBackMapping['text']) { this.callBackMapping['text']({ type: 'text', data: event.data }); } } } catch (error) { console.error('消息解析失败:', event.data, error); console.error('消息类型:', typeof event.data); console.error('消息长度:', event.data?.length || 0); } } // ======================================================================== // 重连机制相关方法 // ======================================================================== /** * 尝试重新连接WebSocket */ private attemptReconnect(): void { if (this.connectionStatus === ConnectionStatus.RECONNECTING) { return; } this.connectionStatus = ConnectionStatus.RECONNECTING; this.connectRetryCount++; const delay = this.config.reconnectDelay! * this.connectRetryCount; console.log(`尝试第${this.connectRetryCount}次重连,${delay}ms后开始...`); setTimeout(() => { try { const result = this.connect(); if (result instanceof Promise) { result.catch((error: any) => { console.error('重连失败:', error); }); } } catch (error: any) { console.error('重连失败:', error); } }, delay); } // ======================================================================== // 心跳机制相关方法 // ======================================================================== /** * 启动心跳机制 */ private startHeartbeat(): void { this.lastResponseHeartTime = Date.now(); this.createHeartbeatWorker(); } /** * 创建心跳Worker * 使用Worker在独立线程中处理心跳定时器,避免主线程阻塞 */ private createHeartbeatWorker(): void { try { // 创建Worker脚本 const workerScript = ` setInterval(function() { postMessage('heartbeat'); }, ${this.config.heartbeatInterval}); `; this.workerBlobUrl = window.URL.createObjectURL( new Blob([workerScript], { type: 'application/javascript' }) ); this.heartbeatWorker = new Worker(this.workerBlobUrl); // 心跳Worker消息处理 this.heartbeatWorker.onmessage = (event: MessageEvent) => { this.handleHeartbeatTick(); }; // Worker错误处理 this.heartbeatWorker.onerror = (error: ErrorEvent) => { console.error('心跳Worker错误:', error); this.clearHeartbeat(); }; } catch (error) { console.error('创建心跳Worker失败:', error); } } /** * 处理心跳定时器事件 * 检查连接超时并发送心跳消息 */ private handleHeartbeatTick(): void { // 检查是否超时(距离上次收到心跳响应的时间) const timeSinceLastResponse = Date.now() - this.lastResponseHeartTime; if (timeSinceLastResponse > this.config.timeout!) { console.error(`WebSocket心跳超时: ${timeSinceLastResponse}ms > ${this.config.timeout}ms`); ElMessage.error("WebSocket连接超时,请检查网络连接!"); this.clearHeartbeat(); this.closeWs(); return; } this.sendHeartbeat(); } /** * 发送心跳消息到服务器 */ private sendHeartbeat(): void { if (this.connected && this.ws) { console.log(`${new Date().toLocaleTimeString()} - 发送心跳消息`); this.ws.send('alive'); } } /** * 清理心跳机制 * 终止Worker并清理相关资源 */ private clearHeartbeat(): void { if (this.heartbeatWorker) { this.heartbeatWorker.terminate(); this.heartbeatWorker = null; } if (this.workerBlobUrl) { window.URL.revokeObjectURL(this.workerBlobUrl); this.workerBlobUrl = null; } } }