Industrial-image-management.../src/services/websocketService.ts

788 lines
23 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<string, Function[]> = new Map()
// 消息队列 - 用于离线时缓存消息
private messageQueue: WebSocketMessage[] = []
private maxQueueSize = 100
// 消息去重 - 避免重复处理
private processedMessages = new Set<string>()
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