update code level

This commit is contained in:
lychang
2026-03-25 07:29:57 +08:00
parent af15d9f3d1
commit e53ee4b2d0
7 changed files with 397 additions and 94 deletions

View File

@@ -9,6 +9,8 @@ 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 crate::models::AlbumsCache;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
@@ -50,6 +52,47 @@ async fn main() -> std::io::Result<()> {
// 初始化图片处理器
let image_processor = Arc::new(image_processor::ImageProcessor::new(config.storage.clone()));
// 初始化相册内存缓存
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=关键词 - 搜索相册");
@@ -80,9 +123,11 @@ async fn main() -> std::io::Result<()> {
.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)))
// 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))

View File

@@ -34,7 +34,7 @@ pub struct AlbumWithImages {
pub image_count: i64,
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AlbumInfo {
pub id: Uuid,
pub title: String,
@@ -91,4 +91,86 @@ pub struct FavoriteWithImage {
pub struct FavoriteStatus {
pub is_favorited: bool,
pub favorite_id: Option<Uuid>,
}
// 内存缓存结构
#[derive(Debug, Clone)]
pub struct AlbumsCache {
pub albums: Vec<AlbumInfo>,
pub search_index: std::collections::HashMap<String, Vec<AlbumInfo>>,
pub last_updated: chrono::DateTime<chrono::Utc>,
}
impl AlbumsCache {
pub fn new() -> Self {
Self {
albums: Vec::new(),
search_index: std::collections::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.clone();
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.clone())
.or_insert_with(Vec::new)
.push(album.clone());
}
println!("✅ 相册缓存已更新,共 {} 个相册,{} 个搜索索引项",
self.albums.len(), self.search_index.len());
}
pub fn search(&self, query: &str) -> Vec<AlbumInfo> {
let query_lower = query.to_lowercase();
let mut results = Vec::new();
let mut seen_ids = std::collections::HashSet::new();
// 搜索完整匹配
if let Some(matches) = self.search_index.get(&query_lower) {
for album in matches {
if seen_ids.insert(album.id) {
results.push(album.clone());
}
}
}
// 搜索单词匹配
for word in query_lower.split_whitespace() {
if let Some(matches) = self.search_index.get(word) {
for album in matches {
if seen_ids.insert(album.id) {
results.push(album.clone());
}
}
}
}
results
}
}

View File

@@ -2,38 +2,19 @@ use crate::database::Database;
use crate::image_processor::ImageProcessor;
use crate::models::*;
use actix_web::{HttpResponse, Responder, web};
use std::collections::HashMap;
use std::fs;
use std::sync::Arc;
use tokio::sync::RwLock;
use uuid::Uuid;
use crate::image_processor;
pub async fn get_albums(
db: web::Data<Arc<Database>>,
query: web::Query<std::collections::HashMap<String, String>>,
) -> impl Responder {
let page = query.get("page").and_then(|s| s.parse::<usize>().ok());
let limit = query.get("limit").and_then(|s| s.parse::<usize>().ok());
match db.get_albums(page, limit).await {
Ok(albums) => HttpResponse::Ok().json(albums),
Err(e) => {
eprintln!("获取相册列表失败: {}", e);
HttpResponse::InternalServerError().json(serde_json::json!({
"error": "获取相册列表失败"
}))
}
}
}
// 收藏功能API路由
// 获取用户的收藏列表
pub async fn get_favorites(
db: web::Data<Arc<Database>>,
) -> impl Responder {
pub async fn get_favorites(db: web::Data<Arc<Database>>) -> impl Responder {
// 这里使用固定用户ID,实际项目中应该从认证信息中获取
let user_id = "default_user".to_string();
match db.get_user_favorites(&user_id).await {
Ok(favorites) => HttpResponse::Ok().json(favorites),
Err(e) => {
@@ -58,10 +39,10 @@ pub async fn check_favorite_status(
}));
}
};
// 这里使用固定用户ID,实际项目中应该从认证信息中获取
let user_id = "default_user".to_string();
match db.get_favorite_status(&user_id, image_id).await {
Ok(status) => HttpResponse::Ok().json(status),
Err(e) => {
@@ -80,7 +61,7 @@ pub async fn add_favorite(
) -> impl Responder {
// 这里使用固定用户ID,实际项目中应该从认证信息中获取
let user_id = "default_user".to_string();
match db.add_favorite(&user_id, favorite_data.into_inner()).await {
Ok(favorite_id) => HttpResponse::Created().json(serde_json::json!({
"id": favorite_id,
@@ -108,7 +89,7 @@ pub async fn remove_favorite(
}));
}
};
match db.remove_favorite(favorite_id).await {
Ok(_) => HttpResponse::Ok().json(serde_json::json!({
"message": "取消收藏成功"
@@ -135,10 +116,10 @@ pub async fn remove_favorite_by_image(
}));
}
};
// 这里使用固定用户ID,实际项目中应该从认证信息中获取
let user_id = "default_user".to_string();
match db.remove_favorite_by_image(&user_id, image_id).await {
Ok(_) => HttpResponse::Ok().json(serde_json::json!({
"message": "取消收藏成功"
@@ -198,35 +179,106 @@ pub async fn get_original_image(
}
}
pub async fn get_albums(
albums_cache: web::Data<Arc<RwLock<crate::models::AlbumsCache>>>,
query: web::Query<HashMap<String, String>>,
) -> impl Responder {
let page = query.get("page").and_then(|s| s.parse::<usize>().ok()).unwrap_or(1);
let limit = query.get("limit").and_then(|s| s.parse::<usize>().ok()).unwrap_or(50);
let offset = (page - 1) * limit;
// 从内存缓存获取数据
let cache = albums_cache.read().await;
let total_albums = cache.albums.len();
// 应用分页
let start = offset.min(total_albums);
let end = (offset + limit).min(total_albums);
let paginated_albums = if start < end {
cache.albums[start..end].to_vec()
} else {
Vec::new()
};
drop(cache); // 释放读锁
HttpResponse::Ok().json(paginated_albums)
}
pub async fn search_albums(
db: web::Data<Arc<Database>>,
albums_cache: web::Data<Arc<RwLock<crate::models::AlbumsCache>>>,
query: web::Query<std::collections::HashMap<String, String>>,
) -> impl Responder {
let q = query.get("q").map(|s| s.as_str()).unwrap_or("");
let page = query.get("page").and_then(|s| s.parse::<usize>().ok());
let limit = query.get("limit").and_then(|s| s.parse::<usize>().ok());
let page = query
.get("page")
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(1);
let limit = query
.get("limit")
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(50);
let offset = (page - 1) * limit;
if q.is_empty() {
return match db.get_albums(page, limit).await {
Ok(albums) => HttpResponse::Ok().json(albums),
Err(e) => {
eprintln!("获取相册列表失败: {}", e);
HttpResponse::InternalServerError().json(serde_json::json!({
"error": "获取相册列表失败"
}))
}
// 如果没有搜索词,返回所有相册(带分页)
let cache = albums_cache.read().await;
let total_albums = cache.albums.len();
// 应用分页
let start = offset.min(total_albums);
let end = (offset + limit).min(total_albums);
let paginated_albums = if start < end {
cache.albums[start..end].to_vec()
} else {
Vec::new()
};
drop(cache);
return HttpResponse::Ok().json(paginated_albums);
}
match db.search_albums(q, page, limit).await {
Ok(albums) => HttpResponse::Ok().json(albums),
Err(e) => {
eprintln!("搜索相册失败: {}", e);
HttpResponse::InternalServerError().json(serde_json::json!({
"error": "搜索失败"
}))
}
// 从内存缓存搜索
let cache = albums_cache.read().await;
let mut search_results = cache.search(q);
// 应用分页到搜索结果
let total_results = search_results.len();
let start = offset.min(total_results);
let end = (offset + limit).min(total_results);
let paginated_results = if start < end {
search_results.drain(start..end).collect()
} else {
Vec::new()
};
drop(cache);
HttpResponse::Ok().json(paginated_results)
}
pub async fn get_random_album(
albums_cache: web::Data<Arc<RwLock<crate::models::AlbumsCache>>>,
) -> impl Responder {
// 从内存缓存获取数据
let cache = albums_cache.read().await;
if cache.albums.is_empty() {
drop(cache);
return HttpResponse::NotFound().json(serde_json::json!({
"error": "没有可用的相册"
}));
}
// 使用随机数生成器选择一个随机相册
use rand::Rng;
let mut rng = rand::thread_rng();
let random_index = rng.gen_range(0..cache.albums.len());
let random_album = cache.albums[random_index].clone();
drop(cache);
HttpResponse::Ok().json(random_album)
}
pub async fn get_album_detail(
@@ -320,7 +372,10 @@ pub async fn get_thumbnail(
match fs::read(&original_path) {
Ok(data) => {
// 快速生成小尺寸缩略图(不进行复杂处理)
match image_processor.generate_fast_thumbnail(&data, &image.id.to_string()).await {
match image_processor
.generate_fast_thumbnail(&data, &image.id.to_string())
.await
{
Ok(thumbnail_data) => {
// 异步保存高质量缩略图到文件(不阻塞响应)
let config_clone = image_processor.config.clone();
@@ -330,11 +385,18 @@ pub async fn get_thumbnail(
let thumbnail_path = config_clone
.thumbnails_dir
.join(filename_clone.to_string());
// 生成高质量缩略图并保存
if let Ok(high_quality_data) = ImageProcessor::new(config_clone)
.generate_thumbnail_from_memory(&original_data_clone, &filename_clone.to_string()).await {
if let Err(e) = fs::write(&thumbnail_path, &high_quality_data) {
.generate_thumbnail_from_memory(
&original_data_clone,
&filename_clone.to_string(),
)
.await
{
if let Err(e) =
fs::write(&thumbnail_path, &high_quality_data)
{
eprintln!("保存高质量缩略图失败: {}", e);
}
}
@@ -350,8 +412,11 @@ pub async fn get_thumbnail(
Err(e) => {
eprintln!("快速缩略图生成失败: {}", e);
// 如果快速生成失败,返回原始图片的小尺寸版本
let small_data = image_processor.resize_image_to_max(&data, 800).await;
HttpResponse::Ok().content_type(image.mime_type).body(small_data)
let small_data =
image_processor.resize_image_to_max(&data, 800).await;
HttpResponse::Ok()
.content_type(image.mime_type)
.body(small_data)
}
}
}