通知板块 及 功能调整
This commit is contained in:
+151
-78
@@ -3,56 +3,38 @@ import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
type JsonValue = Record<string, any> | any[]
|
||||
|
||||
const WS_BASE = 'wss://mzl.dev.hnxinyouhe.com/ws?token='
|
||||
const WS_BASE = import.meta.env.VITE_API_URL_WSS + '/ws?token='
|
||||
const MAX_RECONNECT_ATTEMPTS = 10
|
||||
const MAX_RECONNECT_DELAY = 50000
|
||||
const HEARTBEAT_INTERVAL = 50000
|
||||
|
||||
let socket: WebSocket | null = null
|
||||
let reconnectTimer: any = null
|
||||
let heartbeatTimer: any = null
|
||||
let reconnectAttempts = 0
|
||||
const maxReconnectDelayMs = 30000
|
||||
const heartbeatIntervalMs = 30000
|
||||
let isInitialConnection = true
|
||||
|
||||
// 生成全局唯一的 trace_id(优先使用 crypto.randomUUID)
|
||||
// 生成唯一ID
|
||||
const generateTraceId = (): string => {
|
||||
try {
|
||||
// @ts-ignore
|
||||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||
// @ts-ignore
|
||||
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
||||
return `req-${crypto.randomUUID()}`
|
||||
}
|
||||
} catch (_) {}
|
||||
// Fallback:基于时间戳 + 随机数
|
||||
const timestampPart = Date.now().toString(36)
|
||||
let randomPart = ''
|
||||
try {
|
||||
// @ts-ignore
|
||||
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
||||
const buf = new Uint8Array(16)
|
||||
// @ts-ignore
|
||||
crypto.getRandomValues(buf)
|
||||
randomPart = Array.from(buf)
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('')
|
||||
} else {
|
||||
randomPart = Array.from({ length: 16 })
|
||||
.map(() =>
|
||||
Math.floor(Math.random() * 256)
|
||||
.toString(16)
|
||||
.padStart(2, '0')
|
||||
)
|
||||
.join('')
|
||||
}
|
||||
} catch (_) {
|
||||
randomPart = Math.random().toString(36).slice(2)
|
||||
} catch {
|
||||
// 忽略错误,使用fallback方案
|
||||
}
|
||||
return `req-${timestampPart}-${randomPart}`
|
||||
const timestamp = Date.now().toString(36)
|
||||
const random = Math.random().toString(36).slice(2)
|
||||
return `req-${timestamp}-${random}`
|
||||
}
|
||||
|
||||
// 计算重连延迟
|
||||
const getReconnectDelay = () => {
|
||||
const base = 1000 * Math.pow(2, reconnectAttempts)
|
||||
return Math.min(base, maxReconnectDelayMs)
|
||||
return Math.min(base, MAX_RECONNECT_DELAY)
|
||||
}
|
||||
|
||||
// 清理定时器
|
||||
const cleanup = () => {
|
||||
if (reconnectTimer) {
|
||||
clearTimeout(reconnectTimer)
|
||||
@@ -64,107 +46,198 @@ const cleanup = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 启动心跳
|
||||
const startHeartbeat = () => {
|
||||
// 清理上一次的心跳
|
||||
if (heartbeatTimer) {
|
||||
clearTimeout(heartbeatTimer)
|
||||
heartbeatTimer = null
|
||||
}
|
||||
// 递归 setTimeout,确保固定间隔执行
|
||||
heartbeatTimer = setTimeout(() => {
|
||||
try {
|
||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||
if (socket?.readyState === WebSocket.OPEN) {
|
||||
const msg = {
|
||||
event: 'system.ping',
|
||||
data: {},
|
||||
trace_id: generateTraceId() // 可选,用于请求-响应匹配
|
||||
trace_id: generateTraceId()
|
||||
}
|
||||
socket.send(JSON.stringify(msg))
|
||||
}
|
||||
} catch (_) {}
|
||||
// 下一次心跳
|
||||
} catch {
|
||||
// 忽略心跳发送错误
|
||||
}
|
||||
startHeartbeat()
|
||||
}, heartbeatIntervalMs)
|
||||
}, HEARTBEAT_INTERVAL)
|
||||
}
|
||||
|
||||
// 发送心跳消息
|
||||
const sendPing = () => {
|
||||
try {
|
||||
if (socket?.readyState === WebSocket.OPEN) {
|
||||
const msg = {
|
||||
event: 'system.ping',
|
||||
data: {},
|
||||
trace_id: generateTraceId()
|
||||
}
|
||||
socket.send(JSON.stringify(msg))
|
||||
}
|
||||
} catch {
|
||||
// 忽略发送错误
|
||||
}
|
||||
}
|
||||
|
||||
// 处理WebSocket消息
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
let data: any = event.data
|
||||
try {
|
||||
data = JSON.parse(event.data)
|
||||
} catch {
|
||||
// 忽略解析错误
|
||||
}
|
||||
console.log('WebSocket message:', data)
|
||||
if (data.event == 'notify.receive') {
|
||||
ElNotification({
|
||||
title: data.data.notice_data.message.title || '通知',
|
||||
message: data.data.notice_data.message.content,
|
||||
type: 'primary'
|
||||
})
|
||||
mittBus.emit('notification-data-changed', 1)
|
||||
}
|
||||
mittBus.emit('ws:message', data)
|
||||
}
|
||||
|
||||
// 设置WebSocket事件处理器
|
||||
const setupWebSocketHandlers = (ws: WebSocket) => {
|
||||
ws.onopen = () => {
|
||||
isInitialConnection = false
|
||||
startHeartbeat()
|
||||
mittBus.emit('ws:open')
|
||||
sendPing()
|
||||
}
|
||||
|
||||
ws.onmessage = handleMessage
|
||||
|
||||
ws.onerror = (err) => {
|
||||
mittBus.emit('ws:error', err)
|
||||
}
|
||||
|
||||
ws.onclose = () => {
|
||||
mittBus.emit('ws:close')
|
||||
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
||||
scheduleReconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 安排重连
|
||||
const scheduleReconnect = () => {
|
||||
if (!isInitialConnection) {
|
||||
reconnectAttempts += 1
|
||||
}
|
||||
|
||||
if (reconnectAttempts > MAX_RECONNECT_ATTEMPTS) {
|
||||
console.warn(`WebSocket重连失败,已达到最大重连次数(${MAX_RECONNECT_ATTEMPTS}),停止重连`)
|
||||
mittBus.emit('ws:max-reconnect-reached')
|
||||
return
|
||||
}
|
||||
|
||||
cleanup()
|
||||
const delay = getReconnectDelay()
|
||||
console.log(`WebSocket重连尝试 ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS},延迟: ${delay}ms`)
|
||||
reconnectTimer = setTimeout(() => {
|
||||
const { accessToken } = useUserStore()
|
||||
if (accessToken) initWebSocket(accessToken)
|
||||
if (accessToken) {
|
||||
try {
|
||||
if (socket) {
|
||||
try {
|
||||
socket.close()
|
||||
} catch {
|
||||
// 忽略关闭错误
|
||||
}
|
||||
socket = null
|
||||
}
|
||||
|
||||
const url = `${WS_BASE}${accessToken}`
|
||||
socket = new WebSocket(url)
|
||||
setupWebSocketHandlers(socket)
|
||||
} catch (e) {
|
||||
mittBus.emit('ws:error', e)
|
||||
if (reconnectAttempts <= MAX_RECONNECT_ATTEMPTS) {
|
||||
scheduleReconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
}, delay)
|
||||
}
|
||||
|
||||
// 初始化WebSocket连接
|
||||
export const initWebSocket = (token: string) => {
|
||||
try {
|
||||
if (!token) return
|
||||
if (socket) {
|
||||
try {
|
||||
socket.close()
|
||||
} catch (_) {}
|
||||
} catch {
|
||||
// 忽略关闭错误
|
||||
}
|
||||
socket = null
|
||||
}
|
||||
cleanup()
|
||||
reconnectAttempts = 0
|
||||
isInitialConnection = true
|
||||
|
||||
const url = `${WS_BASE}${token}`
|
||||
socket = new WebSocket(url)
|
||||
|
||||
socket.onopen = () => {
|
||||
reconnectAttempts = 0
|
||||
startHeartbeat()
|
||||
mittBus.emit('ws:open')
|
||||
try {
|
||||
const msg = {
|
||||
event: 'system.ping',
|
||||
data: {},
|
||||
trace_id: generateTraceId() // 可选,用于请求-响应匹配
|
||||
}
|
||||
socket?.send(JSON.stringify(msg))
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
const dataStr = event.data
|
||||
let data: any = dataStr
|
||||
try {
|
||||
data = JSON.parse(dataStr)
|
||||
} catch (_) {}
|
||||
mittBus.emit('ws:message', data)
|
||||
}
|
||||
|
||||
socket.onerror = (err) => {
|
||||
mittBus.emit('ws:error', err)
|
||||
}
|
||||
|
||||
socket.onclose = () => {
|
||||
mittBus.emit('ws:close')
|
||||
reconnectAttempts += 1
|
||||
scheduleReconnect()
|
||||
}
|
||||
setupWebSocketHandlers(socket)
|
||||
} catch (e) {
|
||||
mittBus.emit('ws:error', e)
|
||||
scheduleReconnect()
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭WebSocket连接
|
||||
export const closeWebSocket = () => {
|
||||
cleanup()
|
||||
try {
|
||||
socket?.close()
|
||||
} catch (_) {}
|
||||
} catch {
|
||||
// 忽略关闭错误
|
||||
}
|
||||
socket = null
|
||||
reconnectAttempts = 0
|
||||
isInitialConnection = true
|
||||
}
|
||||
|
||||
// 发送WebSocket消息
|
||||
export const sendWebSocket = (payload: string | JsonValue) => {
|
||||
if (!socket || socket.readyState !== WebSocket.OPEN) return false
|
||||
try {
|
||||
const data = typeof payload === 'string' ? payload : JSON.stringify(payload)
|
||||
socket.send(data)
|
||||
return true
|
||||
} catch (_) {
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 重置重连次数
|
||||
export const resetWebSocketReconnect = () => {
|
||||
reconnectAttempts = 0
|
||||
isInitialConnection = true
|
||||
console.log('WebSocket重连次数已重置')
|
||||
}
|
||||
|
||||
// 获取当前重连次数
|
||||
export const getWebSocketReconnectAttempts = () => reconnectAttempts
|
||||
|
||||
// 获取最大重连次数
|
||||
export const getMaxReconnectAttempts = () => MAX_RECONNECT_ATTEMPTS
|
||||
|
||||
// 获取WebSocket连接状态
|
||||
export const getWebSocketStatus = () => ({
|
||||
socket,
|
||||
readyState: socket?.readyState,
|
||||
reconnectAttempts,
|
||||
maxReconnectAttempts: MAX_RECONNECT_ATTEMPTS,
|
||||
isReconnecting: reconnectTimer !== null,
|
||||
isHeartbeating: heartbeatTimer !== null,
|
||||
isInitialConnection
|
||||
})
|
||||
Reference in New Issue
Block a user