feat: 手动上传绕防爬、下载错误诊断与健康检查工具;模块化重构 API 与批量同步

后端:
  - 将 handlers.rs (1338行) 拆分为 helpers/papers/notes/sync 四模块
  - 将 batch_sync.rs 拆分为 batch/{mod,meta,asset} 三模块
  - 新增 POST /api/upload 多部件文件上传接口
  - 新增 POST /api/no_resource 标记文献"无全文资源"
  - 新增 GET/POST /api/active_bibcode 追踪活跃文献
  - StandardPaper 结构体扩展 pdf_error / html_error 错误诊断字段
  - download.rs 记录下载失败详情至数据库
  - 新增 health_check 二进制工具,支持只读扫描与 --fix 自动修复
  - 移除 scratch/ 目录、recovered_handlers.rs 及调试日志

  前端:
  - 新建 CustomSelect 可复用组件,替换全部原生 select
  - LibraryPanel:同步按钮反馈动画、下载失败/无资源状态筛选与计数、
    文献类型筛选、状态优先排序、搜索一键清空
  - 详情弹窗:错误诊断展示、手动 PDF/HTML 上传区、无资源标记/恢复
  - SearchPanel:扩展文献类型徽章、下载失败状态提示
  - SyncPanel:同步启动乐观 UI 更新、日志容器内自动滚动
  - Tab 状态 localStorage 持久化、弹窗 z-index 修复
