diff --git a/assets/index.html b/assets/index.html index 2a43b8a..6168052 100644 --- a/assets/index.html +++ b/assets/index.html @@ -16,7 +16,7 @@
diff --git a/assets/js/app.js b/assets/js/app.js index 2358298..5610c8b 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -53,8 +53,8 @@ function initSearch() { // 初始化无限滚动 function initInfiniteScroll() { let scrollTimeout; - - window.addEventListener('scroll', function() { + + window.addEventListener('scroll', function () { clearTimeout(scrollTimeout); scrollTimeout = setTimeout(handleScroll, 100); }); @@ -63,12 +63,12 @@ function initInfiniteScroll() { // 处理滚动事件 function handleScroll() { if (isLoadingMore || !hasMoreAlbums) return; - + // 检查是否滚动到底部 const scrollTop = window.scrollY || document.documentElement.scrollTop; const scrollHeight = document.documentElement.scrollHeight; const clientHeight = window.innerHeight; - + // 当距离底部还有200px时开始加载 if (scrollTop + clientHeight >= scrollHeight - 200) { loadMoreAlbums(); @@ -78,7 +78,7 @@ function handleScroll() { // 加载更多相册 async function loadMoreAlbums() { if (isLoadingMore || !hasMoreAlbums) return; - + isLoadingMore = true; try { await loadAlbums(); @@ -101,18 +101,13 @@ function showLoadMoreError() { 加载失败,点击重试
`; - + const existingError = container.querySelector('.load-more-error'); if (existingError) { existingError.remove(); } - - container.appendChild(errorElement); -} -// 防抖搜索 -function debouncedSearch() { - searchAlbums(); + container.appendChild(errorElement); } // 显示骨架屏 @@ -196,7 +191,7 @@ function appendAlbums(albums) { `; }).join(''); - + // 移除加载指示器并添加新相册 hideLoadingMore(); container.insertAdjacentHTML('beforeend', newAlbumsHTML); @@ -222,37 +217,37 @@ async function loadAlbums(reset = false) { showLoadingMore(); } - const response = await fetch(`${API_BASE}/albums?page=${currentPage}&limit=${albumsPerPage}`); + const response = await fetch(`${API_BASE}/albums`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } - + const newAlbums = await response.json(); - + // 检查是否还有更多数据 if (newAlbums.length < albumsPerPage) { hasMoreAlbums = false; } - + // 合并到现有相册列表 allAlbums = [...allAlbums, ...newAlbums]; - + if (currentPage === 1) { displayAlbums(allAlbums); } else { appendAlbums(newAlbums); } - + updateStats(allAlbums); // 初始化图片懒加载 initLazyLoading(); - + // 预加载下一批图片 preloadNextBatchImages(); - + currentPage++; - + // 隐藏加载更多指示器 hideLoadingMore(); } catch (error) { @@ -264,7 +259,7 @@ async function loadAlbums(reset = false) { // 搜索相册 // 防抖搜索函数 -function debouncedSearch() { +async function debouncedSearch() { const searchTerm = document.getElementById('search-input').value.toLowerCase().trim(); if (searchTerm === '') { @@ -274,28 +269,19 @@ function debouncedSearch() { return; } - // 在当前已加载的数据集中搜索 - const filteredAlbums = searchInLoadedAlbums(searchTerm); - - if (filteredAlbums.length > 0) { - // 如果当前已加载的数据中有匹配项,直接显示 - displayAlbums(filteredAlbums); - document.getElementById('stats').innerHTML = `在当前页面中找到 ${filteredAlbums.length} 个包含 "${searchTerm}" 的相册`; - } else { - // 如果没有找到匹配项,显示提示信息 - showSearchHint(searchTerm); - } + // 直接使用后端搜索API进行全站搜索 + await performServerSearch(searchTerm); } // 在当前已加载的数据集中搜索 function searchInLoadedAlbums(searchTerm) { if (!allAlbums.length) return []; - + return allAlbums.filter(album => { // 搜索标题和描述 const titleMatch = album.title.toLowerCase().includes(searchTerm); const descriptionMatch = album.description && album.description.toLowerCase().includes(searchTerm); - + return titleMatch || descriptionMatch; }); } @@ -328,21 +314,21 @@ async function performServerSearch(searchTerm) { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } - + const serverResults = await response.json(); - + if (serverResults.length === 0) { showError(`没有找到包含 "${searchTerm}" 的相册`); return; } - + // 显示服务器搜索结果 displayAlbums(serverResults); document.getElementById('stats').innerHTML = `在全站找到 ${serverResults.length} 个包含 "${searchTerm}" 的相册`; - + // 初始化图片懒加载 initLazyLoading(); - + } catch (error) { console.error('全站搜索失败:', error); showError('全站搜索失败,请重试'); @@ -416,7 +402,7 @@ function initLazyLoading() { threshold: 0.01 }); } - + const lazyImages = document.querySelectorAll('img.lazy'); lazyImages.forEach(img => { lazyLoadObserver.observe(img); @@ -427,14 +413,14 @@ function initLazyLoading() { function preloadNextBatchImages() { const imagesToPreload = []; const visibleImages = document.querySelectorAll('img.lazy'); - + // 预加载前10张可见图片 visibleImages.forEach((img, index) => { if (index < 10 && img.dataset.src) { imagesToPreload.push(img.dataset.src); } }); - + // 使用link预加载 imagesToPreload.forEach(src => { const link = document.createElement('link'); @@ -461,20 +447,24 @@ function showError(message) { document.getElementById('stats').innerHTML = ''; } -// 键盘事件处理 -function handleKeyPress(event) { - if (event.key === 'Enter') { - searchAlbums(); - } +// 搜索相册函数 +async function searchAlbums() { + await debouncedSearch(); } + // 性能监控 function logPerformance() { if ('performance' in window) { window.addEventListener('load', () => { setTimeout(() => { const navigationTiming = performance.getEntriesByType('navigation')[0]; - console.log('页面加载时间:', navigationTiming.loadEventEnd - navigationTiming.navigationStart, 'ms'); + if (navigationTiming && navigationTiming.loadEventEnd && navigationTiming.navigationStart) { + const loadTime = navigationTiming.loadEventEnd - navigationTiming.navigationStart; + console.log('页面加载时间:', loadTime, 'ms'); + } else { + console.log('页面加载时间: 无法计算'); + } }, 0); }); } @@ -502,7 +492,7 @@ function preloadCriticalResources() { '/assets/css/style.css', '/assets/webfonts/fa-solid-900.woff2' ]; - + criticalResources.forEach(resource => { const link = document.createElement('link'); link.rel = 'preload'; @@ -511,13 +501,13 @@ function preloadCriticalResources() { link.crossOrigin = 'anonymous'; document.head.appendChild(link); }); - + // 预加载第一屏可能需要的图片 setTimeout(() => { const firstScreenImages = [ '/assets/placeholder.svg' ]; - + firstScreenImages.forEach(img => { const preload = document.createElement('link'); preload.rel = 'preload'; @@ -532,7 +522,7 @@ function preloadCriticalResources() { function optimizeScrollPerformance() { // 使用requestAnimationFrame优化滚动 let ticking = false; - + function updateOnScroll() { // 在滚动时暂停非关键任务 if (!ticking) { @@ -544,8 +534,8 @@ function optimizeScrollPerformance() { ticking = true; } } - - window.addEventListener('scroll', updateOnScroll, { passive: true }); + + window.addEventListener('scroll', updateOnScroll, {passive: true}); } // 延迟加载非关键图片 @@ -589,7 +579,7 @@ function openRandomAlbum() { const randomButton = document.querySelector('.random-button'); randomButton.style.transform = 'scale(0.95)'; randomButton.disabled = true; - + // 显示加载状态 const originalText = randomButton.textContent; randomButton.textContent = '随机加载中...'; @@ -623,7 +613,7 @@ function openRandomAlbum() { randomButton.disabled = false; randomButton.textContent = originalText; }, 200); - + alert(error.message); }); } @@ -771,7 +761,7 @@ function previewFavorite(index) { // 显示预览模态框 function showPreviewModal(index) { const favorite = currentFavorites[index]; - + // 构建模态框HTML const modalHtml = `
@@ -830,16 +820,16 @@ function showPreviewModal(index) {
`; - + // 添加到页面 document.body.insertAdjacentHTML('beforeend', modalHtml); - + // 显示模态框 setTimeout(() => { const modal = document.getElementById('previewModal'); modal.classList.add('active'); }, 10); - + // 监听键盘事件 document.addEventListener('keydown', handlePreviewKeydown); } @@ -853,7 +843,7 @@ function closePreviewModal() { modal.remove(); }, 300); } - + // 移除键盘事件监听 document.removeEventListener('keydown', handlePreviewKeydown); } @@ -870,30 +860,30 @@ function navigatePreview(direction) { function updatePreviewContent(newIndex) { const favorite = currentFavorites[newIndex]; currentPreviewIndex = newIndex; - + // 更新标题和相册信息 const titleElement = document.querySelector('.preview-title h3'); const infoElement = document.querySelector('.preview-info'); if (titleElement) titleElement.textContent = favorite.image.title || '未命名图片'; if (infoElement) infoElement.textContent = `来自: ${favorite.album_title}`; - + // 更新计数器 const counterElement = document.querySelector('.preview-counter'); if (counterElement) counterElement.textContent = `${newIndex + 1} / ${currentFavorites.length}`; - + // 更新导航按钮状态 const prevButton = document.querySelector('.preview-prev'); const nextButton = document.querySelector('.preview-next'); if (prevButton) prevButton.disabled = newIndex === 0; if (nextButton) nextButton.disabled = newIndex === currentFavorites.length - 1; - + // 更新元数据 const metaElements = document.querySelectorAll('.preview-meta .meta-item'); if (metaElements.length >= 3) { metaElements[0].innerHTML = ` ${formatDate(favorite.favorite.created_at)}`; metaElements[1].innerHTML = ` ${favorite.image.width} × ${favorite.image.height}`; } - + // 更新操作按钮 const openAlbumButton = document.querySelector('.preview-action:first-child'); const removeButton = document.querySelector('.preview-remove'); @@ -903,22 +893,22 @@ function updatePreviewContent(newIndex) { if (removeButton) { removeButton.onclick = () => removeFavoriteFromPreview(favorite.favorite.id); } - + // 更新图片 const imageElement = document.getElementById('preview-image'); - - if (imageElement ) { + + if (imageElement) { // 显示加载状态 imageElement.style.opacity = '0'; - + // 加载新图片 const newImage = new Image(); newImage.src = `${API_BASE}/original/${favorite.image.id}`; newImage.alt = favorite.image.title || '预览图片'; newImage.className = 'preview-image'; - - newImage.onload = function() { + + newImage.onload = function () { // 替换图片 if (imageElement.parentNode) { imageElement.parentNode.replaceChild(newImage, imageElement); @@ -928,8 +918,8 @@ function updatePreviewContent(newIndex) { newImage.style.opacity = '1'; }; - - newImage.onerror = function() { + + newImage.onerror = function () { // 图片加载失败 imageElement.innerHTML = '图片加载失败'; }; @@ -1010,7 +1000,6 @@ function openFavoriteImage(favoriteId) { } // 导出函数供全局使用 -window.handleKeyPress = handleKeyPress; window.searchAlbums = searchAlbums; window.openRandomAlbum = openRandomAlbum; window.showFavorites = showFavorites; \ No newline at end of file diff --git a/src/image_processor.rs b/src/image_processor.rs index b9bddd2..f497908 100644 --- a/src/image_processor.rs +++ b/src/image_processor.rs @@ -85,8 +85,8 @@ impl ImageProcessor { // 使用快速解码和缩放 let img = image::load_from_memory(image_data)?; - // 快速缩放到小尺寸(200x150) - let thumbnail = img.thumbnail(200, 150); + // 快速缩放到小尺寸(200x200) + let thumbnail = img.thumbnail(200, 200); // 使用低质量JPEG压缩以加快编码速度 let mut output = Vec::new(); @@ -165,8 +165,8 @@ impl ImageProcessor { // 加载图片 let img = image::load_from_memory(image_data)?; - // 生成缩略图(400x300,保持宽高比) - let thumbnail = img.thumbnail(600, 400); + // 生成缩略图(600x600,保持宽高比) + let thumbnail = img.thumbnail(600, 600); // 将缩略图数据保存到内存缓冲区 let mut buffer = std::io::Cursor::new(Vec::new()); diff --git a/src/import.rs b/src/import.rs index 29107c5..e17687a 100644 --- a/src/import.rs +++ b/src/import.rs @@ -9,6 +9,51 @@ use crate::config::Config; use crate::database::Database; use crate::models::*; +/// 批量插入图片并设置封面缩略图的辅助函数 +async fn batch_insert_images_and_set_cover( + db: &Database, + images_to_insert: Vec, + album_id: Uuid, + album_path: &Path, + folder_name: &str, +) -> Result> { + if images_to_insert.is_empty() { + return Ok(0); + } + + println!(" 批量插入 {} 张图片...", images_to_insert.len()); + match db.add_images(images_to_insert).await { + Ok(ids) => { + println!(" 批量插入成功,共插入 {} 张图片", ids.len()); + + // 设置第一张图片为封面图片并生成缩略图 + if let Some(first_image_id) = ids.first() { + println!(" 设置第一张图片为封面图片 (ID: {})...", first_image_id); + + // 更新相册的封面图片ID + if let Err(e) = db.update_album_cover(album_id, Some(*first_image_id)).await { + eprintln!(" 更新相册封面失败: {}", e); + } + + // 获取第一张图片的信息 + if let Ok(Some(first_image)) = db.get_image(*first_image_id).await { + // 生成缩略图 + match generate_thumbnail_for_image(&first_image, album_path, folder_name).await { + Ok(_) => println!(" 封面缩略图生成成功"), + Err(e) => eprintln!(" 封面缩略图生成失败: {}", e), + } + } + } + + Ok(ids.len()) + } + Err(e) => { + eprintln!(" 批量插入失败: {}", e); + Ok(0) + } + } +} + #[derive(Debug, Deserialize)] struct AlbumIndex { title: String, @@ -125,9 +170,6 @@ pub async fn import_existing_data(config: &Config, db: Arc) -> Result< // 继续处理下一个相册,不中断整个导入过程 } } - - // 在每个相册导入后添加短暂延迟,减少数据库压力 - tokio::time::sleep(std::time::Duration::from_millis(100)).await; } println!("🎉 导入完成!成功导入 {} 个相册,共 {} 张图片", successful_albums, total_imported); @@ -261,42 +303,8 @@ async fn import_single_album(db: &Database, folder_name: &str, path: &Path) -> R } } - // 批量插入图片 - if !images_to_insert.is_empty() { - println!(" 批量插入 {} 张图片...", images_to_insert.len()); - match db.add_images(images_to_insert).await { - Ok(ids) => { - println!(" 批量插入成功,共插入 {} 张图片", ids.len()); - - // 设置第一张图片为封面图片并生成缩略图 - if let Some(first_image_id) = ids.first() { - println!(" 设置第一张图片为封面图片 (ID: {})...", first_image_id); - - // 更新相册的封面图片ID - if let Err(e) = db.update_album_cover(album_id, Some(*first_image_id)).await { - eprintln!(" 更新相册封面失败: {}", e); - } - - // 获取第一张图片的信息 - if let Ok(Some(first_image)) = db.get_image(*first_image_id).await { - // 生成缩略图 - match generate_thumbnail_for_image(&first_image, path, folder_name).await { - Ok(_) => println!(" 封面缩略图生成成功"), - Err(e) => eprintln!(" 封面缩略图生成失败: {}", e), - } - } - } - - Ok(ids.len()) - } - Err(e) => { - eprintln!(" 批量插入失败: {}", e); - Ok(0) - } - } - } else { - Ok(0) - } + // 使用辅助函数批量插入图片并设置封面 + batch_insert_images_and_set_cover(&db, images_to_insert, album_id, path, folder_name).await } /// 快速扫描图片文件 @@ -438,47 +446,13 @@ async fn reimport_single_album( println!(" 发现 {} 张图片需要导入", images_to_insert.len()); - // 批量插入图片 - if !images_to_insert.is_empty() { - println!(" 批量插入 {} 张图片...", images_to_insert.len()); - match db.add_images(images_to_insert).await { - Ok(ids) => { - println!(" 批量插入成功,共插入 {} 张图片", ids.len()); - - // 设置第一张图片为封面图片并生成缩略图 - if let Some(first_image_id) = ids.first() { - println!(" 设置第一张图片为封面图片 (ID: {})...", first_image_id); - - // 更新相册的封面图片ID - if let Err(e) = db.update_album_cover(album_id, Some(*first_image_id)).await { - eprintln!(" 更新相册封面失败: {}", e); - } - - // 获取第一张图片的信息 - if let Ok(Some(first_image)) = db.get_image(*first_image_id).await { - // 生成缩略图 - match generate_thumbnail_for_image(&first_image, path, folder_name).await { - Ok(_) => println!(" 封面缩略图生成成功"), - Err(e) => eprintln!(" 封面缩略图生成失败: {}", e), - } - } - } - - Ok(ids.len()) - } - Err(e) => { - eprintln!(" 批量插入失败: {}", e); - Ok(0) - } - } - } else { - Ok(0) - } + // 使用辅助函数批量插入图片并设置封面 + batch_insert_images_and_set_cover(&db, images_to_insert, album_id, path, folder_name).await } /// 为图片生成缩略图(使用ImageProcessor) async fn generate_thumbnail_for_image( - image: &crate::models::Image, + image: &Image, album_path: &Path, _folder_name: &str, ) -> Result<(), Box> { diff --git a/src/main.rs b/src/main.rs index 5323896..cc7203c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,7 +54,7 @@ async fn main() -> std::io::Result<()> { // 初始化相册内存缓存 let albums_cache = Arc::new(tokio::sync::RwLock::new(AlbumsCache::new())); - + // 初始加载相册数据到缓存 let initial_cache = Arc::clone(&albums_cache); let initial_db = Arc::clone(&db); @@ -76,10 +76,10 @@ async fn main() -> std::io::Result<()> { 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; @@ -131,10 +131,7 @@ async fn main() -> std::io::Result<()> { .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)) - .route( - "/api/original/{id}", - web::get().to(routes::get_original_image), - ) + .route("/api/original/{id}", web::get().to(routes::get_original_image)) // 收藏功能API路由 .route("/api/favorites", web::get().to(routes::get_favorites)) .route("/api/favorites/status/{id}", web::get().to(routes::check_favorite_status)) diff --git a/src/models.rs b/src/models.rs index 7518fbd..757696a 100644 --- a/src/models.rs +++ b/src/models.rs @@ -12,7 +12,7 @@ pub struct Album { pub updated_at: String, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct Image { pub id: Uuid, pub album_id: Uuid, @@ -147,11 +147,16 @@ impl AlbumsCache { } pub fn search(&self, query: &str) -> Vec { - let query_lower = query.to_lowercase(); + 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) { @@ -160,10 +165,45 @@ impl AlbumsCache { } } - // 搜索单词匹配 + // 2. 搜索单词匹配 for word in query_lower.split_whitespace() { - if let Some(matches) = self.search_index.get(word) { - for album in matches { + 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()); } diff --git a/src/routes.rs b/src/routes.rs index 92f8f76..28270a8 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -26,6 +26,60 @@ pub async fn get_favorites(db: web::Data>) -> impl Responder { } } +/// 检查缩略图质量并在需要时升级为高质量版本 +async fn check_and_upgrade_thumbnail_quality( + config: &crate::config::StorageConfig, + image: &Image, + original_path: &std::path::Path, +) -> Result<(), Box> { + let thumbnail_path = config.thumbnails_dir.join(image.id.to_string()); + + // 检查原始图片是否存在 + if !original_path.exists() { + return Ok(()); // 原始图片不存在,无法升级 + } + + // 读取缩略图文件信息 + let thumbnail_metadata = match std::fs::metadata(&thumbnail_path) { + Ok(meta) => meta, + Err(_) => return Ok(()), // 无法读取缩略图元数据 + }; + + // 检查缩略图文件大小(简单质量检测) + // 如果缩略图小于20KB,可能质量较低,需要升级 + if thumbnail_metadata.len() < 20 * 1024 { + // 读取原始图片数据 + let original_data = match std::fs::read(original_path) { + Ok(data) => data, + Err(e) => { + eprintln!("读取原始图片失败,无法升级缩略图: {}", e); + return Ok(()); + } + }; + + // 生成高质量缩略图 + let image_processor = crate::image_processor::ImageProcessor::new(config.clone()); + match image_processor + .generate_thumbnail_from_memory(&original_data, &image.id.to_string()) + .await + { + Ok(high_quality_data) => { + // 保存高质量缩略图 + if let Err(e) = std::fs::write(&thumbnail_path, &high_quality_data) { + eprintln!("保存高质量缩略图失败: {}", e); + } else { + println!("✅ 缩略图已升级为高质量版本 (ID: {})", image.id); + } + } + Err(e) => { + eprintln!("生成高质量缩略图失败: {}", e); + } + } + } + + Ok(()) +} + // 检查图片是否被收藏 pub async fn check_favorite_status( db: web::Data>, @@ -179,35 +233,28 @@ pub async fn get_original_image( } } -pub async fn get_albums( - albums_cache: web::Data>>, - query: web::Query>, -) -> impl Responder { - let page = query.get("page").and_then(|s| s.parse::().ok()).unwrap_or(1); - let limit = query.get("limit").and_then(|s| s.parse::().ok()).unwrap_or(50); - let offset = (page - 1) * limit; - +pub async fn get_albums(albums_cache: web::Data>>) -> impl Responder { // 从内存缓存获取数据 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 start = 0; + let end = 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( - albums_cache: web::Data>>, - query: web::Query>, + albums_cache: web::Data>>, + query: web::Query>, ) -> impl Responder { let q = query.get("q").map(|s| s.as_str()).unwrap_or(""); let page = query @@ -262,22 +309,22 @@ pub async fn get_random_album( ) -> 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) } @@ -350,10 +397,25 @@ pub async fn get_thumbnail( let original_path = image_processor.config.images_dir.join(&image.filename); if thumbnail_path.exists() { - // 返回缩略图 + // 返回现有缩略图,并异步检查是否需要替换为高质量版本 match fs::read(&thumbnail_path) { Ok(data) => { let mime_type = image_processor.detect_mime_type("jpg"); // 缩略图总是JPEG格式 + + // 异步检查缩略图质量并可能替换为高质量版本 + let config_clone = image_processor.config.clone(); + let image_clone = image.clone(); + let original_path_clone = original_path.clone(); + tokio::spawn(async move { + if let Err(e) = check_and_upgrade_thumbnail_quality( + &config_clone, + &image_clone, + &original_path_clone + ).await { + eprintln!("缩略图质量检查失败: {}", e); + } + }); + HttpResponse::Ok() .content_type(mime_type) .insert_header(("Cache-Control", "public, max-age=31536000")) // 1年缓存 @@ -405,7 +467,7 @@ pub async fn get_thumbnail( // 返回快速生成的缩略图数据 HttpResponse::Ok() .content_type("image/jpeg") - .insert_header(("Cache-Control", "public, max-age=31536000")) // 1年缓存 + .insert_header(("Cache-Control", "public, max-age=30")) // 0.5分钟缓存 .insert_header(("ETag", format!("\"{}\"", image_id))) .body(thumbnail_data) }