update
This commit is contained in:
141
src/cache/mod.rs
vendored
141
src/cache/mod.rs
vendored
@@ -1,5 +1,6 @@
|
||||
//! 缓存层 — 内存缓存管理
|
||||
|
||||
use crate::db::Database;
|
||||
use crate::models::*;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
@@ -88,14 +89,23 @@ impl AlbumsCache {
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct TagCache {
|
||||
pub popular_tags: Vec<TagStat>,
|
||||
/// tag → 包含该标签的相册列表(倒排索引,供按标签搜索用)
|
||||
pub tag_albums: HashMap<String, Vec<AlbumInfo>>,
|
||||
pub last_updated: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
impl TagCache {
|
||||
pub fn update(&mut self, tags: Vec<TagStat>) {
|
||||
pub fn update(&mut self, tags: Vec<TagStat>, tag_albums: HashMap<String, Vec<AlbumInfo>>) {
|
||||
self.popular_tags = tags;
|
||||
self.tag_albums = tag_albums;
|
||||
self.last_updated = chrono::Utc::now();
|
||||
info!("标签缓存已更新,共 {} 个热门标签", self.popular_tags.len());
|
||||
info!("标签缓存已更新,共 {} 个热门标签,{} 个标签倒排索引项",
|
||||
self.popular_tags.len(), self.tag_albums.len());
|
||||
}
|
||||
|
||||
/// 按标签搜索相册(从缓存中检索)
|
||||
pub fn search_albums(&self, tag: &str) -> Vec<AlbumInfo> {
|
||||
self.tag_albums.get(tag).cloned().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,30 +113,127 @@ impl TagCache {
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct PersonCache {
|
||||
pub persons: Vec<PersonWithCounts>,
|
||||
/// 搜索索引:关键词小写 → person_id 集合
|
||||
pub search_index: HashMap<String, Vec<i64>>,
|
||||
/// person_id → person 投影(用于按 id 快速访问)
|
||||
pub id_map: HashMap<i64, PersonWithCounts>,
|
||||
pub last_updated: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
impl PersonCache {
|
||||
pub fn update(&mut self, persons: Vec<PersonWithCounts>) {
|
||||
self.search_index.clear();
|
||||
self.id_map.clear();
|
||||
|
||||
for p in &persons {
|
||||
// 构建搜索索引(姓名 + 所有别名)
|
||||
let name_lower = p.name.to_lowercase();
|
||||
for word in name_lower.split_whitespace() {
|
||||
if word.len() >= 2 {
|
||||
self.search_index.entry(word.to_string()).or_default().push(p.id);
|
||||
}
|
||||
}
|
||||
self.search_index.entry(name_lower.clone()).or_default().push(p.id);
|
||||
// 显示名
|
||||
if let Some(ref dn) = p.display_name {
|
||||
let dn_lower = dn.to_lowercase();
|
||||
for word in dn_lower.split_whitespace() {
|
||||
if word.len() >= 2 {
|
||||
self.search_index.entry(word.to_string()).or_default().push(p.id);
|
||||
}
|
||||
}
|
||||
self.search_index.entry(dn_lower).or_default().push(p.id);
|
||||
}
|
||||
// 别名
|
||||
for alias in &p.aliases {
|
||||
let alias_lower = alias.to_lowercase();
|
||||
for word in alias_lower.split_whitespace() {
|
||||
if word.len() >= 2 {
|
||||
self.search_index.entry(word.to_string()).or_default().push(p.id);
|
||||
}
|
||||
}
|
||||
self.search_index.entry(alias_lower).or_default().push(p.id);
|
||||
}
|
||||
self.id_map.insert(p.id, p.clone());
|
||||
}
|
||||
|
||||
self.persons = persons;
|
||||
self.last_updated = chrono::Utc::now();
|
||||
info!("人物缓存已更新,共 {} 人", self.persons.len());
|
||||
info!("人物缓存已更新,共 {} 人,{} 个搜索索引项",
|
||||
self.persons.len(), self.search_index.len());
|
||||
}
|
||||
}
|
||||
|
||||
/// 共享缓存
|
||||
pub struct AppCache {
|
||||
pub albums: Arc<RwLock<AlbumsCache>>,
|
||||
pub tags: Arc<RwLock<TagCache>>,
|
||||
pub persons: Arc<RwLock<PersonCache>>,
|
||||
}
|
||||
/// 从缓存搜索人物(按姓名/别名)
|
||||
pub fn search(&self, query: &str) -> Vec<PersonWithCounts> {
|
||||
let q = query.to_lowercase().trim().to_string();
|
||||
if q.is_empty() || q.len() < 2 { return Vec::new(); }
|
||||
|
||||
impl AppCache {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
albums: Arc::new(RwLock::new(AlbumsCache::new())),
|
||||
tags: Arc::new(RwLock::new(TagCache::default())),
|
||||
persons: Arc::new(RwLock::new(PersonCache::default())),
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
|
||||
// 1. 完整匹配
|
||||
if let Some(ids) = self.search_index.get(&q) {
|
||||
for id in ids {
|
||||
if seen.insert(*id) {}
|
||||
}
|
||||
}
|
||||
// 2. 单词匹配
|
||||
for word in q.split_whitespace() {
|
||||
if word.len() >= 2 {
|
||||
if let Some(ids) = self.search_index.get(word) {
|
||||
for id in ids {
|
||||
seen.insert(*id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 3. 模糊匹配
|
||||
if seen.is_empty() {
|
||||
for p in &self.persons {
|
||||
let full = format!("{} {} {}", p.name.to_lowercase(),
|
||||
p.display_name.as_deref().unwrap_or(""),
|
||||
p.aliases.join(" ").to_lowercase());
|
||||
if full.contains(&q) {
|
||||
seen.insert(p.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
seen.iter()
|
||||
.filter_map(|id| self.id_map.get(id).cloned())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 刷新所有缓存(相册 + 标签 + 人物)
|
||||
pub async fn refresh_all_caches(
|
||||
db: &Database,
|
||||
albums_cache: &Arc<RwLock<AlbumsCache>>,
|
||||
persons_cache: &Arc<RwLock<PersonCache>>,
|
||||
tags_cache: &Arc<RwLock<TagCache>>,
|
||||
) {
|
||||
// 1. 刷新相册
|
||||
if let Ok(albums) = db.get_all_albums().await {
|
||||
let mut c = albums_cache.write().await;
|
||||
c.update(albums.clone());
|
||||
|
||||
// 2. 刷新标签
|
||||
let popular_tags = db.get_popular_tags(200).await;
|
||||
let tag_albums = db.get_all_tag_albums().await;
|
||||
let mut tc = tags_cache.write().await;
|
||||
tc.update(popular_tags, tag_albums);
|
||||
}
|
||||
|
||||
// 3. 刷新人物
|
||||
let persons = db.get_all_persons_with_aliases().await;
|
||||
let mut pc = persons_cache.write().await;
|
||||
pc.update(persons);
|
||||
|
||||
info!("全部缓存已刷新");
|
||||
}
|
||||
|
||||
/// 仅刷新人物缓存(管线执行后调用)
|
||||
pub async fn refresh_persons_cache(db: &Database, persons_cache: &Arc<RwLock<PersonCache>>) {
|
||||
let persons = db.get_all_persons_with_aliases().await;
|
||||
let mut pc = persons_cache.write().await;
|
||||
pc.update(persons);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user