feat(douyu): 精英宝典支持多批次并发与多二维码并行扫码
- useWebSocketLogs 改为多连接(Map),新增 connectBatch/closeBatch/closeAll,旧 connect/close 向后兼容 - DouyuTasksPage runningBatchId 单值改为 Set 集合,移除 batchBusy 全局锁,右键菜单按账号维度禁用 - 二维码弹窗支持多账号标签切换并行扫码,恢复三弹窗互斥 - connectBatch 已有连接时不清日志,修复 onerror 不更新 connected - 修复自动弹出循环覆盖 active 标签的 bug
This commit is contained in:
@@ -13,6 +13,7 @@ interface ConnectOptions {
|
||||
}
|
||||
|
||||
const MAX_LOGS = 1000;
|
||||
const DEFAULT_KEY = '__default__';
|
||||
|
||||
function toWebSocketUrl(pathOrUrl: string): string {
|
||||
if (pathOrUrl.startsWith('ws://') || pathOrUrl.startsWith('wss://')) {
|
||||
@@ -26,43 +27,59 @@ function toWebSocketUrl(pathOrUrl: string): string {
|
||||
export function useWebSocketLogs() {
|
||||
const [logs, setLogs] = useState<RealtimeLog[]>([]);
|
||||
const [connected, setConnected] = useState(false);
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const callbacksRef = useRef<ConnectOptions>({});
|
||||
const suppressCloseRef = useRef(false);
|
||||
const wsMapRef = useRef<Map<string, WebSocket>>(new Map());
|
||||
const callbacksMapRef = useRef<Map<string, ConnectOptions>>(new Map());
|
||||
const suppressCloseRef = useRef<Set<string>>(new Set());
|
||||
|
||||
const syncConnected = useCallback(() => {
|
||||
setConnected(wsMapRef.current.size > 0);
|
||||
}, []);
|
||||
|
||||
const clearLogs = useCallback(() => {
|
||||
setLogs([]);
|
||||
}, []);
|
||||
|
||||
const close = useCallback((notify = false) => {
|
||||
if (!wsRef.current) {
|
||||
setConnected(false);
|
||||
return;
|
||||
}
|
||||
suppressCloseRef.current = !notify;
|
||||
wsRef.current.close();
|
||||
wsRef.current = null;
|
||||
setConnected(false);
|
||||
}, []);
|
||||
const closeBatch = useCallback((key: string, notify = false) => {
|
||||
const ws = wsMapRef.current.get(key);
|
||||
if (!ws) return;
|
||||
if (!notify) suppressCloseRef.current.add(key);
|
||||
ws.close();
|
||||
wsMapRef.current.delete(key);
|
||||
syncConnected();
|
||||
}, [syncConnected]);
|
||||
|
||||
const connect = useCallback((pathOrUrl: string, options: ConnectOptions = {}) => {
|
||||
close(false);
|
||||
suppressCloseRef.current = false;
|
||||
callbacksRef.current = options;
|
||||
if (options.clear ?? true) {
|
||||
// 向后兼容:单连接场景关闭默认通道。
|
||||
const close = useCallback((notify = false) => {
|
||||
closeBatch(DEFAULT_KEY, notify);
|
||||
}, [closeBatch]);
|
||||
|
||||
const closeAll = useCallback((notify = false) => {
|
||||
Array.from(wsMapRef.current.keys()).forEach((key) => closeBatch(key, notify));
|
||||
}, [closeBatch]);
|
||||
|
||||
const connectBatch = useCallback((key: string, pathOrUrl: string, options: ConnectOptions = {}) => {
|
||||
// 关闭同 key 旧连接,避免重复
|
||||
if (wsMapRef.current.has(key)) {
|
||||
closeBatch(key, false);
|
||||
}
|
||||
suppressCloseRef.current.delete(key);
|
||||
callbacksMapRef.current.set(key, options);
|
||||
// 已有其他批次连接时不清空日志,避免冲掉正在进行的批次日志
|
||||
const shouldClear = (options.clear ?? true) && wsMapRef.current.size === 0;
|
||||
if (shouldClear) {
|
||||
setLogs([]);
|
||||
}
|
||||
|
||||
const ws = new WebSocket(toWebSocketUrl(pathOrUrl));
|
||||
wsRef.current = ws;
|
||||
setConnected(true);
|
||||
wsMapRef.current.set(key, ws);
|
||||
syncConnected();
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const msg = JSON.parse(event.data) as RealtimeLog;
|
||||
if (msg.level === 'heartbeat') return;
|
||||
if (msg.level === 'result') {
|
||||
callbacksRef.current.onResult?.();
|
||||
callbacksMapRef.current.get(key)?.onResult?.();
|
||||
return;
|
||||
}
|
||||
setLogs((prev) => [...prev, msg].slice(-MAX_LOGS));
|
||||
@@ -72,27 +89,32 @@ export function useWebSocketLogs() {
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
const isCurrent = wsRef.current === ws;
|
||||
const shouldNotify = isCurrent && !suppressCloseRef.current;
|
||||
if (isCurrent) {
|
||||
wsRef.current = null;
|
||||
setConnected(false);
|
||||
suppressCloseRef.current = false;
|
||||
}
|
||||
if (shouldNotify) {
|
||||
callbacksRef.current.onClose?.();
|
||||
// 只处理自己这个实例的关闭,避免覆盖重连后的新连接
|
||||
if (wsMapRef.current.get(key) !== ws) return;
|
||||
wsMapRef.current.delete(key);
|
||||
syncConnected();
|
||||
const cb = callbacksMapRef.current.get(key);
|
||||
if (!suppressCloseRef.current.has(key)) {
|
||||
cb?.onClose?.();
|
||||
}
|
||||
callbacksMapRef.current.delete(key);
|
||||
suppressCloseRef.current.delete(key);
|
||||
};
|
||||
|
||||
ws.onerror = () => {
|
||||
setConnected(false);
|
||||
callbacksRef.current.onError?.();
|
||||
if (wsMapRef.current.get(key) === ws) setConnected(false);
|
||||
callbacksMapRef.current.get(key)?.onError?.();
|
||||
};
|
||||
}, [close]);
|
||||
}, [closeBatch, syncConnected]);
|
||||
|
||||
// 向后兼容:connect(pathOrUrl, options) 等价于 connectBatch(DEFAULT_KEY, ...)
|
||||
const connect = useCallback((pathOrUrl: string, options: ConnectOptions = {}) => {
|
||||
connectBatch(DEFAULT_KEY, pathOrUrl, options);
|
||||
}, [connectBatch]);
|
||||
|
||||
useEffect(() => () => {
|
||||
close(false);
|
||||
}, [close]);
|
||||
closeAll(false);
|
||||
}, [closeAll]);
|
||||
|
||||
return {
|
||||
logs,
|
||||
@@ -100,5 +122,8 @@ export function useWebSocketLogs() {
|
||||
clearLogs,
|
||||
connect,
|
||||
close,
|
||||
connectBatch,
|
||||
closeBatch,
|
||||
closeAll,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user