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