update code level

This commit is contained in:
lychang
2026-07-21 02:11:53 +08:00
parent 9c3ce4ec0d
commit f572c7370d
5 changed files with 126 additions and 40 deletions

16
src/cache/mod.rs vendored
View File

@@ -246,14 +246,20 @@ pub async fn refresh_all_caches(
a
}).collect();
let mut c = albums_cache.write().await;
c.update(enriched);
// 2. 刷新标签(从 search.db)
// 2. 刷新标签(tag→albums 倒排索引直接从已富化的 album 列表构建,零 DB 查询)
let popular_tags = search_db.get_popular_tags(200).await;
let tag_albums = search_db.get_all_tag_albums().await;
let mut tag_albums: HashMap<String, Vec<AlbumInfo>> = HashMap::new();
for album in &enriched {
for tag in &album.tags {
tag_albums.entry(tag.clone()).or_default().push(album.clone());
}
}
let mut tc = tags_cache.write().await;
tc.update(popular_tags, tag_albums);
// 3. 刷新相册缓存(依赖 enriched 数据,必须在标签刷新之后执行)
let mut c = albums_cache.write().await;
c.update(enriched);
}
// 3. 刷新人物(从 search.db)

View File

@@ -228,10 +228,66 @@ impl Database {
pub async fn delete_album(&self, album_id: Uuid) -> Result<(), sqlx::Error> {
let _guard = self.write_lock.lock().await;
sqlx::query("DELETE FROM albums WHERE id = ?").bind(album_id.to_string()).execute(&self.pool).await?;
let aid = album_id.to_string();
// 先删除 person_albums(无 CASCADE)
sqlx::query("DELETE FROM person_albums WHERE album_id = ?").bind(&aid).execute(&self.pool).await?;
// 再删除 albums(images/favorites/album_tags 由 CASCADE 自动处理)
sqlx::query("DELETE FROM albums WHERE id = ?").bind(&aid).execute(&self.pool).await?;
Ok(())
}
/// 批量删除相册(级联清理所有关联数据)
pub async fn delete_albums_batch(&self, album_ids: &[Uuid]) -> usize {
if album_ids.is_empty() { return 0; }
let _guard = self.write_lock.lock().await;
for id in album_ids {
let aid = id.to_string();
sqlx::query("DELETE FROM person_albums WHERE album_id = ?").bind(&aid).execute(&self.pool).await.ok();
sqlx::query("DELETE FROM albums WHERE id = ?").bind(&aid).execute(&self.pool).await.ok();
}
album_ids.len()
}
/// 查询 image_count = 0 的空相册
pub async fn get_empty_albums(&self) -> Vec<AlbumInfo> {
let rows = sqlx::query(
r#"SELECT id, title, document, description, cover_image_id, image_count, created_at
FROM albums WHERE image_count IS NULL OR image_count = 0
ORDER BY created_at DESC"#
).fetch_all(&self.pool).await.unwrap_or_default();
rows.iter().map(|r| {
let id_str = r.get::<String, _>("id");
AlbumInfo {
id: Uuid::parse_str(&id_str).unwrap_or_else(|_| Uuid::nil()),
title: r.get("title"), document: r.get("document"),
description: r.get("description"), cover_image_id: r.get("cover_image_id"),
image_count: r.get("image_count"), created_at: r.get("created_at"),
tags: vec![], persons: vec![],
}
}).collect()
}
/// 查找重名相册,返回 (keep_id, [duplicate_ids])
pub async fn find_duplicate_albums(&self) -> Vec<(Uuid, Vec<Uuid>)> {
let rows = sqlx::query(
r#"SELECT id, title FROM albums WHERE title != '' ORDER BY created_at ASC"#
).fetch_all(&self.pool).await.unwrap_or_default();
let mut title_map: std::collections::HashMap<String, Vec<Uuid>> = std::collections::HashMap::new();
for r in &rows {
let title: String = r.get("title");
let id_str: String = r.get("id");
if let Ok(id) = Uuid::parse_str(&id_str) {
title_map.entry(title).or_default().push(id);
}
}
title_map.into_iter()
.filter(|(_, ids)| ids.len() > 1)
.map(|(_, ids)| (ids[0], ids[1..].to_vec()))
.collect()
}
pub async fn update_album_cover(&self, album_id: Uuid, image_id: Uuid) -> Result<(), sqlx::Error> {
sqlx::query("UPDATE albums SET cover_image_id = ? WHERE id = ?")
.bind(image_id.to_string()).bind(album_id.to_string()).execute(&self.pool).await?;

View File

@@ -244,6 +244,47 @@ async fn phase_clean_persons_combined(db: &Database) -> (DirtySet, usize, usize)
(dirty, non_persons_deleted, stop_word_persons_removed)
}
// ════════════════════════════════════════════════
// Phase Z0:删除空相册(image_count = 0)
// ════════════════════════════════════════════════
/// 删除所有图片数为 0 的空相册
pub async fn phase_delete_empty_albums(db: &Database) -> usize {
let empty = db.get_empty_albums().await;
let count = empty.len();
if count == 0 {
println!("[管线·PhaseZ0] 无空相册需要删除");
return 0;
}
let ids: Vec<uuid::Uuid> = empty.iter().map(|a| a.id).collect();
let deleted = db.delete_albums_batch(&ids).await;
println!("[管线·PhaseZ0] 删除 {} 个空相册(无图片)", deleted);
deleted
}
// ════════════════════════════════════════════════
// Phase Z1:去重同名相册
// ════════════════════════════════════════════════
/// 查找同名的相册,只保留第一个(最早创建的),删除其余
pub async fn phase_dedup_albums(db: &Database) -> usize {
let dups = db.find_duplicate_albums().await;
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);
}
}
if total_deleted == 0 {
println!("[管线·PhaseZ1] 无重复相册");
} else {
println!("[管线·PhaseZ1] 去重完成:删除 {} 个重复相册", total_deleted);
}
total_deleted
}
// ════════════════════════════════════════════════
// Pipeline 一键执行
// ════════════════════════════════════════════════
@@ -262,16 +303,26 @@ pub struct PipelineResult {
pub persons_merged: usize, // Phase ④
pub aliases_moved: usize, // Phase ④
pub tags_removed_person: usize, // Phase ⑤
pub empty_albums_deleted: usize, // Phase Z0
pub dup_albums_deleted: usize, // Phase Z1
}
/// 一键执行精简管线(5 阶段,2-3 次全库扫描)
/// 一键执行精简管线
///
/// Phase Z0 删除空相册
/// Phase Z1 去重同名相册
/// Phase ① 合并预处理(清洗+短标签+人名标签+新相册) → 1次相册扫描
/// Phase ② 合并人物清理(非人物+停用词人物) → 1次人物扫描
/// Phase ③ 增量解析脏集 → 只扫脏集
/// Phase ④ 人物合并 + 重计 → 1次人物扫描
/// Phase ⑤ 后校验标签中的人名
pub async fn run_pipeline(db: &Database) -> 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 ①:合并预处理(1 次相册扫描)
let pre = phase_preprocess_combined(db).await;
let tags_cleaned = pre.tags_cleaned;
@@ -305,6 +356,7 @@ pub async fn run_pipeline(db: &Database) -> PipelineResult {
non_persons_deleted, stop_word_persons_removed, new_albums_found,
persons_updated, tags_updated, persons_merged,
aliases_moved, tags_removed_person,
empty_albums_deleted, dup_albums_deleted,
}
}

