AstroResearch/recovered_handlers.rs
Asfmq e13fa2ad40 refactor!: 模块化拆分 src 结构,新增批量同步服务、查询解析器及前端分页/高级检索功能
- src/ 按 clients/services/api 分层,Config 提升至 crate 根
- 新增 batch_sync.rs(双源并行收割)、query_parser.rs(多平台检索式转换)
- build.rs 自动触发前端 npm install & build
- SearchPanel 支持分页/排序/每页条数/高级检索构建器,前端加入搜索缓存
- 新增 SyncPanel 替换 SettingsPanel;新增 live_search 集成测试
2026-06-09 10:29:24 +08:00

1272 lines
6.9 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// src/handlers.rs
use axum::{
extract::{Query, State},
http::StatusCode,
Json,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use std::fs;
use tracing::{info, warn, error};
use sqlx::{SqlitePool, Row};
use crate::config::Config;
use crate::translation::Dictionary;
use crate::qiniu::QiniuClient;
use crate::ads::{AdsClient, AdsPaperDoc};
use crate::arxiv::{ArxivClient, ArxivPaper};
use crate::download::Downloader;
// 全局共享的 Axum 应用上下文状态
pub struct AppState {
pub config: Config,
pub db: SqlitePool,
pub dict: Dictionary,
pub qiniu: QiniuClient,
pub ads: AdsClient,
pub arxiv: ArxivClient,
pub downloader: Downloader,
pub harvest_status: Arc<tokio::sync::Mutex<crate::harvester::HarvestStatus>>,
pub process_status: Arc<tokio::sync::Mutex<crate::processor::ProcessStatus>>,
}
// 检索请求参数
#[derive(Debug, Deserialize)]
pub struct SearchParams {
pub q: String,
pub source: Option<String>, // "all" | "ads" | "arxiv"
pub rows: Option<i32>,
pub start: Option<i32>, // 分页起始偏移量
pub sort: Option<String>, // 排序字段,例如 "date_desc", "citations_desc", "relevance"
}
// 统一标准化的文献格式,用于向前端传输
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct StandardPaper {
pub bibcode: String,
pub title: String,
pub authors: Vec<String>,
let doi = doc.doi.as_ref()
.and_then(|v: &Vec<String>| v.first())
.cloned()
.unwrap_or_default();
let mut arxiv_id = String::new();
if let Some(identifiers) = &doc.identifier {
for id in identifiers {
if id.starts_with("arXiv:") {
arxiv_id = id.replace("arXiv:", "").trim().to_string();
break;
}
}
}
if arxiv_id.is_empty() {
if doc.bibcode.starts_with("arXiv") {
arxiv_id = doc.bibcode.replace("arXiv", "").trim().to_string();
}
}
StandardPaper {
bibcode: doc.bibcode.clone(),
title,
authors,
year: doc.year.clone().unwrap_or_default(),
pub_journal: doc.pub_journal.clone().unwrap_or_default(),
keywords,
abstract_text: doc.abstract_text.clone().unwrap_or_default(),
doi,
arxiv_id,
citation_count: doc.citation_count.unwrap_or(0),
reference_count: doc.reference_count.unwrap_or(0),
is_downloaded: false,
has_markdown: false,
has_translation: false,
}
}
pub(crate) fn convert_arxiv_to_standard(doc: &ArxivPaper) -> StandardPaper {
StandardPaper {
bibcode: doc.id.clone(),
title: doc.title.clone(),
authors: doc.authors.clone(),
year: doc.year.clone(),
pub_journal: "arXiv Preprint".to_string(),
keywords: Vec::new(),
abstract_text: doc.abstract_text.clone(),
doi: doc.doi.clone().unwrap_or_default(),
arxiv_id: doc.id.clone(),
citation_count: 0,
reference_count: 0,
is_downloaded: false,
has_markdown: false,
has_translation: false,
}
}
pub(crate) async fn save_paper_to_db(db: &SqlitePool, p: &StandardPaper) -> anyhow::Result<()> {
let authors_json = serde_json::to_string(&p.authors)?;
let keywords_json = serde_json::to_string(&p.keywords)?;
// 1. 如果存在 arxiv_id检查是否有已存在的相同 arxiv_id 记录以防 duplicate
if !p.arxiv_id.is_empty() {
let existing_opt: Option<(String, Option<String>, Option<String>, Option<String>, Option<String>)> = sqlx::query_as(
"SELECT bibcode, pdf_path, html_path, markdown_path, translation_path FROM papers WHERE arxiv_id = ?"
)
.bind(&p.arxiv_id)
.fetch_optional(db)
.await?;
if let Some((existing_bibcode, _pdf, _html, _md, _tr)) = existing_opt {
if existing_bibcode != p.bibcode {
// 发现不同 bibcode 标识的同一篇文献记录,需要进行合并合并
// 如果已存在的记录使用的是临时 arXiv ID 作为 bibcode且新记录使用的是正式 ADS bibcode我们升级 bibcode 主键
let is_existing_temp = existing_bibcode == p.arxiv_id;
let is_new_formal = p.bibcode != p.arxiv_id;
if is_existing_temp && is_new_formal {
info!("发现相同 arXiv ID 的
.await?;
// 运行迁移
sqlx::migrate!("./migrations")
.run(&pool)
.await?;
let paper = StandardPaper {
bibcode: "2026A&A...123..456X".to_string(),
title: "A Test Title".to_string(),
authors: vec!["Author A".to_string()],
year: "2026".to_string(),
pub_journal: "Astronomy & Astrophysics".to_string(),
keywords: vec!["Keyword 1".to_string()],
abstract_text: "This is abstract".to_string(),
doi: "10.1000/test.doi".to_string(),
arxiv_id: "".to_string(),
citation_count: 5,
reference_count: 10,
is_downloaded: false,
has_markdown: false,
has_translation: false,
};
// 保存
save_paper_to_db(&pool, &paper).await?;
// 读取
let retrieved = get_paper_from_db(&pool, std::path::Path::new(""), "2026A&A...123..456X").await?;
assert_eq!(retrieved.title, paper.title);
assert_eq!(retrieved.authors, paper.authors);
assert_eq!(retrieved.keywords, paper.keywords);
// 检查路径状态(初始为 None
let paths = check_paper_paths_in_db(&pool, std::path::Path::new(""), "2026A&A...123..456X").await?;
assert!(paths.is_some());
let (pdf, html, md, tr) = paths.unwrap();
assert!(pdf.is_none());
assert!(html.is_none());
assert!(md.is_none());
assert!(tr.is_none());
Ok(())
}
}