diff --git a/index.html b/index.html index 4451c75..ba8f83c 100644 --- a/index.html +++ b/index.html @@ -5,6 +5,10 @@ 西安智慧应急多灾种灾害链分析专项 +
diff --git a/package.json b/package.json index dd58b4a..245027d 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@element-plus/icons-vue": "^2.3.2", + "@stomp/stompjs": "^7.3.0", "@types/spark-md5": "^3.0.5", "axios": "^1.12.2", "cesium": "1.101.0", @@ -20,6 +21,7 @@ "gm-crypto": "^0.1.12", "pinia": "^3.0.3", "proj4": "^2.20.8", + "sockjs-client": "^1.6.1", "spark-md5": "^3.0.2", "vite-plugin-cesium": "^1.2.22", "vue": "^3.5.32", @@ -29,6 +31,7 @@ "@tsconfig/node22": "^22.0.2", "@types/node": "^24.12.2", "@types/proj4": "^2.19.0", + "@types/sockjs-client": "^1.5.4", "@vitejs/plugin-vue": "^6.0.5", "@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-typescript": "^14.6.0", diff --git a/src/types/websocket/WebSocketMessage.ts b/src/types/websocket/WebSocketMessage.ts deleted file mode 100644 index 60be2aa..0000000 --- a/src/types/websocket/WebSocketMessage.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * WebSocket消息类型 - */ -export interface WebSocketMessage { - type: string; - data?: unknown; - encryptedData?: string; - sm4KeyEncrypted?: string; - timestamp?: number; - params?: Record; // 额外参数 -} diff --git a/src/utils/request/websocket.ts b/src/utils/request/websocket.ts deleted file mode 100644 index 831e765..0000000 --- a/src/utils/request/websocket.ts +++ /dev/null @@ -1,392 +0,0 @@ -import type { WebSocketOptions } from '@/types/websocket/WebSocketOptions'; -import { SafetyUtils } from '../safety/SafetyUtils'; -import type { WebSocketMessage } from '@/types/websocket/WebSocketMessage'; - -/** - * WebSocket事件回调类型 - */ -export type WebSocketEventCallback = (data: unknown) => void; - -/** - * WebSocket管理器 - * 提供WebSocket连接的统一管理 - */ -export class WebSocketManager { - #ws: WebSocket | null = null; - #url: string = ''; - #options: Required; - #eventCallbacks: Map> = new Map(); - #reconnectAttempts: number = 0; - #heartbeatTimer: number | null = null; - #reconnectTimer: number | null = null; - #isManualClose: boolean = false; - #sessionSm4Key: string | null = null; - #connectionTimeoutTimer: number | null = null; - - constructor(options: WebSocketOptions = {}) { - this.#options = { - path: options.path || '/websocket', - enableEncrypt: options.enableEncrypt ?? true, - reconnectInterval: options.reconnectInterval ?? 5000, - maxReconnectAttempts: options.maxReconnectAttempts ?? 10, - heartbeatInterval: options.heartbeatInterval ?? 30000, - connectionTimeout: options.connectionTimeout ?? 10000, - }; - - // 构建WebSocket URL - const wsBaseUrl = - import.meta.env.VITE_WEBSOCKET_URL || 'ws://localhost:8081'; - this.#url = `${wsBaseUrl}${this.#options.path}`; - } - - /** - * 连接WebSocket - * @returns Promise - */ - connect(): Promise { - return new Promise((resolve, reject) => { - if (this.#ws && this.#ws.readyState === WebSocket.OPEN) { - resolve(); - return; - } - - this.#isManualClose = false; - this.#ws = new WebSocket(this.#url); - - // 设置连接超时 - this.#connectionTimeoutTimer = window.setTimeout(() => { - if (this.#ws?.readyState !== WebSocket.OPEN) { - this.#ws?.close(); - reject(new Error('WebSocket连接超时')); - } - }, this.#options.connectionTimeout); - - // 连接成功 - this.#ws.onopen = async () => { - this.#reconnectAttempts = 0; - - // 清除连接超时定时器 - if (this.#connectionTimeoutTimer) { - clearTimeout(this.#connectionTimeoutTimer); - this.#connectionTimeoutTimer = null; - } - - // 如果启用加密,建立会话密钥 - if (this.#options.enableEncrypt) { - try { - // 生成会话SM4密钥 - this.#sessionSm4Key = SafetyUtils.generateSm4Key(); - // 使用SM2公钥加密会话密钥并发送给后端 - const encryptedSessionKey = await SafetyUtils.sm2Encrypt( - this.#sessionSm4Key - ); - - // 发送会话密钥给后端(使用特殊的消息类型) - this.#ws?.send( - JSON.stringify({ - type: 'session-key', - sm4KeyEncrypted: encryptedSessionKey, - timestamp: Date.now(), - }) - ); - } catch (error) { - console.error('会话密钥建立失败:', error); - } - } - - // 启动心跳 - this.#startHeartbeat(); - - resolve(); - }; - - // 接收消息 - this.#ws.onmessage = async (event) => { - try { - let message: WebSocketMessage; - - if (typeof event.data === 'string') { - message = JSON.parse(event.data); - } else { - message = event.data; - } - - // 解密处理 - if ( - this.#options.enableEncrypt && - message.encryptedData && - this.#sessionSm4Key - ) { - try { - // 使用会话SM4密钥解密数据 - const decryptedPayload = SafetyUtils.sm4Decrypt( - this.#sessionSm4Key, - message.encryptedData - ); - - // 解析解密后的payload - if ( - typeof decryptedPayload === 'object' && - decryptedPayload !== null - ) { - const payload = decryptedPayload as { - data?: unknown; - params?: Record; - }; - message.data = payload.data; - message.params = payload.params; - } else { - message.data = decryptedPayload; - } - } catch (error) { - console.error('WebSocket消息解密失败:', error); - } - } - - // 触发对应类型的回调 - const callbacks = this.#eventCallbacks.get(message.type); - if (callbacks) { - callbacks.forEach((callback) => callback(message.data)); - } - - // 触发通用消息回调 - const allCallbacks = this.#eventCallbacks.get('*'); - if (allCallbacks) { - allCallbacks.forEach((callback) => callback(message)); - } - } catch (error) { - console.error('WebSocket消息处理失败:', error); - } - }; - - // 连接关闭 - this.#ws.onclose = (event) => { - console.log('WebSocket连接关闭:', event.code, event.reason); - this.#stopHeartbeat(); - this.#clearConnectionTimeout(); - - // 如果不是手动关闭且未达到最大重连次数,则尝试重连 - if (!this.#isManualClose) { - this.#attemptReconnect(); - } - }; - - // 连接错误 - this.#ws.onerror = (error) => { - console.error('WebSocket连接错误:', error); - this.#clearConnectionTimeout(); - reject(error); - }; - }); - } - - /** - * 发送消息 - * @param type - 消息类型 - * @param data - 消息数据 - * @param params - 额外参数(可选) - */ - async send( - type: string, - data?: unknown, - params?: Record - ): Promise { - if (!this.#ws || this.#ws.readyState !== WebSocket.OPEN) { - throw new Error('WebSocket未连接'); - } - - let message: WebSocketMessage = { - type, - data, - params, - timestamp: Date.now(), - }; - - // 加密处理 - if (this.#options.enableEncrypt) { - try { - // 生成SM4密钥 - const sm4Key = SafetyUtils.generateSm4Key(); - - // 使用SM2公钥加密SM4密钥 - const sm4KeyEncrypted = await SafetyUtils.sm2Encrypt(sm4Key); - - // 组合数据和参数进行加密 - const payload = { - data, - params, - }; - - // 使用SM4加密整个payload - const encryptedData = SafetyUtils.sm4Encrypt(sm4Key, payload); - - message = { - type, - encryptedData, - sm4KeyEncrypted, - timestamp: Date.now(), - }; - } catch (error) { - console.error('WebSocket消息加密失败:', error); - throw new Error('消息加密失败'); - } - } - - this.#ws.send(JSON.stringify(message)); - } - - /** - * 注册事件监听器 - * @param type - 消息类型('*'表示监听所有消息) - * @param callback - 回调函数 - * @returns 取消订阅函数 - */ - on(type: string, callback: WebSocketEventCallback): () => void { - if (!this.#eventCallbacks.has(type)) { - this.#eventCallbacks.set(type, new Set()); - } - this.#eventCallbacks.get(type)!.add(callback); - - // 返回取消订阅函数 - return () => { - this.off(type, callback); - }; - } - - /** - * 移除事件监听器 - * @param type - 消息类型 - * @param callback - 回调函数 - */ - off(type: string, callback: WebSocketEventCallback): void { - const callbacks = this.#eventCallbacks.get(type); - if (callbacks) { - callbacks.delete(callback); - if (callbacks.size === 0) { - this.#eventCallbacks.delete(type); - } - } - } - - /** - * 关闭WebSocket连接 - */ - close(): void { - this.#isManualClose = true; - this.#stopHeartbeat(); - this.#clearReconnectTimer(); - this.#clearConnectionTimeout(); - this.#sessionSm4Key = null; // 清除会话密钥 - - if (this.#ws) { - this.#ws.close(); - this.#ws = null; - } - - console.log('WebSocket连接已手动关闭'); - } - - /** - * 获取连接状态 - * @returns WebSocket就绪状态 - */ - get readyState(): number { - return this.#ws?.readyState ?? WebSocket.CLOSED; - } - - /** - * 是否已连接 - * @returns 是否已连接 - */ - get isConnected(): boolean { - return this.#ws?.readyState === WebSocket.OPEN; - } - - // ===================== 私有方法 ===================== - - /** - * 尝试重连 - */ - #attemptReconnect(): void { - const { maxReconnectAttempts, reconnectInterval } = this.#options; - - // 检查是否达到最大重连次数 - if ( - maxReconnectAttempts !== -1 && - this.#reconnectAttempts >= maxReconnectAttempts - ) { - console.error( - `WebSocket重连失败:已达到最大重连次数(${maxReconnectAttempts})` - ); - return; - } - - this.#reconnectAttempts++; - console.log( - `WebSocket将在${reconnectInterval}ms后尝试第${this.#reconnectAttempts}次重连...` - ); - - this.#reconnectTimer = window.setTimeout(() => { - this.connect().catch((error) => { - console.error('WebSocket重连失败:', error); - }); - }, reconnectInterval); - } - - /** - * 启动心跳 - */ - #startHeartbeat(): void { - this.#stopHeartbeat(); - - this.#heartbeatTimer = window.setInterval(() => { - if (this.isConnected) { - this.send('ping').catch((error) => { - console.error('心跳发送失败:', error); - }); - } - }, this.#options.heartbeatInterval); - } - - /** - * 停止心跳 - */ - #stopHeartbeat(): void { - if (this.#heartbeatTimer) { - clearInterval(this.#heartbeatTimer); - this.#heartbeatTimer = null; - } - } - - /** - * 清除重连定时器 - */ - #clearReconnectTimer(): void { - if (this.#reconnectTimer) { - clearTimeout(this.#reconnectTimer); - this.#reconnectTimer = null; - } - } - - /** - * 清除连接超时定时器 - */ - #clearConnectionTimeout(): void { - if (this.#connectionTimeoutTimer) { - clearTimeout(this.#connectionTimeoutTimer); - this.#connectionTimeoutTimer = null; - } - } -} - -/** - * 创建WebSocket管理器实例的工厂函数 - * @param options - WebSocket配置选项 - * @returns WebSocket管理器实例 - */ -export function createWebSocket(options?: WebSocketOptions): WebSocketManager { - return new WebSocketManager(options); -} - -// 导出默认实例 -export const defaultWebSocket = createWebSocket();