refactor: 全栈质量硬化

后端:
  - 权限系统重写: 全局→按 Session 隔离, 新增规则查询 API
  - 安全加固: 登录 IP 限流, Token 仅存 Cookie, bibcode 白名单校验
  - SSE 超时保护, 异步 I/O 迁移, 10+ 处静默 DB 错误改为显式日志
  - ar5iv 下标解析修复, parse_paper_row 去重, 优雅关闭

  前端:
  - useSyncScroll 重写: 段落 ID 映射修复中英错位
  - 全局竞态修复 (active 标志), libraryRef 闭包过期修复
  - ErrorBoundary + vitest 测试基础设施
  - Logo 组件提取, CustomSelect 泛型化, TabId 类型统一
  - ReaderPanel 自动视图模式, AIAssistantPanel 状态批处理
This commit is contained in:
fmq
2026-06-28 14:40:26 +08:00
parent 5f2d2d83f6
commit c5fd5b0d66
54 changed files with 3223 additions and 743 deletions
+30 -6
View File
@@ -193,15 +193,39 @@ pub async fn chat_agent(
}
});
// 将 mpsc 通道转换为 SSE 事件流
// 将 mpsc 通道转换为 SSE 事件流(带 10 分钟超时)
const SSE_TIMEOUT_SECS: u64 = 600;
let stream = async_stream::stream! {
while let Some(event) = rx.recv().await {
let data = serde_json::to_string(&event).unwrap_or_default();
let is_done = matches!(event, AgentStreamEvent::Done);
yield Ok(Event::default().data(data));
if is_done {
let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(SSE_TIMEOUT_SECS);
loop {
let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
if remaining.is_zero() {
let timeout_event = AgentStreamEvent::Error {
message: "Agent 执行超时(10 分钟),请重试。".to_string(),
};
let data = serde_json::to_string(&timeout_event).unwrap_or_default();
yield Ok(Event::default().data(data));
break;
}
match tokio::time::timeout(remaining, rx.recv()).await {
Ok(Some(event)) => {
let data = serde_json::to_string(&event).unwrap_or_default();
let is_done = matches!(event, AgentStreamEvent::Done);
yield Ok(Event::default().data(data));
if is_done {
break;
}
}
Ok(None) => break, // channel closed
Err(_) => {
let timeout_event = AgentStreamEvent::Error {
message: "Agent 执行超时(10 分钟),请重试。".to_string(),
};
let data = serde_json::to_string(&timeout_event).unwrap_or_default();
yield Ok(Event::default().data(data));
break;
}
}
}
};