View File

@@ -25,9 +25,12 @@ pub async fn pipeline_run(
"persons_merged": result.persons_merged,
"aliases_moved": result.aliases_moved,
"tags_removed_person": result.tags_removed_person,
"empty_albums_deleted": result.empty_albums_deleted,
"dup_albums_deleted": result.dup_albums_deleted,
"search_db_synced": synced,
"message": format!(
"管线执行完毕:清洗标点{}相册,移除短标签{},检测人名标签{},脏集{}相册,删除非人物{},清除停用词重叠人物{},发现新相册{},更新人名{},更新标签{},合并人物{},迁移别名{},后移除人名标签{},同步标签/人物到search.db{}",
"管线执行完毕:删除空相册{},去重{},清洗标点{}相册,移除短标签{},检测人名标签{},脏集{}相册,删除非人物{},清除停用词重叠人物{},发现新相册{},更新人名{},更新标签{},合并人物{},迁移别名{},后移除人名标签{},同步标签/人物到search.db{}",
result.empty_albums_deleted, result.dup_albums_deleted,
result.tags_cleaned, result.short_tags_removed, result.person_like_tags_removed, result.dirty_albums_detected,
result.non_persons_deleted, result.stop_word_persons_removed, result.new_albums_found,
result.persons_updated, result.tags_updated,

View File

@@ -151,41 +151,10 @@ impl SearchDb {
rows.iter().map(|r| TagStat {
tag: r.get("name"),
count: r.get("cnt"),
album_count: 0,
album_count: r.get("cnt"),
}).collect()
}
/// 获取标签倒排索引:tag → albums(缓存烘焙用)
pub async fn get_all_tag_albums(&self) -> HashMap<String, Vec<AlbumInfo>> {
let rows = sqlx::query(
r#"SELECT t.name as tag_name,
a.id, a.title, a.cover_image_id, a.image_count, a.created_at
FROM tags t
JOIN album_tags at ON t.id = at.tag_id
JOIN albums a ON a.id = at.album_id
ORDER BY a.created_at DESC"#
).fetch_all(&self.pool).await.unwrap_or_default();
let mut map: HashMap<String, Vec<AlbumInfo>> = HashMap::new();
for r in &rows {
let tag_name: String = r.get("tag_name");
let id_str: String = r.get("id");
let album = AlbumInfo {
id: Uuid::parse_str(&id_str).unwrap_or_else(|_| Uuid::nil()),
title: r.get("title"),
document: String::new(),
description: None,
cover_image_id: r.get("cover_image_id"),
image_count: r.get("image_count"),
created_at: r.get("created_at"),
tags: vec![],
persons: vec![],
};
map.entry(tag_name).or_default().push(album);
}
map
}
/// 按标签搜索相册
pub async fn search_albums_by_tag(&self, tag: &str) -> Vec<AlbumInfo> {
let rows = sqlx::query(