import { ref, onMounted, onUnmounted } from 'vue' import notificationService from './notificationService' interface WebSocketMessage { type: 'NOTIFICATION' | 'APPROVAL_UPDATE' | 'PROCUREMENT_UPDATE' | 'SYSTEM' | 'EQUIPMENT_STATUS_UPDATE' | 'HEARTBEAT' | 'EQUIPMENT_BORROW_UPDATE' | 'EQUIPMENT_RETURN_UPDATE' | 'EQUIPMENT_MAINTENANCE_UPDATE' | 'EQUIPMENT_ALERT' | 'WORKFLOW_UPDATE' data: any timestamp: number messageId?: string } class WebSocketService { private ws: WebSocket | null = null private reconnectAttempts = 0 private maxReconnectAttempts = 5 private reconnectInterval = 3000 // 3秒 private heartbeatInterval: NodeJS.Timeout | null = null private isConnected = ref(false) private isConnecting = ref(false) // 连接状态 public readonly connected = this.isConnected public readonly connecting = this.isConnecting // 连接配置 private wsUrl = import.meta.env.VITE_API_WS_URL || 'ws://localhost:8888/websocket' private token = localStorage.getItem('token') || '' // 事件监听器 private eventListeners: Map = new Map() // 消息队列 - 用于离线时缓存消息 private messageQueue: WebSocketMessage[] = [] private maxQueueSize = 100 // 消息去重 - 避免重复处理 private processedMessages = new Set() private maxProcessedMessages = 1000 constructor() { this.setupEventListeners() this.loadMessageQueue() } // 连接WebSocket public connect() { if (this.isConnecting.value || this.isConnected.value) { return } this.isConnecting.value = true try { // 构建WebSocket URL,包含认证token const url = `${this.wsUrl}?token=${encodeURIComponent(this.token)}` this.ws = new WebSocket(url) this.ws.onopen = this.handleOpen.bind(this) this.ws.onmessage = this.handleMessage.bind(this) this.ws.onclose = this.handleClose.bind(this) this.ws.onerror = this.handleError.bind(this) } catch (error) { console.error('WebSocket连接失败:', error) this.isConnecting.value = false this.scheduleReconnect() } } // 断开连接 public disconnect() { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval) this.heartbeatInterval = null } if (this.ws) { this.ws.close() this.ws = null } this.isConnected.value = false this.isConnecting.value = false } // 发送消息 public send(message: any) { if (this.ws && this.isConnected.value) { try { this.ws.send(JSON.stringify(message)) } catch (error) { console.error('发送WebSocket消息失败:', error) // 发送失败时,将消息加入队列 this.addToMessageQueue(message) } } else { console.warn('WebSocket未连接,将消息加入队列') this.addToMessageQueue(message) } } // 发送心跳 public sendHeartbeat() { this.send({ type: 'HEARTBEAT', data: { timestamp: Date.now() }, timestamp: Date.now() }) } // 处理连接打开 private handleOpen() { console.log('WebSocket连接已建立') this.isConnected.value = true this.isConnecting.value = false this.reconnectAttempts = 0 // 启动心跳 this.startHeartbeat() // 发送认证消息 this.send({ type: 'AUTH', data: { token: this.token }, timestamp: Date.now() }) // 处理离线期间的消息队列 this.processMessageQueue() // 触发连接事件 this.emit('connected', null) } // 处理消息接收 private handleMessage(event: MessageEvent) { try { const message: WebSocketMessage = JSON.parse(event.data) this.processMessage(message) } catch (error) { console.error('解析WebSocket消息失败:', error) } } // 处理连接关闭 private handleClose(event: CloseEvent) { console.log('WebSocket连接已关闭:', event.code, event.reason) this.isConnected.value = false this.isConnecting.value = false // 停止心跳 if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval) this.heartbeatInterval = null } // 触发断开连接事件 this.emit('disconnected', { code: event.code, reason: event.reason }) // 如果不是主动关闭,尝试重连 if (event.code !== 1000) { this.scheduleReconnect() } } // 处理连接错误 private handleError(error: Event) { console.error('WebSocket连接错误:', error) this.isConnecting.value = false // 触发错误事件 this.emit('error', error) // 尝试重连 this.scheduleReconnect() } // 处理接收到的消息 private processMessage(message: WebSocketMessage) { console.log('收到WebSocket消息:', message) // 消息去重检查 if (message.messageId && this.processedMessages.has(message.messageId)) { console.log('消息已处理,跳过:', message.messageId) return } // 添加到已处理消息集合 if (message.messageId) { this.processedMessages.add(message.messageId) // 限制已处理消息数量 if (this.processedMessages.size > this.maxProcessedMessages) { const firstKey = this.processedMessages.keys().next().value if (firstKey) { this.processedMessages.delete(firstKey) } } } switch (message.type) { case 'NOTIFICATION': this.handleNotificationMessage(message.data) break case 'APPROVAL_UPDATE': this.handleApprovalUpdateMessage(message.data) break case 'PROCUREMENT_UPDATE': this.handleProcurementUpdateMessage(message.data) break case 'EQUIPMENT_STATUS_UPDATE': this.handleEquipmentStatusUpdateMessage(message.data) break case 'EQUIPMENT_BORROW_UPDATE': this.handleEquipmentBorrowUpdateMessage(message.data) break case 'EQUIPMENT_RETURN_UPDATE': this.handleEquipmentReturnUpdateMessage(message.data) break case 'EQUIPMENT_MAINTENANCE_UPDATE': this.handleEquipmentMaintenanceUpdateMessage(message.data) break case 'EQUIPMENT_ALERT': this.handleEquipmentAlertMessage(message.data) break case 'WORKFLOW_UPDATE': this.handleWorkflowUpdateMessage(message.data) break case 'SYSTEM': this.handleSystemMessage(message.data) break case 'HEARTBEAT': // 心跳响应,不需要特殊处理 break default: console.warn('未知的WebSocket消息类型:', message.type) } // 触发消息接收事件 this.emit('message', message) // 保存消息到本地存储 this.saveMessageToStorage(message) } // 处理通知消息 private handleNotificationMessage(data: any) { if (data.notification) { notificationService.addNotification({ type: data.notification.type || 'SYSTEM', title: data.notification.title || '新通知', content: data.notification.content || '', priority: data.notification.priority || 'NORMAL', category: data.notification.category, targetUrl: data.notification.targetUrl, metadata: data.notification.metadata }) } } // 处理审批更新消息 private handleApprovalUpdateMessage(data: any) { if (data.approval) { const approval = data.approval // 根据审批状态添加相应通知 if (approval.status === 'APPROVED') { notificationService.addApprovalNotification( approval.equipmentName, 'APPROVED', approval.approverName || '审批人' ) // 触发审批状态更新事件 this.emit('approvalStatusChanged', { type: 'APPROVED', approvalId: approval.approvalId, equipmentId: approval.equipmentId, status: approval.status }) } else if (approval.status === 'REJECTED') { notificationService.addApprovalNotification( approval.equipmentName, 'REJECTED', approval.approverName || '审批人' ) // 触发审批状态更新事件 this.emit('approvalStatusChanged', { type: 'REJECTED', approvalId: approval.approvalId, equipmentId: approval.equipmentId, status: approval.status }) } else if (approval.status === 'SUBMITTED') { // 新增待审批通知 notificationService.addNotification({ type: 'PENDING', title: '新的审批申请', content: `收到来自 ${approval.applicantName} 的${approval.businessType || '设备'}申请:${approval.equipmentName}`, targetUrl: '/asset-management/device-management/approval', priority: 'HIGH', category: '审批申请', metadata: { approvalId: approval.approvalId, equipmentName: approval.equipmentName, applicantName: approval.applicantName, businessType: approval.businessType, timestamp: Date.now() } }) // 触发新审批申请事件 this.emit('newApprovalRequest', approval) } } } // 处理采购更新消息 private handleProcurementUpdateMessage(data: any) { if (data.procurement) { const procurement = data.procurement if (procurement.status === 'SUBMITTED') { notificationService.addProcurementNotification( procurement.equipmentName, procurement.applicantName || '申请人' ) // 触发采购状态更新事件 this.emit('procurementStatusChanged', { type: 'SUBMITTED', procurementId: procurement.procurementId, equipmentId: procurement.equipmentId, status: procurement.status }) } else if (procurement.status === 'APPROVED') { // 采购申请被批准 notificationService.addNotification({ type: 'PROCUREMENT', title: '采购申请已批准', content: `您的设备采购申请"${procurement.equipmentName}"已获得批准`, targetUrl: '/asset-management/device-management/procurement', priority: 'NORMAL', category: '采购审批', metadata: { equipmentName: procurement.equipmentName, status: procurement.status, timestamp: Date.now() } }) // 触发采购状态更新事件 this.emit('procurementStatusChanged', { type: 'APPROVED', procurementId: procurement.procurementId, equipmentId: procurement.equipmentId, status: procurement.status }) } } } // 处理设备状态更新消息 private handleEquipmentStatusUpdateMessage(data: any) { if (data.equipment) { const equipment = data.equipment // 触发设备状态更新事件 this.emit('equipmentStatusChanged', { equipmentId: equipment.equipmentId, oldStatus: equipment.oldStatus, newStatus: equipment.newStatus, updateTime: equipment.updateTime }) // 如果状态变化涉及采购,添加相应通知 if (equipment.statusChangeType === 'PROCUREMENT') { notificationService.addNotification({ type: 'PROCUREMENT', title: '设备采购状态更新', content: `设备"${equipment.equipmentName}"的采购状态已更新为:${equipment.newStatus}`, targetUrl: '/asset-management/device-management/procurement', priority: 'NORMAL', category: '设备状态', metadata: { equipmentId: equipment.equipmentId, equipmentName: equipment.equipmentName, oldStatus: equipment.oldStatus, newStatus: equipment.newStatus, timestamp: Date.now() } }) } } } // 处理设备借用更新消息 private handleEquipmentBorrowUpdateMessage(data: any) { if (data.borrow) { const borrow = data.borrow // 触发设备借用状态更新事件 this.emit('equipmentBorrowChanged', { equipmentId: borrow.equipmentId, borrowId: borrow.borrowId, oldStatus: borrow.oldStatus, newStatus: borrow.newStatus, updateTime: borrow.updateTime }) // 添加借用状态更新通知 notificationService.addNotification({ type: 'EQUIPMENT_BORROW', title: '设备借用状态更新', content: `设备"${borrow.equipmentName}"的借用状态已更新为:${borrow.newStatus}`, targetUrl: '/asset-management/device-management/device-center', priority: 'NORMAL', category: '设备借用', metadata: { equipmentId: borrow.equipmentId, equipmentName: borrow.equipmentName, borrowId: borrow.borrowId, oldStatus: borrow.oldStatus, newStatus: borrow.newStatus, timestamp: Date.now() } }) } } // 处理设备归还更新消息 private handleEquipmentReturnUpdateMessage(data: any) { if (data.return) { const returnData = data.return // 触发设备归还状态更新事件 this.emit('equipmentReturnChanged', { equipmentId: returnData.equipmentId, returnId: returnData.returnId, oldStatus: returnData.oldStatus, newStatus: returnData.newStatus, updateTime: returnData.updateTime }) // 添加归还状态更新通知 notificationService.addNotification({ type: 'EQUIPMENT_RETURN', title: '设备归还状态更新', content: `设备"${returnData.equipmentName}"的归还状态已更新为:${returnData.newStatus}`, targetUrl: '/asset-management/device-management/device-center', priority: 'NORMAL', category: '设备归还', metadata: { equipmentId: returnData.equipmentId, equipmentName: returnData.equipmentName, returnId: returnData.returnId, oldStatus: returnData.oldStatus, newStatus: returnData.newStatus, timestamp: Date.now() } }) } } // 处理设备维护更新消息 private handleEquipmentMaintenanceUpdateMessage(data: any) { if (data.maintenance) { const maintenance = data.maintenance // 触发设备维护状态更新事件 this.emit('equipmentMaintenanceChanged', { equipmentId: maintenance.equipmentId, maintenanceId: maintenance.maintenanceId, oldStatus: maintenance.oldStatus, newStatus: maintenance.newStatus, updateTime: maintenance.updateTime }) // 添加维护状态更新通知 notificationService.addNotification({ type: 'EQUIPMENT_MAINTENANCE', title: '设备维护状态更新', content: `设备"${maintenance.equipmentName}"的维护状态已更新为:${maintenance.newStatus}`, targetUrl: '/asset-management/device-management/device-center', priority: 'NORMAL', category: '设备维护', metadata: { equipmentId: maintenance.equipmentId, equipmentName: maintenance.equipmentName, maintenanceId: maintenance.maintenanceId, oldStatus: maintenance.oldStatus, newStatus: maintenance.newStatus, timestamp: Date.now() } }) } } // 处理设备告警消息 private handleEquipmentAlertMessage(data: any) { if (data.alert) { const alert = data.alert // 触发设备告警事件 this.emit('equipmentAlert', { equipmentId: alert.equipmentId, alertId: alert.alertId, alertType: alert.alertType, alertLevel: alert.alertLevel, message: alert.message, timestamp: alert.timestamp }) // 添加设备告警通知 notificationService.addNotification({ type: 'EQUIPMENT_ALERT', title: `设备告警 - ${alert.alertType}`, content: `设备"${alert.equipmentName}"发生告警:${alert.message}`, targetUrl: '/asset-management/device-management/device-center', priority: alert.alertLevel === 'CRITICAL' ? 'URGENT' : 'HIGH', category: '设备告警', actionRequired: true, metadata: { equipmentId: alert.equipmentId, equipmentName: alert.equipmentName, alertId: alert.alertId, alertType: alert.alertType, alertLevel: alert.alertLevel, message: alert.message, timestamp: Date.now() } }) } } // 处理工作流更新消息 private handleWorkflowUpdateMessage(data: any) { if (data.workflow) { const workflow = data.workflow // 触发工作流状态更新事件 this.emit('workflowStatusChanged', { workflowId: workflow.workflowId, oldStatus: workflow.oldStatus, newStatus: workflow.newStatus, currentNode: workflow.currentNode, updateTime: workflow.updateTime }) // 添加工作流状态更新通知 notificationService.addNotification({ type: 'WORKFLOW', title: '工作流状态更新', content: `工作流"${workflow.workflowName}"状态已更新为:${workflow.newStatus}`, targetUrl: '/asset-management/device-management/approval', priority: 'NORMAL', category: '工作流', metadata: { workflowId: workflow.workflowId, workflowName: workflow.workflowName, oldStatus: workflow.oldStatus, newStatus: workflow.newStatus, currentNode: workflow.currentNode, timestamp: Date.now() } }) } } // 处理系统消息 private handleSystemMessage(data: any) { if (data.system) { const system = data.system notificationService.addSystemNotification( system.title || '系统通知', system.content || '', system.priority || 'NORMAL' ) } } // 启动心跳 private startHeartbeat() { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval) } this.heartbeatInterval = setInterval(() => { if (this.isConnected.value) { this.sendHeartbeat() } }, 30000) // 30秒发送一次心跳 } // 安排重连 private scheduleReconnect() { if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.error('WebSocket重连次数已达上限,停止重连') return } this.reconnectAttempts++ console.log(`WebSocket将在 ${this.reconnectInterval / 1000} 秒后尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`) setTimeout(() => { this.connect() }, this.reconnectInterval) // 递增重连间隔 this.reconnectInterval = Math.min(this.reconnectInterval * 1.5, 30000) } // 消息队列管理 private addToMessageQueue(message: any) { this.messageQueue.push(message) if (this.messageQueue.length > this.maxQueueSize) { this.messageQueue.shift() // 移除最旧的消息 } this.saveMessageQueue() } private processMessageQueue() { if (this.messageQueue.length > 0) { console.log(`处理离线期间的消息队列,共 ${this.messageQueue.length} 条消息`) const messages = [...this.messageQueue] this.messageQueue = [] messages.forEach(message => { this.processMessage(message) }) this.saveMessageQueue() } } private saveMessageQueue() { try { localStorage.setItem('websocket_message_queue', JSON.stringify(this.messageQueue)) } catch (error) { console.error('保存消息队列失败:', error) } } private loadMessageQueue() { try { const stored = localStorage.getItem('websocket_message_queue') if (stored) { this.messageQueue = JSON.parse(stored) } } catch (error) { console.error('加载消息队列失败:', error) } } // 消息存储管理 private saveMessageToStorage(message: WebSocketMessage) { try { const messages = this.getStoredMessages() messages.unshift(message) // 限制存储的消息数量 if (messages.length > 200) { messages.splice(200) } localStorage.setItem('websocket_messages', JSON.stringify(messages)) } catch (error) { console.error('保存消息到本地存储失败:', error) } } private getStoredMessages(): WebSocketMessage[] { try { const stored = localStorage.getItem('websocket_messages') return stored ? JSON.parse(stored) : [] } catch (error) { console.error('获取存储的消息失败:', error) return [] } } // 设置事件监听器 private setupEventListeners() { // 监听token变化,重新连接 window.addEventListener('storage', (event) => { if (event.key === 'token' && event.newValue !== this.token) { this.token = event.newValue || '' if (this.isConnected.value) { this.disconnect() this.connect() } } }) } // 事件系统 public on(event: string, callback: Function) { if (!this.eventListeners.has(event)) { this.eventListeners.set(event, []) } this.eventListeners.get(event)!.push(callback) } public off(event: string, callback: Function) { const listeners = this.eventListeners.get(event) if (listeners) { const index = listeners.indexOf(callback) if (index > -1) { listeners.splice(index, 1) } } } private emit(event: string, data: any) { const listeners = this.eventListeners.get(event) if (listeners) { listeners.forEach(callback => { try { callback(data) } catch (error) { console.error('WebSocket事件回调执行失败:', error) } }) } } // 获取连接状态 public getStatus() { return { connected: this.isConnected.value, connecting: this.isConnecting.value, reconnectAttempts: this.reconnectAttempts, maxReconnectAttempts: this.maxReconnectAttempts, messageQueueSize: this.messageQueue.length, processedMessagesCount: this.processedMessages.size } } // 更新认证token public updateToken(newToken: string) { this.token = newToken if (this.isConnected.value) { // 重新认证 this.send({ type: 'AUTH', data: { token: this.token }, timestamp: Date.now() }) } } // 获取存储的消息 public getMessages() { return this.getStoredMessages() } // 清空存储的消息 public clearMessages() { try { localStorage.removeItem('websocket_messages') localStorage.removeItem('websocket_message_queue') this.messageQueue = [] this.processedMessages.clear() } catch (error) { console.error('清空消息失败:', error) } } } // 创建单例实例 const websocketService = new WebSocketService() // 自动连接 websocketService.connect() export default websocketService