Files
rust-imager/src/main.rs
lychang 093924ea99
Some checks failed
Rust CI / Build Release (push) Has been cancelled
Rust CI / Cargo Check (push) Has been cancelled
update
2026-07-19 16:34:04 +08:00

224 lines
9.1 KiB
Rust
Raw 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.

mod config;
mod models;
mod db;
mod engine;
mod cache;
mod routes;
mod services;
mod image_processor;
mod queue;
use crate::cache::AlbumsCache;
use actix_files as afs;
use actix_web::{App, HttpResponse, HttpServer, middleware, web};
use std::fs;
use std::sync::Arc;
use tokio::time::{Duration, interval};
use tracing::{error, info};
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
tracing_subscriber::fmt::init();
// 加载配置
let config = match config::Config::load() {
Ok(cfg) => cfg,
Err(e) => {
error!("加载配置失败: {}", e);
std::process::exit(1);
}
};
info!("启动图片展示服务 v2.0...");
info!("服务地址: http://{}", config.server_address());
// 初始化数据库
let db = match db::Database::new(&config).await {
Ok(db) => {
info!("数据库连接成功");
Arc::new(db)
}
Err(e) => {
error!("数据库连接失败: {}", e);
std::process::exit(1);
}
};
let image_processor = Arc::new(image_processor::ImageProcessor::new(config.storage.clone()));
// 后台导入本地相册 + 自动解析title
let config_clone = config.clone();
let db_clone = Arc::clone(&db);
let auto_parse = true;
tokio::spawn(async move {
info!("[后台任务] 开始扫描图片目录并导入新相册...");
let import_ok = match services::import::import_existing_data(&config_clone, Arc::clone(&db_clone), None).await {
Ok(_) => {
info!("[后台任务] 相册导入完成");
true
}
Err(e) => {
error!("[后台任务] 相册导入失败: {}", e);
false
}
};
if import_ok && auto_parse {
info!("[后台任务] 开始解析相册 title(提取人名 + 标签)...");
let (persons, tags) = engine::pipeline::process_all_albums(&db_clone).await;
info!("[后台任务] Title 解析完成:{} 个人名,{} 个标签", persons, tags);
}
});
// 相册内存缓存
let albums_cache = Arc::new(tokio::sync::RwLock::new(AlbumsCache::new()));
// 初始加载缓存
let initial_cache = Arc::clone(&albums_cache);
let initial_db = Arc::clone(&db);
tokio::spawn(async move {
match initial_db.get_all_albums().await {
Ok(albums) => {
let mut cache = initial_cache.write().await;
cache.update(albums);
println!("✅ 初始相册缓存加载完成");
}
Err(e) => {
eprintln!("❌ 初始缓存加载失败: {}", e);
}
}
});
// 定时刷新缓存(每30分钟)
let refresh_cache = Arc::clone(&albums_cache);
let refresh_db = Arc::clone(&db);
tokio::spawn(async move {
let mut interval = interval(Duration::from_secs(30 * 60));
loop {
interval.tick().await;
match refresh_db.get_all_albums().await {
Ok(albums) => {
let mut cache = refresh_cache.write().await;
cache.update(albums);
println!("🔄 相册缓存定时刷新完成");
}
Err(e) => {
eprintln!("❌ 缓存刷新失败: {}", e);
}
}
}
});
// 任务队列
let task_queue = Arc::new(queue::TaskQueue::new(
config.queue.max_concurrent_tasks,
config.queue.queue_size_limit,
config.queue.high_request_rate_threshold,
config.queue.idle_request_rate_threshold,
));
println!("API接口:");
println!(" - GET /api/albums - 获取所有相册");
println!(" - GET /api/search?q=关键词 - 搜索相册");
println!(" - GET /api/album/random - 随机相册");
println!(" - GET /api/album/:id - 获取相册详情");
println!(" - POST /api/albums - 创建新相册");
println!(" - GET /api/image/:id - 获取图片");
println!(" - GET /api/favorites - 收藏列表");
println!(" - POST /api/favorites - 添加收藏");
println!(" - DELETE /api/favorites/:id - 取消收藏");
println!("元数据接口:");
println!(" - GET /api/people - 人员列表");
println!(" - GET /api/people/search?q=关键词 - 搜索人员");
println!(" - GET /api/people/{{name}} - 人员详情");
println!(" - GET /api/people/{{id}}/aliases - 人员别名");
println!(" - GET /api/metadata/stats - 元数据统计");
println!(" - GET /api/tags/search?tag=标签 - 按标签搜索");
println!(" - GET /api/tags/popular?limit=50 - 热门标签");
println!("推荐接口:");
println!(" - GET /api/recommend/tags?q=xxx - 标签推荐");
println!(" - GET /api/recommend/persons?q=xxx - 人物推荐");
println!(" - GET /api/recommend/related?context=xxx - 关联推荐");
println!(" - GET /api/recommend/popular-persons - 热门人物");
println!("迁移接口:");
println!(" - GET /api/migration/stats - 迁移状态");
println!(" - POST /api/migration/run?path=persons.db - 执行迁移");
println!(" - GET /api/queue/stats - 队列状态");
println!("按 Ctrl+C 停止服务");
fs::create_dir_all(&config.storage.images_dir).unwrap_or(());
HttpServer::new(move || {
App::new()
.wrap(middleware::Compress::default())
.wrap(middleware::Logger::default())
.app_data(web::Data::new(Arc::clone(&db)))
.app_data(web::Data::new(Arc::clone(&image_processor)))
.app_data(web::Data::new(Arc::clone(&albums_cache)))
.app_data(web::Data::new(Arc::clone(&task_queue)))
// 相册 API
.route("/api/albums", web::get().to(routes::get_albums))
.route("/api/search", web::get().to(routes::search_albums))
.route("/api/album/random", web::get().to(routes::get_random_album))
.route("/api/album/{id}", web::get().to(routes::get_album_detail))
.route("/api/albums", web::post().to(routes::create_album))
.route("/api/image/{id}", web::get().to(routes::get_image))
// 收藏 API
.route("/api/favorites", web::get().to(routes::get_favorites))
.route("/api/favorites/status/{id}", web::get().to(routes::check_favorite_status))
.route("/api/favorites", web::post().to(routes::add_favorite))
.route("/api/favorites/{id}", web::delete().to(routes::remove_favorite))
.route("/api/favorites/image/{id}", web::delete().to(routes::remove_favorite_by_image))
// 人员 API
.route("/api/people", web::get().to(routes::get_people))
.route("/api/people/search", web::get().to(routes::search_people))
.route("/api/people/{name}", web::get().to(routes::get_person_detail))
.route("/api/people/{id}/aliases", web::get().to(routes::get_person_aliases))
// 标签 API
.route("/api/metadata/stats", web::get().to(routes::get_metadata_stats))
.route("/api/tags/search", web::get().to(routes::search_by_tag))
.route("/api/tags/popular", web::get().to(routes::get_popular_tags))
// 推荐 API
.route("/api/recommend/tags", web::get().to(routes::recommend_tags))
.route("/api/recommend/persons", web::get().to(routes::recommend_persons))
.route("/api/recommend/related", web::get().to(routes::recommend_related))
.route("/api/recommend/popular-persons", web::get().to(routes::popular_persons))
// 迁移 API
.route("/api/migration/stats", web::get().to(routes::migration_stats))
.route("/api/migration/run", web::post().to(routes::migration_run))
// 管线 API
.route("/api/pipeline/run", web::post().to(routes::pipeline_run))
// 队列 API
.route("/api/queue/stats", web::get().to(routes::get_queue_stats))
// 静态文件
.service(
afs::Files::new("/assets", "./assets")
.show_files_listing()
.prefer_utf8(true)
.use_last_modified(true)
.use_etag(true),
)
.route("/", web::get().to(|| async {
match fs::read_to_string("./assets/index.html") {
Ok(content) => HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(content),
Err(_) => HttpResponse::NotFound().body("首页文件不存在"),
}
}))
.route("/album", web::get().to(|| async {
match fs::read_to_string("./assets/album.html") {
Ok(content) => HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(content),
Err(_) => HttpResponse::NotFound().body("相册页面不存在"),
}
}))
})
.bind(config.server_address())?
.run()
.await
}