230 lines
6.5 KiB
Rust
230 lines
6.5 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use tracing::info;
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct Album {
|
|
pub id: Uuid,
|
|
pub title: String,
|
|
pub description: Option<String>,
|
|
pub cover_image_id: Option<Uuid>,
|
|
pub image_count: i64, // 新增图片数量字段
|
|
pub created_at: String,
|
|
pub updated_at: String,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct Image {
|
|
pub id: Uuid,
|
|
pub album_id: Uuid,
|
|
pub filename: String,
|
|
pub original_url: Option<String>,
|
|
pub title: Option<String>,
|
|
pub description: Option<String>,
|
|
pub file_size: i64,
|
|
pub width: i32,
|
|
pub height: i32,
|
|
pub mime_type: String,
|
|
pub created_at: String,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct AlbumWithImages {
|
|
pub album: Album,
|
|
pub images: Vec<Image>,
|
|
pub image_count: i64,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct AlbumInfo {
|
|
pub id: Uuid,
|
|
pub title: String,
|
|
pub document: String,
|
|
pub description: Option<String>,
|
|
pub cover_image_id: Option<String>,
|
|
pub image_count: i64,
|
|
pub created_at: String,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct CreateAlbum {
|
|
pub title: String,
|
|
pub document: String,
|
|
pub description: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct CreateImage {
|
|
pub album_id: Uuid,
|
|
pub filename: String,
|
|
pub original_url: Option<String>,
|
|
pub title: Option<String>,
|
|
pub description: Option<String>,
|
|
pub file_size: i64,
|
|
pub width: i32,
|
|
pub height: i32,
|
|
pub mime_type: String,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct Favorite {
|
|
pub id: Uuid,
|
|
pub user_id: String,
|
|
pub image_id: Uuid,
|
|
pub album_id: Uuid,
|
|
pub created_at: String,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct CreateFavorite {
|
|
pub image_id: Uuid,
|
|
pub album_id: Uuid,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct FavoriteWithImage {
|
|
pub favorite: Favorite,
|
|
pub image: Image,
|
|
pub album_title: String,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct FavoriteStatus {
|
|
pub is_favorited: bool,
|
|
pub favorite_id: Option<Uuid>,
|
|
}
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct AlbumIndex {
|
|
pub title: String,
|
|
pub images: Vec<String>,
|
|
}
|
|
// 内存缓存结构
|
|
#[derive(Debug, Clone)]
|
|
pub struct AlbumsCache {
|
|
pub albums: Vec<AlbumInfo>,
|
|
pub search_index: HashMap<String, Vec<AlbumInfo>>,
|
|
pub last_updated: chrono::DateTime<chrono::Utc>,
|
|
}
|
|
|
|
impl AlbumsCache {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
albums: Vec::new(),
|
|
search_index: HashMap::new(),
|
|
last_updated: chrono::Utc::now(),
|
|
}
|
|
}
|
|
|
|
pub fn is_expired(&self) -> bool {
|
|
let now = chrono::Utc::now();
|
|
let duration = now - self.last_updated;
|
|
duration.num_minutes() >= 30 // 30分钟过期
|
|
}
|
|
|
|
pub fn update(&mut self, albums: Vec<AlbumInfo>) {
|
|
self.albums = albums; // 不再克隆整个向量,而是移动所有权
|
|
self.last_updated = chrono::Utc::now();
|
|
|
|
// 构建搜索索引
|
|
self.search_index.clear();
|
|
for album in &self.albums {
|
|
let title_lower = album.title.to_lowercase();
|
|
|
|
// 为每个单词创建索引
|
|
for word in title_lower.split_whitespace() {
|
|
if word.len() >= 2 {
|
|
// 只索引长度>=2的单词
|
|
self.search_index
|
|
.entry(word.to_string())
|
|
.or_insert_with(Vec::new)
|
|
.push(album.clone()); // 这里仍需要克隆单个元素
|
|
}
|
|
}
|
|
|
|
// 为完整标题创建索引
|
|
self.search_index
|
|
.entry(title_lower)
|
|
.or_insert_with(Vec::new)
|
|
.push(album.clone()); // 这里仍需要克隆单个元素
|
|
}
|
|
|
|
info!(
|
|
"相册缓存已更新,共 {} 个相册,{} 个搜索索引项",
|
|
self.albums.len(),
|
|
self.search_index.len()
|
|
);
|
|
}
|
|
|
|
pub fn search(&self, query: &str) -> Vec<AlbumInfo> {
|
|
let query_lower = query.to_lowercase().trim().to_string();
|
|
|
|
if query_lower.is_empty() {
|
|
return Vec::new();
|
|
}
|
|
|
|
let mut results = Vec::new();
|
|
let mut seen_ids = std::collections::HashSet::new();
|
|
|
|
// 1. 搜索完整匹配(最高优先级)
|
|
if let Some(matches) = self.search_index.get(&query_lower) {
|
|
for album in matches {
|
|
if seen_ids.insert(album.id) {
|
|
results.push(album.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. 搜索单词匹配
|
|
for word in query_lower.split_whitespace() {
|
|
if word.len() >= 2 {
|
|
// 只搜索长度>=2的单词
|
|
if let Some(matches) = self.search_index.get(word) {
|
|
for album in matches {
|
|
if seen_ids.insert(album.id) {
|
|
results.push(album.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. 模糊搜索:在相册标题和描述中搜索包含查询词的结果
|
|
if results.is_empty() {
|
|
for album in &self.albums {
|
|
let title_lower = album.title.to_lowercase();
|
|
let description_lower = album
|
|
.description
|
|
.as_ref()
|
|
.map(|d| d.to_lowercase())
|
|
.unwrap_or_default();
|
|
|
|
// 检查标题或描述是否包含搜索词
|
|
if title_lower.contains(&query_lower) || description_lower.contains(&query_lower) {
|
|
if seen_ids.insert(album.id) {
|
|
results.push(album.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 4. 如果还没有结果,尝试部分匹配(更宽松的搜索)
|
|
if results.is_empty() && query_lower.len() >= 2 {
|
|
for album in &self.albums {
|
|
let title_lower = album.title.to_lowercase();
|
|
|
|
// 检查标题是否包含搜索词的任何部分
|
|
if query_lower
|
|
.split_whitespace()
|
|
.any(|word| word.len() >= 2 && title_lower.contains(word))
|
|
{
|
|
if seen_ids.insert(album.id) {
|
|
results.push(album.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
results
|
|
}
|
|
} |