From 005688b58f9a348c33b08c0d04dba1dbe8730ba9 Mon Sep 17 00:00:00 2001 From: lychang <632945494@qq.com> Date: Tue, 21 Jul 2026 02:17:46 +0800 Subject: [PATCH] update code level --- src/db/album.rs | 20 +++++++++++++------- src/engine/pipeline.rs | 38 ++++++++++++++++++++++++++++---------- src/main.rs | 16 ++++++++++++---- src/routes/pipeline.rs | 2 +- 4 files changed, 54 insertions(+), 22 deletions(-) diff --git a/src/db/album.rs b/src/db/album.rs index d7d0d40..975e646 100644 --- a/src/db/album.rs +++ b/src/db/album.rs @@ -267,24 +267,30 @@ impl Database { }).collect() } - /// 查找重名相册,返回 (keep_id, [duplicate_ids]) - pub async fn find_duplicate_albums(&self) -> Vec<(Uuid, Vec)> { + /// 查找重名相册,返回结构:每个元素为 (keep_id, keep_document, [(dup_id, dup_document)]) + pub async fn find_duplicate_albums(&self) -> Vec<(Uuid, String, Vec<(Uuid, String)>)> { let rows = sqlx::query( - r#"SELECT id, title FROM albums WHERE title != '' ORDER BY created_at ASC"# + r#"SELECT id, title, document FROM albums WHERE title != '' ORDER BY created_at ASC"# ).fetch_all(&self.pool).await.unwrap_or_default(); - let mut title_map: std::collections::HashMap> = std::collections::HashMap::new(); + // title → [(id, document)] + let mut title_map: std::collections::HashMap> = std::collections::HashMap::new(); for r in &rows { let title: String = r.get("title"); let id_str: String = r.get("id"); + let doc: String = r.get("document"); if let Ok(id) = Uuid::parse_str(&id_str) { - title_map.entry(title).or_default().push(id); + title_map.entry(title).or_default().push((id, doc)); } } title_map.into_iter() - .filter(|(_, ids)| ids.len() > 1) - .map(|(_, ids)| (ids[0], ids[1..].to_vec())) + .filter(|(_, items)| items.len() > 1) + .map(|(_, items)| { + let keep = items[0].clone(); // (keep_id, keep_document) + let dups = items[1..].to_vec(); + (keep.0, keep.1, dups) + }) .collect() } diff --git a/src/engine/pipeline.rs b/src/engine/pipeline.rs index 464a9d3..7da26b0 100644 --- a/src/engine/pipeline.rs +++ b/src/engine/pipeline.rs @@ -266,15 +266,33 @@ pub async fn phase_delete_empty_albums(db: &Database) -> usize { // Phase Z1:去重同名相册 // ════════════════════════════════════════════════ -/// 查找同名的相册,只保留第一个(最早创建的),删除其余 -pub async fn phase_dedup_albums(db: &Database) -> usize { +/// 查找同名的相册,只保留第一个(最早创建的),删除其余(包括文件夹和 search.db 数据) +pub async fn phase_dedup_albums(db: &Database, search_db: &SearchDb) -> usize { let dups = db.find_duplicate_albums().await; + let images_dir = &db.config.storage.images_dir; let mut total_deleted = 0usize; - for (keep, to_delete) in &dups { - let deleted = db.delete_albums_batch(to_delete).await; - total_deleted += deleted; - for id in to_delete { - println!("[管线·PhaseZ1] 删除重复相册 {}(保留 {})", id, keep); + for (keep_id, keep_document, to_delete) in &dups { + let dup_ids: Vec = to_delete.iter().map(|(id, _)| *id).collect(); + // 删除数据库记录 + db.delete_albums_batch(&dup_ids).await; + // 删除 search.db 数据 + for (id, _) in to_delete { + search_db.delete_album(&id.to_string()).await.ok(); + } + // 删除磁盘文件夹 + for (_, doc) in to_delete { + let folder = images_dir.join(doc); + if folder.exists() { + if let Err(e) = std::fs::remove_dir_all(&folder) { + eprintln!("[管线·PhaseZ1] 删除文件夹失败 {}: {}", folder.display(), e); + } else { + println!("[管线·PhaseZ1] 删除文件夹 {}", folder.display()); + } + } + } + total_deleted += to_delete.len(); + for (id, doc) in to_delete { + println!("[管线·PhaseZ1] 删除重复相册 {} ({}),保留 {} ({})", id, doc, keep_id, keep_document); } } if total_deleted == 0 { @@ -316,12 +334,12 @@ pub struct PipelineResult { /// Phase ③ 增量解析脏集 → 只扫脏集 /// Phase ④ 人物合并 + 重计 → 1次人物扫描 /// Phase ⑤ 后校验标签中的人名 -pub async fn run_pipeline(db: &Database) -> PipelineResult { +pub async fn run_pipeline(db: &Database, search_db: &SearchDb) -> PipelineResult { // Phase Z0:删除空相册 let empty_albums_deleted = phase_delete_empty_albums(db).await; - // Phase Z1:去重同名相册 - let dup_albums_deleted = phase_dedup_albums(db).await; + // Phase Z1:去重同名相册(含删除文件夹和 search.db 数据) + let dup_albums_deleted = phase_dedup_albums(db, search_db).await; // Phase ①:合并预处理(1 次相册扫描) let pre = phase_preprocess_combined(db).await; diff --git a/src/main.rs b/src/main.rs index d4f494b..bcff9d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -122,8 +122,16 @@ async fn main() -> std::io::Result<()> { Err(e) => error!("[启动] 导入失败: {}", e), } - // ② 查找新相册 / 半成品 - info!("[启动] ② 查找新相册..."); + // ② 清理:删除空相册 + 去重 + info!("[启动] ② 清理空相册和重复相册..."); + let empty = engine::pipeline::phase_delete_empty_albums(&boot_db).await; + let dedup = engine::pipeline::phase_dedup_albums(&boot_db, &boot_search_db).await; + if empty > 0 || dedup > 0 { + info!("[启动] 清理完成:删除空相册 {},去重删除 {}", empty, dedup); + } + + // ③ 查找新相册 / 半成品 + info!("[启动] ③ 查找新相册..."); let dirty = engine::pipeline::phase_collect_new_albums(&boot_db).await; let new_count = dirty.len(); if new_count > 0 { @@ -134,8 +142,8 @@ async fn main() -> std::io::Result<()> { info!("[启动] 没有新相册"); } - // ③ 刷新所有内存缓存(从 search.db 读取) - info!("[启动] ③ 刷新所有内存缓存..."); + // ④ 刷新所有内存缓存(从 search.db 读取) + info!("[启动] ④ 刷新所有内存缓存..."); cache::refresh_all_caches(&boot_db, &boot_search_db, &boot_albums_cache, &boot_persons_cache, &boot_tags_cache).await; info!("[启动] 启动管线执行完毕(新相册={})", new_count); }); diff --git a/src/routes/pipeline.rs b/src/routes/pipeline.rs index dfec3de..8f45187 100644 --- a/src/routes/pipeline.rs +++ b/src/routes/pipeline.rs @@ -8,7 +8,7 @@ pub async fn pipeline_run( db: web::Data>, search_db: web::Data>, ) -> impl Responder { - let result = engine::pipeline::run_pipeline(db.get_ref()).await; + let result = engine::pipeline::run_pipeline(db.get_ref(), search_db.get_ref()).await; let synced = engine::pipeline::sync_albums_to_search_db(db.get_ref(), search_db.get_ref()).await; HttpResponse::Ok().json(serde_json::json!({