mirror of
https://github.com/NanmiCoder/MediaCrawler.git
synced 2026-07-28 23:40:30 +08:00
refactor: 将 WebUI 源码纳入仓库,移除打包产物
- 新增 webui/ 前端源码目录(React+Vite+TypeScript+Tailwind) - 删除 api/webui/ 中旧的打包静态资源 - 更新 .gitignore 忽略 api/webui/ 构建输出和 webui/node_modules - 更新 README 增加前端构建说明
This commit is contained in:
111
webui/src/hooks/useWebSocket.ts
Normal file
111
webui/src/hooks/useWebSocket.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
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 }
|
||||
}
|
||||
Reference in New Issue
Block a user