Files
rust-imager/src/main.rs
2026-03-25 18:31:11 +08:00

209 lines
7.9 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 database;
mod image_processor;
mod import;
mod models;
mod routes;
mod queue;
use actix_files as afs;
use actix_web::{App, HttpResponse, HttpServer, web, middleware};
use std::fs;
use std::sync::Arc;
use tokio::time::{interval, Duration};
use tracing::{info, error};
use crate::models::AlbumsCache;
#[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!("启动图片展示服务...");
info!("服务地址: http://{}", config.server_address());
// 初始化数据库
let db = match database::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()));
// 初始化智能任务队列
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, // 空闲请求频率阈值
));
// 在后台线程中自动导入新相册(不阻塞HTTP服务启动)
let config_clone = config.clone();
let db_clone = Arc::clone(&db);
let task_queue_clone = Arc::clone(&task_queue);
tokio::spawn(async move {
info!("[后台任务] 开始扫描图片目录并导入新相册...");
match import::import_existing_data(&config_clone, db_clone, Some(task_queue_clone)).await {
Ok(_) => info!("[后台任务] 相册导入完成"),
Err(e) => error!("[后台任务] 相册导入失败: {}", e),
}
});
// 启动缩略图生成Worker
let thumbnail_worker = queue::ThumbnailWorker::new(
Arc::clone(&task_queue),
Arc::clone(&image_processor),
);
tokio::spawn(async move {
thumbnail_worker.start().await;
});
// 启动相册导入Worker
let album_import_worker = queue::AlbumImportWorker::new(
Arc::clone(&task_queue),
Arc::clone(&db),
config.storage.clone(),
);
tokio::spawn(async move {
album_import_worker.start().await;
});
// 初始化相册内存缓存
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)); // 30分钟
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);
}
}
}
});
println!("API接口:");
println!(" - GET /api/albums - 获取所有相册");
println!(" - GET /api/search?q=关键词 - 搜索相册");
println!(" - GET /api/album/:id - 获取相册详情");
println!(" - POST /api/albums - 创建新相册");
println!(" - GET /api/thumbnail/:id - 获取缩略图");
println!(" - GET /api/original/:id - 获取原始图片");
println!(" - GET /api/favorites - 获取收藏列表");
println!(" - GET /api/favorites/status/:id - 检查收藏状态");
println!(" - POST /api/favorites - 添加收藏");
println!(" - DELETE /api/favorites/:id - 取消收藏");
println!(" - DELETE /api/favorites/image/:id - 通过图片ID取消收藏");
println!("按 Ctrl+C 停止服务");
// 创建数据目录
fs::create_dir_all(&config.storage.images_dir).unwrap_or(());
fs::create_dir_all(&config.storage.thumbnails_dir).unwrap_or(());
fs::create_dir_all(&config.database.url[7..]).unwrap_or(());
// 在闭包外提取需要的路径
let server_address = config.server_address();
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/thumbnail/{id}", web::get().to(routes::get_thumbnail))
.route("/api/original/{id}", web::get().to(routes::get_original_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/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))
// HTML页面路由
.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(server_address)?
.run()
.await
}