chore: initialize insar management system v2

This commit is contained in:
2026-04-14 13:16:01 +08:00
commit ecc72ec9cd
361 changed files with 2142522 additions and 0 deletions
+206
View File
@@ -0,0 +1,206 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import apiClient from '../api/client';
import { normalizeTaskStatus } from '../utils/appUiHelpers';
export default function useGlobalTaskControl({
currentUser,
licenseOk,
activeTasks,
setActiveTasks,
pendingTaskIds,
setPendingTaskIds,
isGlobalLocked,
setIsGlobalLocked,
setIsCheckingTasks,
handleTaskCompletionRef,
initializeAppDataRef,
addLog,
}) {
const [forceUnlockPwd, setForceUnlockPwd] = useState('');
const [showForceUnlock, setShowForceUnlock] = useState(false);
const lastLockTimeRef = useRef(null);
const isGlobalLockedRef = useRef(isGlobalLocked);
useEffect(() => {
isGlobalLockedRef.current = isGlobalLocked;
if (isGlobalLocked) {
lastLockTimeRef.current = Date.now();
}
}, [isGlobalLocked]);
// Stable refs so SSE handler doesn't need to re-subscribe on every render
const pendingTaskIdsRef = useRef(pendingTaskIds);
useEffect(() => { pendingTaskIdsRef.current = pendingTaskIds; }, [pendingTaskIds]);
const handleTasksUpdate = useCallback(async (tasks) => {
setActiveTasks(tasks);
const hasRunningTasks = tasks.length > 0;
// 首次检查完成,清除检查状态
setIsCheckingTasks(false);
const currentPending = pendingTaskIdsRef.current;
let updatedPending = currentPending;
// 如果 pendingTaskIds 为空,但 activeTasks 有任务,说明是刷新后的初始化
// 需要将 activeTasks 中的任务添加到 pendingTaskIds
if (currentPending.length === 0 && hasRunningTasks) {
const activeTaskIds = tasks.map((t) => t.task_id);
console.log('初始化:将活跃任务添加到 pending 列表:', activeTaskIds);
setPendingTaskIds(activeTaskIds);
updatedPending = activeTaskIds;
}
if (currentPending.length > 0) {
const currentTaskIds = new Set(tasks.map((t) => t.task_id));
const finishedTaskIds = currentPending.filter((id) => !currentTaskIds.has(id));
if (finishedTaskIds.length > 0) {
console.log('检测到可能已结束的任务:', finishedTaskIds);
const reallyFinishedIds = [];
for (const taskId of finishedTaskIds) {
try {
const statusRes = await apiClient.get(`/tasks/${taskId}`);
const taskInfo = statusRes.data;
const taskStatus = normalizeTaskStatus(taskInfo?.status);
// 检查任务是否真正完成:解析 message 中的进度信息
let isReallyFinished = taskStatus === 'COMPLETED' || taskStatus === 'FAILED';
// 如果任务状态是 PENDING,检查进度信息
if (taskStatus === 'PENDING' && taskInfo?.message) {
// 匹配格式:(current/total)
const match = taskInfo.message.match(/\((\d+)\/(\d+)\)/);
if (match) {
const current = parseInt(match[1], 10);
const total = parseInt(match[2], 10);
// 如果还没处理完,任务还在运行
if (current < total) {
console.log(`任务 ${taskId} 还在运行,进度: ${current}/${total}`);
isReallyFinished = false;
} else {
console.log(`任务 ${taskId} 进度已完成: ${current}/${total}`);
}
}
}
if (isReallyFinished) {
reallyFinishedIds.push(taskId);
if (taskStatus === 'COMPLETED' || taskStatus === 'FAILED') {
handleTaskCompletionRef.current?.(taskInfo);
}
}
} catch (error) {
console.error(`获取任务 ${taskId} 结果失败:`, error);
// 查询失败时,保守处理:认为任务已完成
reallyFinishedIds.push(taskId);
}
}
if (reallyFinishedIds.length > 0) {
console.log('真正完成的任务:', reallyFinishedIds);
setPendingTaskIds((prev) => {
const newPending = prev.filter((id) => !reallyFinishedIds.includes(id));
updatedPending = newPending;
return newPending;
});
} else {
// 没有真正完成的任务,保持 updatedPending 不变
updatedPending = currentPending;
}
}
}
// 只有当没有运行中的任务且没有待处理的任务时,才解锁
const shouldBeLocked = hasRunningTasks || updatedPending.length > 0;
if (shouldBeLocked !== isGlobalLockedRef.current) {
setIsGlobalLocked(shouldBeLocked);
if (!shouldBeLocked) {
addLog('success', '后台任务已完成,正在同步最新数据...');
setTimeout(() => {
initializeAppDataRef.current?.({ refreshRadarSearch: true });
}, 500);
}
}
}, [
setActiveTasks,
setIsCheckingTasks,
handleTaskCompletionRef,
setPendingTaskIds,
setIsGlobalLocked,
addLog,
initializeAppDataRef,
]);
// Fallback polling (used when SSE is unavailable)
const syncActiveTasks = useCallback(async () => {
try {
const response = await apiClient.get('/tasks/active');
const tasks = Array.isArray(response.data) ? response.data : [];
await handleTasksUpdate(tasks);
} catch (error) {
console.error('同步任务状态失败:', error);
}
}, [handleTasksUpdate]);
useEffect(() => {
if (!currentUser || !licenseOk) return;
// Initial fetch
syncActiveTasks();
// Try SSE first; fall back to polling on error
let es = null;
let fallbackInterval = null;
const startSSE = () => {
const baseURL = apiClient.defaults.baseURL || '';
es = new EventSource(`${baseURL}/tasks/active/stream`);
es.onmessage = (event) => {
try {
const tasks = JSON.parse(event.data);
handleTasksUpdate(Array.isArray(tasks) ? tasks : []);
} catch (e) {
console.error('SSE parse error:', e);
}
};
es.onerror = () => {
console.warn('SSE 连接断开,降级为轮询模式');
es.close();
es = null;
if (!fallbackInterval) {
fallbackInterval = setInterval(syncActiveTasks, 5000);
}
};
};
startSSE();
return () => {
if (es) es.close();
if (fallbackInterval) clearInterval(fallbackInterval);
};
}, [currentUser, licenseOk, syncActiveTasks, handleTasksUpdate]);
const handleForceUnlock = useCallback(() => {
if (!forceUnlockPwd || activeTasks.length === 0) return;
Promise.all(activeTasks.map((task) =>
apiClient.post(`/tasks/${task.task_id}/force-cancel`, { password: forceUnlockPwd }).catch(() => {})
)).then(() => {
setForceUnlockPwd('');
setShowForceUnlock(false);
syncActiveTasks();
});
}, [activeTasks, forceUnlockPwd, syncActiveTasks]);
return {
forceUnlockPwd,
setForceUnlockPwd,
showForceUnlock,
setShowForceUnlock,
handleForceUnlock,
};
}