fix(backend): Resolve PgBoss infinite loop issue and cleanup unused files

Backend fixes:
- Fix PgBoss task infinite loop on SAE (root cause: missing queue table constraints)
- Add singletonKey to prevent duplicate job enqueueing
- Add idempotency check in reviewWorker (skip completed tasks)
- Add optimistic locking in reviewService (atomic status update)

Frontend fixes:
- Add isSubmitting state to prevent duplicate submissions in RVW Dashboard
- Fix API baseURL in knowledgeBaseApi (relative path)

Cleanup (removed):
- Old frontend/ directory (migrated to frontend-v2)
- python-microservice/ (unused, replaced by extraction_service)
- Root package.json and node_modules (accidentally created)
- redcap-docker-dev/ (external dependency)
- Various temporary files and outdated docs in root

New documentation:
- docs/07-运维文档/01-PgBoss队列监控与维护.md
- docs/07-运维文档/02-故障预防检查清单.md
- docs/07-运维文档/03-数据库迁移注意事项.md

Database fix applied to RDS:
- Added PRIMARY KEY to platform_schema.queue
- Added 3 missing foreign key constraints

Tested: Local build passed, RDS constraints verified
This commit is contained in:
2026-01-27 18:16:22 +08:00
parent 2481b786d8
commit bbf98c4d5c
214 changed files with 4318 additions and 44920 deletions

View File

@@ -169,5 +169,4 @@ export const getAvailableModulesByCode = (userModuleCodes: string[]): ModuleDefi
// 检查用户是否有该模块的访问权限
return userModuleCodes.includes(module.moduleCode);
});
}
}

View File

@@ -1,15 +1,20 @@
/**
* PKB个人知识库 API
* API路径: /api/v1/pkb/knowledge
*
* 修复说明2026-01-27
* - 移除硬编码的 localhost:3000改用相对路径
* - 使用相对路径后Nginx 会自动代理到后端服务
*/
import axios from 'axios';
import { getAccessToken } from '../../../framework/auth/api';
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000';
// 使用相对路径,生产环境由 Nginx 代理到后端
const API_BASE_URL = '/api/v1/pkb/knowledge';
const api = axios.create({
baseURL: `${API_BASE_URL}/api/v1/pkb/knowledge`,
baseURL: API_BASE_URL,
timeout: 30000,
headers: {
'Content-Type': 'application/json',

View File

@@ -10,9 +10,10 @@ interface AgentModalProps {
taskCount: number;
onClose: () => void;
onConfirm: (agents: AgentType[]) => void;
isSubmitting?: boolean; // 🔒 防止重复提交
}
export default function AgentModal({ visible, taskCount, onClose, onConfirm }: AgentModalProps) {
export default function AgentModal({ visible, taskCount, onClose, onConfirm, isSubmitting = false }: AgentModalProps) {
const [selectedAgents, setSelectedAgents] = useState<AgentType[]>(['editorial']);
const toggleAgent = (agent: AgentType) => {
@@ -110,10 +111,10 @@ export default function AgentModal({ visible, taskCount, onClose, onConfirm }: A
</button>
<button
onClick={handleConfirm}
disabled={selectedAgents.length === 0}
disabled={selectedAgents.length === 0 || isSubmitting}
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-bold rounded-lg shadow-sm transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{isSubmitting ? '提交中...' : '立即运行'}
</button>
</div>
</div>
@@ -140,6 +141,9 @@ export default function AgentModal({ visible, taskCount, onClose, onConfirm }: A

View File

@@ -26,6 +26,7 @@ export default function Dashboard() {
const [filters, setFilters] = useState<TaskFilters>({ status: 'all', timeRange: 'all' });
const [agentModalVisible, setAgentModalVisible] = useState(false);
const [pendingTaskForRun, setPendingTaskForRun] = useState<ReviewTask | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false); // 🔒 防止重复提交
// 报告详情
const [reportDetail, setReportDetail] = useState<ReviewReport | null>(null);
@@ -119,12 +120,19 @@ export default function Dashboard() {
};
const handleConfirmRun = async (agents: AgentType[]) => {
// 🔒 防止重复提交
if (isSubmitting) {
console.warn('[Dashboard] 已在提交中,忽略重复请求');
return;
}
// 🔥 保存到局部变量避免onClose后丢失
const taskToRun = pendingTaskForRun;
// 立即关闭弹窗
// 立即关闭弹窗并设置提交状态
setAgentModalVisible(false);
setPendingTaskForRun(null);
setIsSubmitting(true); // 🔒 锁定
try {
if (taskToRun) {
@@ -159,6 +167,8 @@ export default function Dashboard() {
loadTasks();
} catch (error: any) {
message.error({ content: error.message || '启动失败', key: 'run', duration: 3 });
} finally {
setIsSubmitting(false); // 🔓 解锁
}
};
@@ -277,6 +287,7 @@ export default function Dashboard() {
setPendingTaskForRun(null);
}}
onConfirm={handleConfirmRun}
isSubmitting={isSubmitting}
/>
</div>
);
@@ -301,6 +312,9 @@ export default function Dashboard() {

File diff suppressed because one or more lines are too long