mirror of
https://github.com/NanmiCoder/MediaCrawler.git
synced 2026-07-28 23:40:30 +08:00
- 新增 webui/ 前端源码目录(React+Vite+TypeScript+Tailwind) - 删除 api/webui/ 中旧的打包静态资源 - 更新 .gitignore 忽略 api/webui/ 构建输出和 webui/node_modules - 更新 README 增加前端构建说明
112 lines
2.9 KiB
TypeScript
112 lines
2.9 KiB
TypeScript
import { useEffect, useRef } from 'react'
|
|
import { useCrawlerStore } from '@/store/crawlerStore'
|
|
import type { LogEntry } from '@/types/crawler'
|
|
|
|
// 模块级单例,确保全局只有一个 WebSocket 连接
|
|
let globalWs: WebSocket | null = null
|
|
let globalReconnectTimer: ReturnType<typeof setTimeout> | null = null
|
|
let connectionCount = 0 // 跟踪连接使用者数量
|
|
|
|
export function useLogWebSocket() {
|
|
const addLog = useCrawlerStore((state) => state.addLog)
|
|
const addLogRef = useRef(addLog)
|
|
|
|
// 保持 addLog 引用最新
|
|
useEffect(() => {
|
|
addLogRef.current = addLog
|
|
}, [addLog])
|
|
|
|
useEffect(() => {
|
|
connectionCount++
|
|
|
|
const connect = () => {
|
|
if (globalReconnectTimer) {
|
|
clearTimeout(globalReconnectTimer)
|
|
globalReconnectTimer = null
|
|
}
|
|
|
|
// 如果已经连接或正在连接,不重复创建
|
|
if (globalWs && (globalWs.readyState === WebSocket.OPEN || globalWs.readyState === WebSocket.CONNECTING)) {
|
|
return
|
|
}
|
|
|
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
const host = window.location.host
|
|
const wsUrl = `${protocol}//${host}/api/ws/logs`
|
|
|
|
const ws = new WebSocket(wsUrl)
|
|
globalWs = ws
|
|
|
|
ws.onopen = () => {
|
|
if (globalWs !== ws) return
|
|
console.log('WebSocket connected')
|
|
}
|
|
|
|
ws.onmessage = (event) => {
|
|
if (globalWs !== ws) return
|
|
if (event.data === 'ping') {
|
|
ws.send('pong')
|
|
return
|
|
}
|
|
if (event.data === 'pong') {
|
|
return
|
|
}
|
|
|
|
try {
|
|
const log: LogEntry = JSON.parse(event.data)
|
|
if (log.id && log.message) {
|
|
addLogRef.current(log)
|
|
}
|
|
} catch (e) {
|
|
console.warn('Failed to parse WebSocket message:', event.data)
|
|
}
|
|
}
|
|
|
|
ws.onclose = () => {
|
|
if (globalWs !== ws) return
|
|
console.log('WebSocket disconnected')
|
|
globalWs = null
|
|
// 只在还有使用者时才重连
|
|
if (connectionCount > 0) {
|
|
globalReconnectTimer = setTimeout(connect, 2000)
|
|
}
|
|
}
|
|
|
|
ws.onerror = (error) => {
|
|
if (globalWs !== ws) return
|
|
console.error('WebSocket error:', error)
|
|
}
|
|
}
|
|
|
|
// 首次连接
|
|
connect()
|
|
|
|
// Heartbeat
|
|
const heartbeat = setInterval(() => {
|
|
if (globalWs && globalWs.readyState === WebSocket.OPEN) {
|
|
globalWs.send('ping')
|
|
}
|
|
}, 30000)
|
|
|
|
return () => {
|
|
connectionCount--
|
|
clearInterval(heartbeat)
|
|
|
|
// 只在没有使用者时才断开连接
|
|
if (connectionCount === 0) {
|
|
if (globalReconnectTimer) {
|
|
clearTimeout(globalReconnectTimer)
|
|
globalReconnectTimer = null
|
|
}
|
|
if (globalWs) {
|
|
const ws = globalWs
|
|
globalWs = null
|
|
ws.close()
|
|
}
|
|
}
|
|
}
|
|
}, []) // 空依赖数组
|
|
|
|
return { ws: globalWs }
|
|
}
|