| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334 |
- import { ref, Ref } from 'vue'
- import { getAccessToken } from '@/utils/auth'
- interface WebSocketConfig {
- wsUrl: string
- onSOSAlert?: (data: any) => void
- onOrderAlert?: (data: any) => void
- onHealthAlert?: (data: any) => void
- onDeviceDataUpdate?: (data: any) => void
- onStatsUpdate?: (data: any) => void
- }
- export const useWebSocket = (config: WebSocketConfig) => {
- const socket: Ref<WebSocket | null> = ref(null)
- const isConnecting = ref(false)
- const connectionId: Ref<string | null> = ref(null)
- const reconnectAttempts = ref(0)
- const maxReconnectAttempts = ref(10)
- const lastActivityTime = ref('-')
- const heartbeatStatus = ref('normal')
- const lastHeartbeatTime: Ref<number | null> = ref(null)
- const lastHeartbeatAckTime: Ref<number | null> = ref(null)
- let heartbeatInterval: ReturnType<typeof setInterval> | null = null
- let heartbeatTimeout: ReturnType<typeof setTimeout> | null = null
- let reconnectTimeout: ReturnType<typeof setTimeout> | null = null
- let lastActivity = Date.now()
- const heartbeatIntervalTime = 25000
- const heartbeatTimeoutTime = 10000
- const updateLastActivity = () => {
- lastActivityTime.value = new Date().toLocaleTimeString()
- }
- const generateClientId = () => {
- const timestamp = Date.now()
- const random = Math.random().toString(36).substr(2, 9)
- return `monitor_${timestamp}_${random}`
- }
- const sendMessage = (message: any) => {
- if (socket.value && socket.value.readyState === WebSocket.OPEN) {
- try {
- socket.value.send(JSON.stringify(message))
- lastActivity = Date.now()
- updateLastActivity()
- return true
- } catch (error) {
- console.error('发送消息失败:', error)
- return false
- }
- }
- return false
- }
- const handleHeartbeatAck = (data: any) => {
- console.log('心跳消息', data)
- if (heartbeatTimeout) {
- clearTimeout(heartbeatTimeout)
- heartbeatTimeout = null
- }
- heartbeatStatus.value = 'normal'
- lastHeartbeatAckTime.value = Date.now()
- lastActivity = Date.now()
- updateLastActivity()
- }
- const handleHeartbeatExpired = () => {
- console.error('🚨 心跳已过期,关闭连接并重新连接')
- stopHeartbeat()
- if (socket.value) {
- socket.value.close(1000, '心跳过期')
- socket.value = null
- }
- reconnectAttempts.value = 0
- setTimeout(() => {
- if (!socket.value && !isConnecting.value) {
- connect()
- }
- }, 1000)
- }
- const handleHeartbeatTimeout = () => {
- console.warn('⏰ 心跳响应超时,关闭连接触发重连')
- stopHeartbeat()
- if (socket.value) {
- socket.value.close(1000, '心跳响应超时')
- }
- }
- const startHeartbeat = () => {
- stopHeartbeat()
- heartbeatStatus.value = 'normal'
- lastHeartbeatTime.value = Date.now()
- heartbeatInterval = setInterval(() => {
- if (!socket.value || socket.value.readyState !== WebSocket.OPEN) {
- heartbeatStatus.value = 'expired'
- stopHeartbeat()
- return
- }
- const now = Date.now()
- const timeSinceLastHeartbeat = now - (lastHeartbeatTime.value || 0)
- const totalTimeout = heartbeatIntervalTime + heartbeatTimeoutTime
- if (lastHeartbeatTime.value && timeSinceLastHeartbeat > totalTimeout) {
- console.warn(`💔 心跳已过期,距离上次心跳${Math.round(timeSinceLastHeartbeat / 1000)}秒`)
- heartbeatStatus.value = 'expired'
- handleHeartbeatExpired()
- return
- }
- heartbeatStatus.value = 'waiting'
- lastHeartbeatTime.value = now
- const params = {
- type: 'HEARTBEAT',
- timestamp: now,
- clientTime: now
- }
- const success = sendMessage(params)
- if (success) {
- if (heartbeatTimeout) {
- clearTimeout(heartbeatTimeout)
- }
- heartbeatTimeout = setTimeout(() => {
- console.warn('💔 心跳响应超时')
- heartbeatStatus.value = 'timeout'
- handleHeartbeatTimeout()
- }, heartbeatTimeoutTime)
- } else {
- heartbeatStatus.value = 'timeout'
- handleHeartbeatTimeout()
- }
- }, heartbeatIntervalTime)
- }
- const stopHeartbeat = () => {
- if (heartbeatInterval) {
- clearInterval(heartbeatInterval)
- heartbeatInterval = null
- }
- if (heartbeatTimeout) {
- clearTimeout(heartbeatTimeout)
- heartbeatTimeout = null
- }
- }
- const checkConnectionHealth = () => {
- if (!socket.value || socket.value.readyState !== WebSocket.OPEN) {
- return
- }
- const now = Date.now()
- const timeSinceLastActivity = now - lastActivity
- const timeSinceLastHeartbeat = now - (lastHeartbeatTime.value || 0)
- const totalTimeout = heartbeatIntervalTime + heartbeatTimeoutTime
- if (timeSinceLastActivity > totalTimeout + 10000) {
- console.error('🚨 连接长时间无活动')
- heartbeatStatus.value = 'expired'
- handleHeartbeatExpired()
- return
- }
- if (
- heartbeatStatus.value === 'waiting' &&
- timeSinceLastHeartbeat > heartbeatTimeoutTime + 5000
- ) {
- console.warn('⚠️ 心跳响应延迟')
- sendMessage({ type: 'PING', timestamp: now })
- }
- }
- const handleOpen = () => {
- isConnecting.value = false
- reconnectAttempts.value = 0
- lastActivity = Date.now()
- startHeartbeat()
- const postData = {
- type: 'AUTH',
- clientType: 'homecare-web',
- clientId: generateClientId(),
- timestamp: Date.now(),
- accessToken: `Bearer ${getAccessToken()}`
- }
- sendMessage(postData)
- }
- const handleMessage = (event: MessageEvent) => {
- try {
- lastActivity = Date.now()
- updateLastActivity()
- const data = JSON.parse(event.data)
- processIncomingData(data)
- } catch (error) {
- console.error('消息解析错误:', error)
- }
- }
- const processIncomingData = (data: any) => {
- if (!data || !data.type) return
- console.log('data', data)
- switch (data.type) {
- case 'CONNECT_SUCCESS':
- connectionId.value = data.connectionId
- break
- case 'AUTH_SUCCESS':
- console.log('身份验证成功')
- break
- case 'SOS_ALERT':
- config.onSOSAlert?.(data)
- break
- case 'HEALTH_ALERT':
- config.onHealthAlert?.(data)
- break
- case 'DEVICE_DATA_UPDATE':
- config.onDeviceDataUpdate?.(data)
- break
- case 'ORDER_ALERT':
- config.onOrderAlert?.(data)
- break
- case 'SYSTEM_STATS_UPDATE':
- config.onStatsUpdate?.(data)
- break
- case 'HEARTBEAT_ACK':
- handleHeartbeatAck(data)
- break
- default:
- lastActivity = Date.now()
- updateLastActivity()
- }
- }
- const handleClose = (event: CloseEvent) => {
- console.log(`WebSocket连接关闭: 代码 ${event.code}`)
- isConnecting.value = false
- socket.value = null
- connectionId.value = null
- heartbeatStatus.value = 'expired'
- stopHeartbeat()
- if (reconnectTimeout) {
- clearTimeout(reconnectTimeout)
- reconnectTimeout = null
- }
- if (event.code !== 1000 && reconnectAttempts.value < maxReconnectAttempts.value) {
- reconnectAttempts.value++
- const delay = Math.min(3000 * Math.pow(1.5, reconnectAttempts.value - 1), 30000)
- console.log(`${Math.round(delay / 1000)}秒后尝试重连`)
- reconnectTimeout = setTimeout(() => {
- if (!socket.value && !isConnecting.value) {
- connect()
- }
- }, delay)
- } else if (reconnectAttempts.value >= maxReconnectAttempts.value) {
- console.error('已达到最大重连次数')
- heartbeatStatus.value = 'expired'
- }
- }
- const handleError = (event: Event) => {
- console.error('WebSocket错误:', event)
- }
- const connect = () => {
- if (isConnecting.value || socket.value) {
- return
- }
- isConnecting.value = true
- if (reconnectTimeout) {
- clearTimeout(reconnectTimeout)
- reconnectTimeout = null
- }
- try {
- const clientId = generateClientId()
- const wsUrl = config.wsUrl + clientId
- socket.value = new WebSocket(wsUrl)
- socket.value.onopen = handleOpen
- socket.value.onmessage = handleMessage
- socket.value.onclose = handleClose
- socket.value.onerror = handleError
- } catch (error) {
- console.error('连接创建错误:', error)
- isConnecting.value = false
- socket.value = null
- if (reconnectAttempts.value < maxReconnectAttempts.value) {
- reconnectAttempts.value++
- const delay = Math.min(3000 * Math.pow(1.5, reconnectAttempts.value - 1), 30000)
- reconnectTimeout = setTimeout(() => {
- if (!socket.value && !isConnecting.value) {
- connect()
- }
- }, delay)
- }
- }
- }
- const disconnect = () => {
- stopHeartbeat()
- if (reconnectTimeout) {
- clearTimeout(reconnectTimeout)
- reconnectTimeout = null
- }
- if (socket.value) {
- socket.value.close(1000, '主动关闭')
- socket.value = null
- }
- }
- return {
- socket,
- isConnecting,
- connectionId,
- reconnectAttempts,
- lastActivityTime,
- heartbeatStatus,
- lastHeartbeatTime,
- lastHeartbeatAckTime,
- connect,
- disconnect,
- sendMessage,
- checkConnectionHealth,
- stopHeartbeat
- }
- }
|