AstroResearch/src/main.rs
Asfmq cd6af4f995 feat: 重构 PDF/文献检索同步机制、升级引力图交互与控制台 UI 样式
- [后端/PDF解析] 重构 MinerU PDF 解析流程:引入预签名两阶段直传机制,解决大文件 API 传输限制问题;支持轮询机制与本地 images 备用目录存储。
- [后端/同步与下载] 新增经典 ADS SCAN 扫描件 PDF 和 ADS_PDF 直接通道的下载逻辑;新增常用同步检索配置的持久化存储与去重管理 API。
- [后端/日志] 重构日志系统,支持控制台 pretty 输出与每日滚动文件日志(使用上海 +08:00 时区),引入 HTTP 路由请求链路追踪。
- [前端/引力图] 升级引用星系图 canvas 交互:支持平移拖拽与滚轮缩放,添加引力圈轨道装饰及未导入文献的半透明视觉区分。
- [前端/控制台] 统一重构为扁平高对比度浅色纯中文控制台样式;重新设计文献详情弹窗与状态进度条。
- [数据库] 新增 papers 表的 doctype 字段及 sync_queries 检索配置表。
2026-06-10 17:29:07 +08:00

145 lines
5.6 KiB
Rust

// src/main.rs
use std::net::SocketAddr;
use std::sync::Arc;
use axum::{
routing::{get, post},
Router,
};
use tower_http::cors::{Any, CorsLayer};
use tower_http::services::ServeDir;
use sqlx::sqlite::SqlitePoolOptions;
use tracing::{info, error};
use astroresearch::Config;
use astroresearch::services::translation::Dictionary;
use astroresearch::clients::qiniu::QiniuClient;
use astroresearch::clients::ads::AdsClient;
use astroresearch::clients::arxiv::ArxivClient;
use astroresearch::services::download::Downloader;
use astroresearch::api::handlers::{AppState, self};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// 1. 初始化日志记录器并保留异步写保护 Guard
let _logging_guards = astroresearch::services::logging::init_logging()?;
info!("正在启动 AstroResearch 天文学文献辅助系统后端服务...");
// 2. 加载环境变量配置
let config = Config::from_env();
info!("系统配置成功载入。本地 SQLite 连接串: {}", config.database_url);
// 创建本地馆藏物理文件夹分类结构
std::fs::create_dir_all(&config.library_dir).unwrap_or_default();
std::fs::create_dir_all(config.library_dir.join("PDF")).unwrap_or_default();
std::fs::create_dir_all(config.library_dir.join("HTML")).unwrap_or_default();
std::fs::create_dir_all(config.library_dir.join("Markdown")).unwrap_or_default();
std::fs::create_dir_all(config.library_dir.join("Translation")).unwrap_or_default();
// 3. 初始化本地 SQLite 数据库文件连接池
if config.database_url.starts_with("sqlite://") {
let db_path = config.database_url.replace("sqlite://", "");
if !db_path.contains(":memory:") {
let path = std::path::Path::new(&db_path);
if !path.exists() {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).unwrap_or_default();
}
std::fs::File::create(path)?;
info!("初始化创建本地 SQLite 数据库文件: {:?}", path);
}
}
}
let pool = SqlitePoolOptions::new()
.max_connections(5)
.connect(&config.database_url)
.await?;
info!("SQLite 数据库连接已建立。");
// 4. 自动执行数据库迁移脚本
info!("开始执行 SQL 表结构迁移...");
sqlx::migrate!("./migrations")
.run(&pool)
.await?;
info!("数据库迁移执行完成,主表准备就绪。");
// 5. 异步加载天文学专业名词对照词表
let mut dict = Dictionary::new();
if let Err(e) = dict.load_from_file("dictionary.txt") {
error!("天文学名词词表加载失败: {}", e);
}
// 6. 初始化并配置全部 API 与下载客户端
let qiniu = QiniuClient::new(
config.qiniu_ak.clone(),
config.qiniu_sk.clone(),
config.qiniu_bucket.clone(),
config.qiniu_domain.clone(),
);
let ads = AdsClient::new(config.ads_api_key.clone());
let arxiv = ArxivClient::new();
let downloader = Downloader::new();
let app_state = Arc::new(AppState {
config: config.clone(),
db: pool,
dict,
qiniu,
ads,
arxiv,
downloader,
harvest_status: Arc::new(tokio::sync::Mutex::new(astroresearch::services::batch_sync::MetaSyncStatus::new())),
process_status: Arc::new(tokio::sync::Mutex::new(astroresearch::services::batch_sync::AssetSyncStatus::new())),
});
// 7. 设置 Axum 路由、CORS 头以及 React 仪表盘静态资源托管
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
.allow_headers(Any);
let api_routes = Router::new()
.route("/search", get(handlers::search_papers))
.route("/download", post(handlers::download_paper))
.route("/parse", post(handlers::parse_paper))
.route("/translate", post(handlers::translate_paper))
.route("/citations", get(handlers::get_citation_network))
.route("/paper", get(handlers::get_paper_detail))
.route("/library", get(handlers::get_library))
.route("/export", post(handlers::export_citations))
.route("/notes", post(handlers::create_note))
.route("/notes", get(handlers::get_notes))
.route("/notes", axum::routing::delete(handlers::delete_note))
.route("/sync/meta/count", get(handlers::get_meta_sync_count))
.route("/sync/meta/run", post(handlers::run_meta_sync))
.route("/sync/meta/status", get(handlers::get_meta_sync_status))
.route("/sync/asset/run", post(handlers::run_asset_sync))
.route("/sync/asset/stop", post(handlers::stop_asset_sync))
.route("/sync/asset/status", get(handlers::get_asset_sync_status))
.route("/sync/queries", get(handlers::get_sync_queries))
.route("/sync/queries/:id", axum::routing::delete(handlers::delete_sync_query));
// 静态文件资源代理托管(当前端打包至 dashboard/dist 后,直接挂载到主域名根路由)
let serve_dir = ServeDir::new("dashboard/dist")
.fallback(tower_http::services::ServeFile::new("dashboard/dist/index.html"));
let app = Router::new()
.nest("/api", api_routes)
.fallback_service(serve_dir)
.layer(cors)
.layer(tower_http::trace::TraceLayer::new_for_http())
.with_state(app_state);
let addr = SocketAddr::from(([0, 0, 0, 0], config.port));
info!("天文学科研服务已成功监听 http://localhost:{}", config.port);
let listener = tokio::net::TcpListener::bind(addr).await?;
axum::serve(listener, app).await?;
Ok(())
}