This commit is contained in:
fmq
2026-06-11 22:56:36 +08:00
parent cd6af4f995
commit 8cc2b74abc
43 changed files with 4512 additions and 3879 deletions
+184 -15
View File
@@ -25,6 +25,9 @@ export interface StandardPaper {
is_downloaded: boolean;
has_markdown: boolean;
has_translation: boolean;
doctype: string;
pdf_error?: string; // PDF 下载失败诊断信息(如存在)
html_error?: string; // HTML 下载失败诊断信息(如存在)
}
// 笔记记录
@@ -37,8 +40,35 @@ export interface NoteRecord {
selected_text: string;
created_at: string;
}
// 引文网络
export interface CitationNetwork {
bibcode: string;
title: string;
citation_count: number;
reference_count: number;
references: string[];
citations: string[];
citation_counts?: Record<string, number>;
}
// 已保存的同步检索条件
export interface SavedSyncQuery {
id: number;
query: string;
source: string;
limit_count: number;
last_run: string;
}
```
### 1.1 错误诊断字段说明
`pdf_error``html_error` 字段用于传递文献下载失败的具体原因:
- 当数据库中对应的 `pdf_path``html_path``error:` 前缀存储时,前端会自动提取前缀后的内容作为诊断信息。
- 特殊值 `no_resource`:表示用户手动标记了该文献为"无有效全文资源",后续批量下载任务将自动跳过此文献。
- 其他值:为系统自动检测到的下载失败原因(如网络超时、Cloudflare 拦截、404 等)。
---
## 2. 接口分模块详述 (API Endpoints)
@@ -94,7 +124,7 @@ export interface NoteRecord {
#### 2.2.1 获取馆藏文献列表
- **Endpoint**: `GET /api/library`
- **Description**: 查询本地 SQLite 数据库中已收藏入库的所有文献列表,后端会自动**实时感应物理文件是否存在**来修正 `is_downloaded` / `has_markdown` 等布尔状态。
- **Description**: 查询本地 SQLite 数据库中已收藏入库的所有文献列表,后端会自动**实时感应物理文件是否存在**来修正 `is_downloaded` / `has_markdown` 等布尔状态。同时读取并返回 `pdf_error` / `html_error` 诊断字段。
- **Response Schema (`Vec<StandardPaper>`)**:
- HTTP `200 OK`
- **cURL 示例**:
@@ -104,7 +134,7 @@ export interface NoteRecord {
#### 2.2.2 触发并行文献下载
- **Endpoint**: `POST /api/download`
- **Description**: 触发后台线程拉取文献的 PDF 及 HTML。如果是 arXiv 来源优先官方 HTML 兜底 ar5iv,并支持强制更新。
- **Description**: 触发后台线程拉取文献的 PDF 及 HTML。如果是 arXiv 来源优先官方 HTML 兜底 ar5iv,并支持强制更新。下载失败时会在数据库中以 `error:` 前缀记录具体原因。
- **Request Body**:
```json
{
@@ -112,7 +142,7 @@ export interface NoteRecord {
"force": false
}
```
- **Response Schema (`StandardPaper`)**: Returns the updated paper structure with `is_downloaded: true`.
- **Response Schema (`StandardPaper`)**: Returns the updated paper structure with `is_downloaded: true` (on success) or `pdf_error`/`html_error` populated (on failure).
- **cURL 示例**:
```bash
curl -X POST "http://localhost:8000/api/download" \
@@ -120,7 +150,49 @@ export interface NoteRecord {
-d '{"bibcode": "2024arXiv241011663H", "force": true}'
```
#### 2.2.3 触发文献结构化解析
#### 2.2.3 手动上传文献物理文件
- **Endpoint**: `POST /api/upload`
- **Description**: 手动上传用户离线下载的 HTML 或 PDF 物理文件,以便系统进行结构化解析和双语翻译。此接口常用于前端手动上传或浏览器书签直推同步,支持绕过防爬与验证码限制。上传时会自动进行文件格式校验(PDF 校验 `%PDF` 文件头),并支持通过 DOI 或 arXiv ID 自动匹配 Bibcode。
- **Request Body (Multipart Form Data)**:
- `bibcode` (string, required): 文献唯一标识符(Bibcode)、DOI 或 arXiv ID。
- `type` (string, required): 文件类别,取值为 `pdf` | `html`。
- `file` (file binary, required): 上传的 PDF 或 HTML 文件。
- **Response Schema (`StandardPaper`)**: 返回已更新下载状态(`is_downloaded: true`)的文献标准化元数据。
- **cURL 示例**:
```bash
curl -X POST "http://localhost:8000/api/upload" \
-F "bibcode=2024arXiv241011663H" \
-F "type=pdf" \
-F "file=@/path/to/downloaded.pdf"
```
#### 2.2.4 标记/取消"无有效全文资源"
- **Endpoint**: `POST /api/no_resource`
- **Description**: 将文献标记为"无有效全文资源"或清除该标记。标记后,后续批量下载/解析任务将自动跳过此文献。此操作会将数据库中的 `pdf_path` 和 `html_path` 设置为(或清除)`error:no_resource`。
- **Request Body**:
```json
{
"bibcode": "2024arXiv241011663H",
"clear": false
}
```
- `bibcode` (string, required): 文献唯一标识符。
- `clear` (boolean, optional): 设为 `true` 清除标记(恢复自动下载),默认 `false`(标记无资源)。
- **Response Schema (`StandardPaper`)**: 返回更新后的文献标准化元数据。
- **cURL 示例**:
```bash
# 标记为无资源
curl -X POST "http://localhost:8000/api/no_resource" \
-H "Content-Type: application/json" \
-d '{"bibcode": "2024arXiv241011663H"}'
# 清除标记(恢复自动下载)
curl -X POST "http://localhost:8000/api/no_resource" \
-H "Content-Type: application/json" \
-d '{"bibcode": "2024arXiv241011663H", "clear": true}'
```
#### 2.2.5 触发文献结构化解析
- **Endpoint**: `POST /api/parse`
- **Description**: 将本地下载的 HTML/PDF 清洗为 Markdown。支持 `force` 强制重新执行。
- **Request Body**:
@@ -205,7 +277,8 @@ export interface NoteRecord {
"citation_count": 12,
"reference_count": 48,
"references": ["bibcode1", "bibcode2"],
"citations": ["bibcode3", "bibcode4"]
"citations": ["bibcode3", "bibcode4"],
"citation_counts": { "bibcode3": 5, "bibcode4": 120 }
}
```
- **cURL 示例**:
@@ -226,7 +299,7 @@ export interface NoteRecord {
"bibcode": "2024arXiv241011663H",
"paragraph_index": 12,
"note_text": "这是一个重要的物理模型",
"highlight_color": "yellow", // 'yellow' | 'green' | 'blue' | 'pink'
"highlight_color": "yellow",
"selected_text": "the standard model of galaxy formation"
}
```
@@ -291,7 +364,7 @@ export interface NoteRecord {
#### 2.6.2 启动后台元数据同步
- **Endpoint**: `POST /api/sync/meta/run`
- **Description**: 后台异步启动对指定关键词的文献元数据的大批量增量检索与同步入库。
- **Description**: 后台异步启动对指定关键词的文献元数据的大批量增量检索与同步入库。若当前已有同步任务在运行中,将返回 `409 Conflict`。
- **Request Body**:
```json
{
@@ -300,7 +373,7 @@ export interface NoteRecord {
"limit": 200
}
```
- **Response Schema**: Returns HTTP `200 OK` (plain text success message).
- **Response Schema**: Returns HTTP `200 OK` (plain text success message) 或 `409 Conflict` (已有任务运行)。
- **cURL 示例**:
```bash
curl -X POST "http://localhost:8000/api/sync/meta/run" \
@@ -328,15 +401,21 @@ export interface NoteRecord {
#### 2.6.4 启动后台文献资源批量下载/解析
- **Endpoint**: `POST /api/sync/asset/run`
- **Description**: 后台异步启动文献物理资源 (PDF/HTML) 的批量下载及结构化 Markdown 转换任务。
- **Description**: 后台异步启动文献物理资源 (PDF/HTML) 的批量下载及结构化 Markdown 转换任务。支持按文献 Bibcode 列表或按状态范围筛选处理目标。
- **Request Body**:
```json
{
"action": "all", // "all" (下载并解析) | "download" (仅下载) | "parse" (仅解析)
"scope": "undownloaded" // "all" (全部) | "undownloaded" (仅未下载) | "unparsed" (仅未解析)
"action": "all",
"scope": "undownloaded",
"sort_order": "default",
"limit_count": 50
}
```
- **Response Schema**: Returns HTTP `200 OK` (plain text success message).
- `action` (string): `"all"` (下载并解析) | `"download"` (仅下载) | `"parse"` (仅解析) | `"translate"` (仅翻译)。
- `scope` (string): `"all"` (全部) | `"undownloaded"` (仅未下载) | `"unparsed"` (仅未解析)。
- `sort_order` (string, optional): `"default"` | `"pub_year_desc"` | `"created_at_desc"`。
- `limit_count` (number, optional): 批量处理上限,默认处理全部匹配文献。
- **Response Schema**: Returns HTTP `200 OK` (plain text success message) 或 `409 Conflict` (已有任务运行)。
- **cURL 示例**:
```bash
curl -X POST "http://localhost:8000/api/sync/asset/run" \
@@ -355,7 +434,7 @@ export interface NoteRecord {
#### 2.6.6 查询批量处理任务状态与日志
- **Endpoint**: `GET /api/sync/asset/status`
- **Description**: 获取当前后台批量下载与解析任务的状态、总匹配文献数、已下载数、已解析数、当前处理的 Bibcode,以及实时流转的终端日志(最多保留最新 1000 行)。
- **Description**: 获取当前后台批量下载与解析任务的状态、总匹配文献数、已下载数、已解析数、失败数、当前处理的 Bibcode,以及实时流转的终端日志(最多保留最新 100)。
- **Response Schema**:
```json
{
@@ -363,6 +442,8 @@ export interface NoteRecord {
"total": 12,
"downloaded": 12,
"parsed": 12,
"download_failed": 0,
"parse_failed": 0,
"current_bibcode": "2020A&A...635A..38C",
"logs": [
"[INFO] 批量处理任务初始化成功",
@@ -377,14 +458,102 @@ export interface NoteRecord {
curl "http://localhost:8000/api/sync/asset/status"
```
#### 2.6.7 获取已保存的同步检索条件
- **Endpoint**: `GET /api/sync/queries`
- **Description**: 获取用户保存的所有同步检索条件列表,用于快速重新同步。
- **Response Schema (`Vec<SavedSyncQuery>`)**:
- HTTP `200 OK`
- **cURL 示例**:
```bash
curl "http://localhost:8000/api/sync/queries"
```
#### 2.6.8 删除已保存的同步检索条件
- **Endpoint**: `DELETE /api/sync/queries/:id`
- **Description**: 删除指定 ID 的已保存同步检索条件。
- **Path Parameters**:
- `id` (number, required): 同步检索条件的唯一自增 ID。
- **Response Schema**: Returns HTTP `200 OK` (plain text success message).
- **cURL 示例**:
```bash
curl -X DELETE "http://localhost:8000/api/sync/queries/1"
```
---
## 3. 常见 HTTP 状态码与异常处理 (Error Codes)
### 2.7 活跃文献追踪 (Active Bibcode Tracking)
#### 2.7.1 获取当前活跃文献
- **Endpoint**: `GET /api/active_bibcode`
- **Description**: 获取当前用户正在查看/操作的文献 Bibcode。前端在用户点击文献外部链接(如 ADS、DOI、arXiv)时自动上报。
- **Response Schema**:
```json
{
"bibcode": "2024arXiv241011663H"
}
```
若无活跃文献,`bibcode` 为 `null`。
- **cURL 示例**:
```bash
curl "http://localhost:8000/api/active_bibcode"
```
#### 2.7.2 设置当前活跃文献
- **Endpoint**: `POST /api/active_bibcode`
- **Description**: 设置当前正在查看的文献 Bibcode,用于浏览器书签直推等场景。
- **Request Body**:
```json
{
"bibcode": "2024arXiv241011663H"
}
```
- **Response Schema**: Returns HTTP `200 OK`.
- **cURL 示例**:
```bash
curl -X POST "http://localhost:8000/api/active_bibcode" \
-H "Content-Type: application/json" \
-d '{"bibcode": "2024arXiv241011663H"}'
```
---
## 3. 完整路由表 (Route Summary)
| 方法 | 路径 | 说明 |
|:---|:---|:---|
| `GET` | `/api/search` | 跨源文献统一搜索 |
| `POST` | `/api/download` | 触发文献下载 |
| `POST` | `/api/upload` | 手动上传文献文件 |
| `POST` | `/api/no_resource` | 标记/取消"无有效全文资源" |
| `POST` | `/api/parse` | 触发文献结构化解析 |
| `POST` | `/api/translate` | 触发 LLM 对照翻译 |
| `GET` | `/api/citations` | 查询引文拓扑网络 |
| `GET` | `/api/paper` | 获取文献阅读详情 |
| `GET` | `/api/library` | 获取馆藏文献列表 |
| `POST` | `/api/export` | 批量 BibTeX 导出 |
| `POST` | `/api/notes` | 创建笔记与高亮 |
| `GET` | `/api/notes` | 获取文献笔记列表 |
| `DELETE` | `/api/notes` | 删除笔记 |
| `GET` | `/api/sync/meta/count` | 预估元数据同步总量 |
| `POST` | `/api/sync/meta/run` | 启动元数据同步 |
| `GET` | `/api/sync/meta/status` | 查询元数据同步状态 |
| `POST` | `/api/sync/asset/run` | 启动资源批量处理 |
| `POST` | `/api/sync/asset/stop` | 停止资源批量处理 |
| `GET` | `/api/sync/asset/status` | 查询资源处理状态 |
| `GET` | `/api/sync/queries` | 获取已保存检索条件 |
| `DELETE` | `/api/sync/queries/:id` | 删除已保存检索条件 |
| `GET` | `/api/active_bibcode` | 获取当前活跃文献 |
| `POST` | `/api/active_bibcode` | 设置当前活跃文献 |
---
## 4. 常见 HTTP 状态码与异常处理 (Error Codes)
系统基于标准的 HTTP Status Codes 返回错误原因,响应的 Response Body 中通常为纯文本提示(String):
| 状态码 | 错误类型 | 触发常见场景及原因说明 |
| :--- | :--- | :--- |
| **`400 Bad Request`** | 业务请求不合规 | - 文献未下载/解析却直接调用 `translate`。<br>- 未在 `.env` 中提供 `ADS_API_KEY` 时调用 `export`。 |
| **`400 Bad Request`** | 业务请求不合规 | - 文献未下载/解析却直接调用 `translate`。<br>- 上传文件格式不合法(如 PDF 文件头校验失败)。<br>- 缺少必需参数(如 `bibcode` 为空)。 |
| **`404 Not Found`** | 资源未找到 | - 数据库中没有该 Bibcode 的收藏记录。 |
| **`409 Conflict`** | 状态冲突 | - 已有批量同步任务在后台运行中,重复启动时触发。 |
| **`500 Internal Error`**| 服务器内部错误 | - 第三方 LLM / ADS 接口通信超时或返回异常。<br>- 本地磁盘 IO 失败(如写入文件权限受阻)。<br>- 数据库查询异常。 |