From 466d5521658a66ee973bdecf780f8504ce9125a5 Mon Sep 17 00:00:00 2001 From: hongawen <83944980@qq.com> Date: Wed, 26 Nov 2025 10:53:52 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AD=BE=E5=87=BA=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/utils/webSocketClient.ts | 764 +++++--------------------- 1 file changed, 126 insertions(+), 638 deletions(-) diff --git a/frontend/src/utils/webSocketClient.ts b/frontend/src/utils/webSocketClient.ts index cbd33b8..d8b771e 100644 --- a/frontend/src/utils/webSocketClient.ts +++ b/frontend/src/utils/webSocketClient.ts @@ -1,697 +1,185 @@ -/** - * WebSocket客户端服务 - * 提供WebSocket连接管理、心跳机制、消息处理等功能 - * 集成JWT token解析,支持自动获取用户登录名 - * - * @author hongawen - * @version 2.0 - */ +import {ElMessage} from "element-plus"; -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 { + static instance = null; + static get Instance() { if (!this.instance) { this.instance = new SocketService(); } return this.instance; } + // 和服务端连接的socket对象 + ws = null; + // 存储回调函数 + callBackMapping = {}; + // 标识是否连接成功 + connected = false; + // 记录重试的次数 + sendRetryCount = 0; + // 重新连接尝试的次数 + connectRetryCount = 0; + work:any; + workerBlobUrl:any; + lastActivityTime= 0; // 上次活动时间戳 + lastResponseHeartTime = Date.now();//最后一次收到心跳回复时间 - // ======================================================================== - // 实例属性 (Properties) - // ======================================================================== + reconnectDelay= 5000; // 重新连接延迟,单位毫秒 - /** - * WebSocket连接实例 - */ - private ws: WebSocket | null = null; + // 定义连接服务器的方法 + connect() { - /** - * 消息回调函数映射表 - */ - 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; + return console.log('您的浏览器不支持WebSocket'); } - // 防止重复连接 - 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 - }; - } + // let token = $.cookie('123'); + // let token = '4E6EF539AAF119D82AC4C2BC84FBA21F'; - // ======================================================================== - // 私有方法 (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; - - // 连接成功事件 + const url = 'ws://127.0.0.1:7777/hello?name=cdf' + this.ws = new WebSocket(url); + // 连接成功的事件 this.ws.onopen = () => { ElMessage.success("webSocket连接服务端成功了"); - // console.log('连接服务端成功了'); - this.connectionStatus = ConnectionStatus.CONNECTED; + console.log('连接服务端成功了'); + this.connected = true; + // 重置重新连接的次数 this.connectRetryCount = 0; + this.updateLastActivityTime(); this.startHeartbeat(); }; - - // 连接关闭事件 - this.ws.onclose = (event: CloseEvent) => { - // console.log('连接webSocket服务端关闭'); - this.connectionStatus = ConnectionStatus.DISCONNECTED; + // 1.连接服务端失败 + // 2.当连接成功之后, 服务器关闭的情况 + this.ws.onclose = () => { + console.log('连接webSocket服务端关闭'); + this.connected = false; + this.connectRetryCount++; this.clearHeartbeat(); - - // 保持原有的重连逻辑(被注释掉的) - // this.connectRetryCount++; /* setTimeout(() => { - this.connect(); - }, 500 * this.connectRetryCount);*/ + 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); + // 得到服务端发送过来的数据 + this.ws.onmessage = (event) => { + // console.log('🚀 ~ SocketService ~ connect ~ event:', event) + if(event.data == 'over') { + //心跳消息处理 + this.lastResponseHeartTime = Date.now(); + this.updateLastActivityTime(); // 收到心跳响应时更新活动时间 + }else { + let message: { [key: string]: any }; + try { + console.log('Received message:',event.data) + message = JSON.parse(event.data); + } catch (e) { + return console.error("消息解析失败", event.data, e); + } - // 心跳响应处理 - 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); + /* 通过接受服务端发送的type字段来回调函数 */ 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 - }); + console.log("抛弃====>") + console.log(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 { + startHeartbeat() { this.lastResponseHeartTime = Date.now(); - this.createHeartbeatWorker(); + const _this = this + _this.workerBlobUrl = window.URL.createObjectURL(new Blob(['(function(e){setInterval(function(){this.postMessage(null)},9000)})()'])); + + this.work = new Worker(_this.workerBlobUrl); + this.work.onmessage = function(e){ + //判断多久没收到心跳响应 + + if(_this.lastActivityTime - _this.lastResponseHeartTime > 30000){ + //说明已经三轮心跳没收到回复了,关闭检测,提示用户。 + ElMessage.error("业务主体模块发生未知异常,请尝试重新启动!"); + _this.clearHeartbeat(); + return; + } + _this.sendHeartbeat(); + } + + } + sendHeartbeat() { + console.log(new Date()+"进入心跳消息发送。。。。。。。。。。。。。") + this.ws.send('alive'); + this.updateLastActivityTime(); // 发送心跳后更新活动时间 } - /** - * 创建心跳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' }) - ); + updateLastActivityTime() { + this.lastActivityTime = Date.now(); + } - 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); + clearHeartbeat() { + const _this = this + if (_this.work) { + _this.work.terminate(); + _this.work = null; + } + if (_this.workerBlobUrl) { + window.URL.revokeObjectURL(_this.workerBlobUrl); // 释放临时的Blob URL + _this.workerBlobUrl = null; } } - /** - * 处理心跳定时器事件 - * 检查连接超时并发送心跳消息 - */ - 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(); + // 回调函数的注册 + registerCallBack(socketType, callBack) { + this.callBackMapping[socketType] = callBack; } - - /** - * 发送心跳消息到服务器 - */ - private sendHeartbeat(): void { - if (this.connected && this.ws) { - // console.log(`${new Date().toLocaleTimeString()} - 发送心跳消息`); - this.ws.send('alive'); + // 取消某一个回调函数 + unRegisterCallBack(socketType) { + this.callBackMapping[socketType] = null; + } + // 发送数据的方法 + send(data) { + // 判断此时此刻有没有连接成功 + if (this.connected) { + this.sendRetryCount = 0; + try { + this.ws.send(JSON.stringify(data)); + } catch (e) { + this.ws.send(data); + } + } else { + this.sendRetryCount++; + setTimeout(() => { + this.send(data); + }, this.sendRetryCount * 500); } } - /** - * 清理心跳机制 - * 终止Worker并清理相关资源 - */ - private clearHeartbeat(): void { - if (this.heartbeatWorker) { - this.heartbeatWorker.terminate(); - this.heartbeatWorker = null; - } - - if (this.workerBlobUrl) { - window.URL.revokeObjectURL(this.workerBlobUrl); - this.workerBlobUrl = null; + // 断开方法 + closeWs() { + if (this.connected) { + this.ws.close() } + console.log('执行WS关闭命令..'); } -} \ No newline at end of file +}