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

788 lines
23 KiB
TypeScript
Raw Normal View History

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