update code level
This commit is contained in:
@@ -995,12 +995,6 @@ function removeFavoriteFromPreview(favoriteId) {
|
||||
}
|
||||
}
|
||||
|
||||
// 排序收藏夹
|
||||
function sortFavorites(mode) {
|
||||
// 这里可以实现排序功能
|
||||
alert('排序功能开发中...');
|
||||
}
|
||||
|
||||
// 从收藏夹移除图片
|
||||
function removeFavorite(event, favoriteId) {
|
||||
event.stopPropagation(); // 阻止事件冒泡
|
||||
|
||||
405
src/database.rs
405
src/database.rs
@@ -1,9 +1,7 @@
|
||||
use sqlx::{SqlitePool, sqlite::SqlitePoolOptions, Row, Error as SqlxError};
|
||||
use sqlx::{SqlitePool, sqlite::SqlitePoolOptions, Row};
|
||||
use crate::config::Config;
|
||||
use crate::models::*;
|
||||
use uuid::Uuid;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
||||
pub struct Database {
|
||||
pub pool: SqlitePool,
|
||||
@@ -61,102 +59,55 @@ impl Database {
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
|
||||
/// 带重试机制的数据库操作执行器
|
||||
async fn execute_with_retry<F, T>(&self, operation: F, operation_name: &str) -> Result<T, sqlx::Error>
|
||||
where
|
||||
F: Fn() -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<T, sqlx::Error>> + Send>>,
|
||||
{
|
||||
const MAX_RETRIES: u32 = 3;
|
||||
const BASE_DELAY_MS: u64 = 100;
|
||||
|
||||
for attempt in 0..=MAX_RETRIES {
|
||||
match operation().await {
|
||||
Ok(result) => return Ok(result),
|
||||
Err(e) => {
|
||||
// 检查是否是死锁或超时错误
|
||||
if self.is_retryable_error(&e) && attempt < MAX_RETRIES {
|
||||
let delay_ms = BASE_DELAY_MS * 2u64.pow(attempt);
|
||||
eprintln!("⚠️ {} 操作失败 (尝试 {}): {}, 等待 {}ms 后重试",
|
||||
operation_name, attempt + 1, e, delay_ms);
|
||||
sleep(Duration::from_millis(delay_ms)).await;
|
||||
} else {
|
||||
eprintln!("❌ {} 操作失败 (尝试 {}): {}", operation_name, attempt + 1, e);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
/// 判断错误是否可重试
|
||||
fn is_retryable_error(&self, error: &SqlxError) -> bool {
|
||||
match error {
|
||||
SqlxError::Database(db_err) => {
|
||||
// SQLite 死锁错误码
|
||||
if let Some(code) = db_err.code() {
|
||||
code == "5" || // SQLITE_BUSY
|
||||
code == "6" || // SQLITE_LOCKED
|
||||
code == "261" // SQLITE_BUSY_SNAPSHOT
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
SqlxError::PoolTimedOut => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
pub async fn get_albums(&self, page: Option<usize>, limit: Option<usize>) -> Result<Vec<AlbumInfo>, sqlx::Error> {
|
||||
let page = page.unwrap_or(1);
|
||||
let limit = limit.unwrap_or(50); // 减少默认加载数量,提高响应速度
|
||||
let offset = (page - 1) * limit;
|
||||
|
||||
let albums = sqlx::query(
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
title,
|
||||
document,
|
||||
description,
|
||||
cover_image_id,
|
||||
image_count,
|
||||
created_at
|
||||
FROM albums
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
"#
|
||||
)
|
||||
.bind(limit as i64)
|
||||
.bind(offset as i64)
|
||||
.fetch_all(&self.pool)
|
||||
.await?;
|
||||
|
||||
let mut result = Vec::new();
|
||||
for row in albums {
|
||||
let id_str = row.get::<String, _>("id");
|
||||
let id = match Uuid::parse_str(&id_str) {
|
||||
Ok(uuid) => uuid,
|
||||
Err(e) => {
|
||||
eprintln!("无效的UUID格式: {} - 错误: {}", id_str, e);
|
||||
continue; // 跳过无效的记录
|
||||
}
|
||||
};
|
||||
|
||||
result.push(AlbumInfo {
|
||||
id,
|
||||
title: row.get("title"),
|
||||
document: row.get("document"),
|
||||
description: row.get("description"),
|
||||
cover_image_id: row.get("cover_image_id"),
|
||||
image_count: row.get("image_count"),
|
||||
created_at: row.get::<String, _>("created_at"),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
// pub async fn get_albums(&self, page: Option<usize>, limit: Option<usize>) -> Result<Vec<AlbumInfo>, sqlx::Error> {
|
||||
// let page = page.unwrap_or(1);
|
||||
// let limit = limit.unwrap_or(50); // 减少默认加载数量,提高响应速度
|
||||
// let offset = (page - 1) * limit;
|
||||
//
|
||||
// let albums = sqlx::query(
|
||||
// r#"
|
||||
// SELECT
|
||||
// id,
|
||||
// title,
|
||||
// document,
|
||||
// description,
|
||||
// cover_image_id,
|
||||
// image_count,
|
||||
// created_at
|
||||
// FROM albums
|
||||
// ORDER BY created_at DESC
|
||||
// LIMIT ? OFFSET ?
|
||||
// "#
|
||||
// )
|
||||
// .bind(limit as i64)
|
||||
// .bind(offset as i64)
|
||||
// .fetch_all(&self.pool)
|
||||
// .await?;
|
||||
//
|
||||
// let mut result = Vec::new();
|
||||
// for row in albums {
|
||||
// let id_str = row.get::<String, _>("id");
|
||||
// let id = match Uuid::parse_str(&id_str) {
|
||||
// Ok(uuid) => uuid,
|
||||
// Err(e) => {
|
||||
// eprintln!("无效的UUID格式: {} - 错误: {}", id_str, e);
|
||||
// continue; // 跳过无效的记录
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// result.push(AlbumInfo {
|
||||
// id,
|
||||
// title: row.get("title"),
|
||||
// document: row.get("document"),
|
||||
// description: row.get("description"),
|
||||
// cover_image_id: row.get("cover_image_id"),
|
||||
// image_count: row.get("image_count"),
|
||||
// created_at: row.get::<String, _>("created_at"),
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// Ok(result)
|
||||
// }
|
||||
// 相册操作
|
||||
pub async fn get_all_albums(&self) -> Result<Vec<AlbumInfo>, sqlx::Error> {
|
||||
|
||||
@@ -202,58 +153,58 @@ impl Database {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn search_albums(&self, query: &str, page: Option<usize>, limit: Option<usize>) -> Result<Vec<AlbumInfo>, sqlx::Error> {
|
||||
let search_pattern = format!("%{}%", query);
|
||||
let page = page.unwrap_or(1);
|
||||
let limit = limit.unwrap_or(50); // 减少默认加载数量,提高响应速度
|
||||
let offset = (page - 1) * limit;
|
||||
|
||||
let albums = sqlx::query(
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
title,
|
||||
document,
|
||||
description,
|
||||
cover_image_id,
|
||||
image_count,
|
||||
created_at
|
||||
FROM albums
|
||||
WHERE title LIKE ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
"#
|
||||
)
|
||||
.bind(&search_pattern)
|
||||
.bind(limit as i64)
|
||||
.bind(offset as i64)
|
||||
.fetch_all(&self.pool)
|
||||
.await?;
|
||||
|
||||
let mut result = Vec::new();
|
||||
for row in albums {
|
||||
let id_str = row.get::<String, _>("id");
|
||||
let id = match Uuid::parse_str(&id_str) {
|
||||
Ok(uuid) => uuid,
|
||||
Err(e) => {
|
||||
eprintln!("无效的UUID格式: {} - 错误: {}", id_str, e);
|
||||
continue; // 跳过无效的记录
|
||||
}
|
||||
};
|
||||
|
||||
result.push(AlbumInfo {
|
||||
id,
|
||||
title: row.get("title"),
|
||||
document: row.get("document"),
|
||||
description: row.get("description"),
|
||||
cover_image_id: row.get("cover_image_id"),
|
||||
image_count: row.get("image_count"),
|
||||
created_at: row.get::<String, _>("created_at"),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
// pub async fn search_albums(&self, query: &str, page: Option<usize>, limit: Option<usize>) -> Result<Vec<AlbumInfo>, sqlx::Error> {
|
||||
// let search_pattern = format!("%{}%", query);
|
||||
// let page = page.unwrap_or(1);
|
||||
// let limit = limit.unwrap_or(50); // 减少默认加载数量,提高响应速度
|
||||
// let offset = (page - 1) * limit;
|
||||
//
|
||||
// let albums = sqlx::query(
|
||||
// r#"
|
||||
// SELECT
|
||||
// id,
|
||||
// title,
|
||||
// document,
|
||||
// description,
|
||||
// cover_image_id,
|
||||
// image_count,
|
||||
// created_at
|
||||
// FROM albums
|
||||
// WHERE title LIKE ?
|
||||
// ORDER BY created_at DESC
|
||||
// LIMIT ? OFFSET ?
|
||||
// "#
|
||||
// )
|
||||
// .bind(&search_pattern)
|
||||
// .bind(limit as i64)
|
||||
// .bind(offset as i64)
|
||||
// .fetch_all(&self.pool)
|
||||
// .await?;
|
||||
//
|
||||
// let mut result = Vec::new();
|
||||
// for row in albums {
|
||||
// let id_str = row.get::<String, _>("id");
|
||||
// let id = match Uuid::parse_str(&id_str) {
|
||||
// Ok(uuid) => uuid,
|
||||
// Err(e) => {
|
||||
// eprintln!("无效的UUID格式: {} - 错误: {}", id_str, e);
|
||||
// continue; // 跳过无效的记录
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// result.push(AlbumInfo {
|
||||
// id,
|
||||
// title: row.get("title"),
|
||||
// document: row.get("document"),
|
||||
// description: row.get("description"),
|
||||
// cover_image_id: row.get("cover_image_id"),
|
||||
// image_count: row.get("image_count"),
|
||||
// created_at: row.get::<String, _>("created_at"),
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// Ok(result)
|
||||
// }
|
||||
|
||||
pub async fn get_album_with_images(&self, album_id: Uuid) -> Result<Option<AlbumWithImages>, sqlx::Error> {
|
||||
// 获取相册基本信息
|
||||
@@ -370,50 +321,50 @@ impl Database {
|
||||
}
|
||||
|
||||
// 图片操作
|
||||
pub async fn add_image(&self, image: CreateImage) -> Result<Uuid, sqlx::Error> {
|
||||
let id = Uuid::new_v4();
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
|
||||
sqlx::query(
|
||||
"INSERT INTO images (id, album_id, filename, original_url, title, description, file_size, width, height, mime_type, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
)
|
||||
.bind(id.to_string())
|
||||
.bind(image.album_id.to_string())
|
||||
.bind(image.filename)
|
||||
.bind(image.original_url)
|
||||
.bind(image.title)
|
||||
.bind(image.description)
|
||||
.bind(image.file_size)
|
||||
.bind(image.width)
|
||||
.bind(image.height)
|
||||
.bind(image.mime_type)
|
||||
.bind(&now)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
|
||||
// 更新相册的图片数量
|
||||
self.update_album_image_count(image.album_id).await?;
|
||||
|
||||
// 如果这是相册的第一张图片,设置为封面
|
||||
let image_count = sqlx::query_scalar::<_, i64>(
|
||||
"SELECT COUNT(*) FROM images WHERE album_id = ?"
|
||||
)
|
||||
.bind(image.album_id.to_string())
|
||||
.fetch_one(&self.pool)
|
||||
.await?;
|
||||
|
||||
if image_count == 1 {
|
||||
sqlx::query(
|
||||
"UPDATE albums SET cover_image_id = ? WHERE id = ?"
|
||||
)
|
||||
.bind(id.to_string())
|
||||
.bind(image.album_id.to_string())
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
// pub async fn add_image(&self, image: CreateImage) -> Result<Uuid, sqlx::Error> {
|
||||
// let id = Uuid::new_v4();
|
||||
// let now = chrono::Utc::now().to_rfc3339();
|
||||
//
|
||||
// sqlx::query(
|
||||
// "INSERT INTO images (id, album_id, filename, original_url, title, description, file_size, width, height, mime_type, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
// )
|
||||
// .bind(id.to_string())
|
||||
// .bind(image.album_id.to_string())
|
||||
// .bind(image.filename)
|
||||
// .bind(image.original_url)
|
||||
// .bind(image.title)
|
||||
// .bind(image.description)
|
||||
// .bind(image.file_size)
|
||||
// .bind(image.width)
|
||||
// .bind(image.height)
|
||||
// .bind(image.mime_type)
|
||||
// .bind(&now)
|
||||
// .execute(&self.pool)
|
||||
// .await?;
|
||||
//
|
||||
// // 更新相册的图片数量
|
||||
// self.update_album_image_count(image.album_id).await?;
|
||||
//
|
||||
// // 如果这是相册的第一张图片,设置为封面
|
||||
// let image_count = sqlx::query_scalar::<_, i64>(
|
||||
// "SELECT COUNT(*) FROM images WHERE album_id = ?"
|
||||
// )
|
||||
// .bind(image.album_id.to_string())
|
||||
// .fetch_one(&self.pool)
|
||||
// .await?;
|
||||
//
|
||||
// if image_count == 1 {
|
||||
// sqlx::query(
|
||||
// "UPDATE albums SET cover_image_id = ? WHERE id = ?"
|
||||
// )
|
||||
// .bind(id.to_string())
|
||||
// .bind(image.album_id.to_string())
|
||||
// .execute(&self.pool)
|
||||
// .await?;
|
||||
// }
|
||||
//
|
||||
// Ok(id)
|
||||
// }
|
||||
|
||||
/// 更新相册的图片数量
|
||||
pub async fn update_album_image_count(&self, album_id: Uuid) -> Result<(), sqlx::Error> {
|
||||
@@ -569,38 +520,38 @@ impl Database {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_image(&self, image_id: Uuid) -> Result<bool, sqlx::Error> {
|
||||
// 先获取图片所属的相册ID
|
||||
let album_id_result = sqlx::query_scalar::<_, String>(
|
||||
r#"
|
||||
SELECT album_id FROM images WHERE id = ?
|
||||
"#
|
||||
)
|
||||
.bind(image_id.to_string())
|
||||
.fetch_optional(&self.pool)
|
||||
.await?;
|
||||
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
DELETE FROM images
|
||||
WHERE id = ?
|
||||
"#
|
||||
)
|
||||
.bind(image_id.to_string())
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
|
||||
// 如果删除成功且找到了相册ID,更新相册图片数量
|
||||
if result.rows_affected() > 0 {
|
||||
if let Some(album_id_str) = album_id_result {
|
||||
if let Ok(album_id) = Uuid::parse_str(&album_id_str) {
|
||||
self.update_album_image_count(album_id).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result.rows_affected() > 0)
|
||||
}
|
||||
// pub async fn delete_image(&self, image_id: Uuid) -> Result<bool, sqlx::Error> {
|
||||
// // 先获取图片所属的相册ID
|
||||
// let album_id_result = sqlx::query_scalar::<_, String>(
|
||||
// r#"
|
||||
// SELECT album_id FROM images WHERE id = ?
|
||||
// "#
|
||||
// )
|
||||
// .bind(image_id.to_string())
|
||||
// .fetch_optional(&self.pool)
|
||||
// .await?;
|
||||
//
|
||||
// let result = sqlx::query(
|
||||
// r#"
|
||||
// DELETE FROM images
|
||||
// WHERE id = ?
|
||||
// "#
|
||||
// )
|
||||
// .bind(image_id.to_string())
|
||||
// .execute(&self.pool)
|
||||
// .await?;
|
||||
//
|
||||
// // 如果删除成功且找到了相册ID,更新相册图片数量
|
||||
// if result.rows_affected() > 0 {
|
||||
// if let Some(album_id_str) = album_id_result {
|
||||
// if let Ok(album_id) = Uuid::parse_str(&album_id_str) {
|
||||
// self.update_album_image_count(album_id).await?;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Ok(result.rows_affected() > 0)
|
||||
// }
|
||||
|
||||
pub async fn update_album_cover(&self, album_id: Uuid, cover_image_id: Option<Uuid>) -> Result<(), sqlx::Error> {
|
||||
let cover_id_str = cover_image_id.map(|id| id.to_string());
|
||||
|
||||
@@ -99,7 +99,7 @@ pub struct FavoriteStatus {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AlbumsCache {
|
||||
pub albums: Vec<AlbumInfo>,
|
||||
pub search_index: std::collections::HashMap<String, Vec<AlbumInfo>>,
|
||||
pub search_index: HashMap<String, Vec<AlbumInfo>>,
|
||||
pub last_updated: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ impl AlbumsCache {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
albums: Vec::new(),
|
||||
search_index: std::collections::HashMap::new(),
|
||||
search_index: HashMap::new(),
|
||||
last_updated: chrono::Utc::now(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user