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 }