update code level

This commit is contained in:
lychang
2026-03-23 03:26:06 +08:00
parent d69c6c6aa3
commit 8a7156128b
3 changed files with 127 additions and 85 deletions

View File

@@ -18,20 +18,20 @@ impl Database {
} else {
url
};
// 确保数据目录存在
if let Some(parent) = std::path::Path::new(file_path).parent() {
if !parent.exists() {
std::fs::create_dir_all(parent)?;
}
}
// 如果数据库文件不存在,创建一个空文件
if !std::path::Path::new(file_path).exists() {
std::fs::File::create(file_path)?;
println!("创建新的数据库文件: {}", file_path);
}
let pool = SqlitePoolOptions::new()
.max_connections(10) // 增加连接数
.min_connections(2) // 保持最小连接数
@@ -40,7 +40,7 @@ impl Database {
.max_lifetime(std::time::Duration::from_secs(1800)) // 增加最大生命周期
.connect(url)
.await?;
// 在迁移之前设置SQLite性能优化参数
// 这些PRAGMA语句必须在事务外执行
sqlx::query("PRAGMA journal_mode=WAL;").execute(&pool).await?;
@@ -50,14 +50,14 @@ impl Database {
sqlx::query("PRAGMA mmap_size=268435456;").execute(&pool).await?;
sqlx::query("PRAGMA temp_store=MEMORY;").execute(&pool).await?;
sqlx::query("PRAGMA foreign_keys=ON;").execute(&pool).await?;
// 创建表
sqlx::migrate!("./migrations").run(&pool).await?;
Ok(Database { pool })
}
/// 带重试机制的数据库操作执行器
async fn execute_with_retry<F, T>(&self, operation: F, operation_name: &str) -> Result<T, sqlx::Error>
where
@@ -65,7 +65,7 @@ impl Database {
{
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),
@@ -73,7 +73,7 @@ impl Database {
// 检查是否是死锁或超时错误
if self.is_retryable_error(&e) && attempt < MAX_RETRIES {
let delay_ms = BASE_DELAY_MS * 2u64.pow(attempt);
eprintln!("⚠️ {} 操作失败 (尝试 {}): {}, 等待 {}ms 后重试",
eprintln!("⚠️ {} 操作失败 (尝试 {}): {}, 等待 {}ms 后重试",
operation_name, attempt + 1, e, delay_ms);
sleep(Duration::from_millis(delay_ms)).await;
} else {
@@ -83,7 +83,7 @@ impl Database {
}
}
}
unreachable!()
}
@@ -94,7 +94,7 @@ impl Database {
// SQLite 死锁错误码
if let Some(code) = db_err.code() {
code == "5" || // SQLITE_BUSY
code == "6" || // SQLITE_LOCKED
code == "6" || // SQLITE_LOCKED
code == "261" // SQLITE_BUSY_SNAPSHOT
} else {
false
@@ -104,16 +104,14 @@ impl Database {
_ => false,
}
}
// 相册操作
pub async fn get_all_albums(&self, page: Option<usize>, limit: Option<usize>) -> Result<Vec<AlbumInfo>, sqlx::Error> {
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(200);
let offset = (page - 1) * limit;
let albums = sqlx::query(
r#"
SELECT
SELECT
a.id,
a.title,
a.document,
@@ -128,11 +126,11 @@ impl Database {
LIMIT ? OFFSET ?
"#
)
.bind(limit as i64)
.bind(offset as i64)
.fetch_all(&self.pool)
.await?;
.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");
@@ -143,7 +141,7 @@ impl Database {
continue; // 跳过无效的记录
}
};
result.push(AlbumInfo {
id,
title: row.get("title"),
@@ -154,16 +152,60 @@ impl Database {
created_at: row.get::<String, _>("created_at"),
});
}
Ok(result)
}
// 相册操作
pub async fn get_all_albums(&self) -> Result<Vec<AlbumInfo>, sqlx::Error> {
let albums = sqlx::query(
r#"
SELECT
id,
title,
document,
description,
cover_image_id,
0 as image_count,
created_at
FROM albums
ORDER BY document DESC
"#
)
.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(200);
let offset = (page - 1) * limit;
let albums = sqlx::query(
r#"
SELECT
@@ -188,7 +230,7 @@ impl Database {
.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");
@@ -199,7 +241,7 @@ impl Database {
continue; // 跳过无效的记录
}
};
result.push(AlbumInfo {
id,
title: row.get("title"),
@@ -210,10 +252,10 @@ impl Database {
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> {
// 获取相册基本信息
let album_row = sqlx::query(
@@ -222,7 +264,7 @@ impl Database {
.bind(album_id.to_string())
.fetch_optional(&self.pool)
.await?;
if let Some(row) = album_row {
let id_str = row.get::<String, _>("id");
let id = match Uuid::parse_str(&id_str) {
@@ -232,7 +274,7 @@ impl Database {
return Ok(None);
}
};
let cover_image_id = match row.get::<Option<String>, _>("cover_image_id") {
Some(s) => match Uuid::parse_str(&s) {
Ok(uuid) => Some(uuid),
@@ -243,7 +285,7 @@ impl Database {
},
None => None
};
let album = Album {
id,
title: row.get("title"),
@@ -252,7 +294,7 @@ impl Database {
created_at: row.get::<String, _>("created_at"),
updated_at: row.get::<String, _>("updated_at"),
};
// 获取相册中的图片
let image_rows = sqlx::query(
"SELECT * FROM images WHERE album_id = ?"
@@ -260,7 +302,7 @@ impl Database {
.bind(album_id.to_string())
.fetch_all(&self.pool)
.await?;
let mut images = Vec::new();
for row in image_rows {
let image_id_str = row.get::<String, _>("id");
@@ -271,7 +313,7 @@ impl Database {
continue;
}
};
let album_id_str = row.get::<String, _>("album_id");
let album_id = match Uuid::parse_str(&album_id_str) {
Ok(uuid) => uuid,
@@ -280,7 +322,7 @@ impl Database {
continue;
}
};
images.push(Image {
id: image_id,
album_id,
@@ -295,9 +337,9 @@ impl Database {
created_at: row.get::<String, _>("created_at"),
});
}
let image_count = images.len() as i64;
Ok(Some(AlbumWithImages {
album,
images,
@@ -307,11 +349,11 @@ impl Database {
Ok(None)
}
}
pub async fn create_album(&self, album: CreateAlbum) -> Result<Uuid, sqlx::Error> {
let id = Uuid::new_v4();
let now = chrono::Utc::now().to_rfc3339();
sqlx::query(
"INSERT INTO albums (id, title, document, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)"
)
@@ -323,15 +365,15 @@ impl Database {
.bind(&now)
.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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
)
@@ -348,7 +390,7 @@ impl Database {
.bind(&now)
.execute(&self.pool)
.await?;
// 如果这是相册的第一张图片,设置为封面
let image_count = sqlx::query_scalar::<_, i64>(
"SELECT COUNT(*) FROM images WHERE album_id = ?"
@@ -356,7 +398,7 @@ impl Database {
.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 = ?"
@@ -366,7 +408,7 @@ impl Database {
.execute(&self.pool)
.await?;
}
Ok(id)
}
@@ -378,17 +420,17 @@ impl Database {
// 由于生命周期问题,简化实现,不使用重试机制
let now = chrono::Utc::now().to_rfc3339();
let mut ids = Vec::new();
// 保存第一个相册ID用于封面设置
let first_album_id = images.first().map(|img| img.album_id);
// 使用事务确保批量插入的原子性
let mut tx = self.pool.begin().await?;
for image in &images {
let id = Uuid::new_v4();
ids.push(id);
sqlx::query(
"INSERT INTO images (id, album_id, filename, original_url, title, description, file_size, width, height, mime_type, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
)
@@ -406,10 +448,10 @@ impl Database {
.execute(&mut *tx)
.await?;
}
// 提交事务
tx.commit().await?;
// 批量设置封面图片(只对每个相册的第一批图片设置)
if let Some(album_id) = first_album_id {
// 检查相册是否已有图片
@@ -419,7 +461,7 @@ impl Database {
.bind(album_id.to_string())
.fetch_one(&self.pool)
.await?;
// 如果这是相册的第一批图片,设置第一张为封面
if existing_count == images.len() as i64 {
if let Some(first_id) = ids.first() {
@@ -433,7 +475,7 @@ impl Database {
}
}
}
Ok(ids)
}
@@ -445,33 +487,33 @@ impl Database {
.bind(album_id.to_string())
.fetch_one(&self.pool)
.await?;
Ok(count)
}
/// 删除相册及其关联的图片
pub async fn delete_album(&self, album_id: Uuid) -> Result<(), sqlx::Error> {
let mut tx = self.pool.begin().await?;
// 删除相册关联的图片
sqlx::query("DELETE FROM images WHERE album_id = ?")
.bind(album_id.to_string())
.execute(&mut *tx)
.await?;
// 删除相册
sqlx::query("DELETE FROM albums WHERE id = ?")
.bind(album_id.to_string())
.execute(&mut *tx)
.await?;
tx.commit().await?;
Ok(())
}
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());
sqlx::query(
"UPDATE albums SET cover_image_id = ?, updated_at = datetime('now') WHERE id = ?"
)
@@ -479,7 +521,7 @@ impl Database {
.bind(album_id.to_string())
.execute(&self.pool)
.await?;
Ok(())
}
@@ -490,7 +532,7 @@ impl Database {
.bind(image_id.to_string())
.fetch_optional(&self.pool)
.await?;
if let Some(row) = row {
let image_id_str = row.get::<String, _>("id");
let image_id = match Uuid::parse_str(&image_id_str) {
@@ -500,7 +542,7 @@ impl Database {
return Ok(None);
}
};
let album_id_str = row.get::<String, _>("album_id");
let album_id = match Uuid::parse_str(&album_id_str) {
Ok(uuid) => uuid,
@@ -509,7 +551,7 @@ impl Database {
return Ok(None);
}
};
Ok(Some(Image {
id: image_id,
album_id,
@@ -527,9 +569,9 @@ impl Database {
Ok(None)
}
}
// 收藏功能数据库操作
// 获取用户的收藏列表
pub async fn get_user_favorites(&self, user_id: &str) -> Result<Vec<FavoriteWithImage>, sqlx::Error> {
let rows = sqlx::query(
@@ -562,7 +604,7 @@ impl Database {
.bind(user_id)
.fetch_all(&self.pool)
.await?;
let mut favorites = Vec::new();
for row in rows {
let favorite_id_str = row.get::<String, _>("favorite_id");
@@ -573,7 +615,7 @@ impl Database {
continue;
}
};
let image_id_str = row.get::<String, _>("image_id");
let image_id = match Uuid::parse_str(&image_id_str) {
Ok(uuid) => uuid,
@@ -582,7 +624,7 @@ impl Database {
continue;
}
};
let album_id_str = row.get::<String, _>("album_id");
let album_id = match Uuid::parse_str(&album_id_str) {
Ok(uuid) => uuid,
@@ -591,7 +633,7 @@ impl Database {
continue;
}
};
let favorite = Favorite {
id: favorite_id,
user_id: row.get("user_id"),
@@ -599,7 +641,7 @@ impl Database {
album_id,
created_at: row.get::<String, _>("favorite_created_at"),
};
let image = Image {
id: image_id,
album_id,
@@ -613,17 +655,17 @@ impl Database {
mime_type: row.get("mime_type"),
created_at: row.get::<String, _>("image_created_at"),
};
favorites.push(FavoriteWithImage {
favorite,
image,
album_title: row.get("album_title"),
});
}
Ok(favorites)
}
// 检查图片是否被用户收藏
pub async fn get_favorite_status(&self, user_id: &str, image_id: Uuid) -> Result<FavoriteStatus, sqlx::Error> {
let row = sqlx::query(
@@ -633,7 +675,7 @@ impl Database {
.bind(image_id.to_string())
.fetch_optional(&self.pool)
.await?;
if let Some(row) = row {
let favorite_id_str = row.get::<String, _>("id");
let favorite_id = match Uuid::parse_str(&favorite_id_str) {
@@ -643,7 +685,7 @@ impl Database {
None
}
};
Ok(FavoriteStatus {
is_favorited: true,
favorite_id,
@@ -655,12 +697,12 @@ impl Database {
})
}
}
// 添加收藏
pub async fn add_favorite(&self, user_id: &str, favorite_data: CreateFavorite) -> Result<Uuid, sqlx::Error> {
let favorite_id = Uuid::new_v4();
let now = chrono::Utc::now().to_rfc3339();
sqlx::query(
"INSERT INTO favorites (id, user_id, image_id, album_id, created_at) VALUES (?, ?, ?, ?, ?)"
)
@@ -671,10 +713,10 @@ impl Database {
.bind(&now)
.execute(&self.pool)
.await?;
Ok(favorite_id)
}
// 移除收藏(通过收藏ID)
pub async fn remove_favorite(&self, favorite_id: Uuid) -> Result<(), sqlx::Error> {
sqlx::query(
@@ -683,10 +725,10 @@ impl Database {
.bind(favorite_id.to_string())
.execute(&self.pool)
.await?;
Ok(())
}
// 移除收藏(通过用户ID和图片ID)
pub async fn remove_favorite_by_image(&self, user_id: &str, image_id: Uuid) -> Result<(), sqlx::Error> {
sqlx::query(
@@ -696,7 +738,7 @@ impl Database {
.bind(image_id.to_string())
.execute(&self.pool)
.await?;
Ok(())
}
}

View File

@@ -26,7 +26,7 @@ pub async fn import_existing_data(config: &Config, db: Arc<Database>) -> Result<
}
// 获取数据库中现有的相册列表
let existing_albums = db.get_all_albums(None, None).await?;
let existing_albums = db.get_all_albums().await?;
let existing_titles: Vec<String> = existing_albums.iter()
.map(|album| album.document.to_lowercase())
.collect();

View File

@@ -13,7 +13,7 @@ pub async fn get_albums(
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_all_albums(page, limit).await {
match db.get_albums(page, limit).await {
Ok(albums) => HttpResponse::Ok().json(albums),
Err(e) => {
eprintln!("获取相册列表失败: {}", e);
@@ -206,7 +206,7 @@ pub async fn search_albums(
let limit = query.get("limit").and_then(|s| s.parse::<usize>().ok());
if q.is_empty() {
return match db.get_all_albums(page, limit).await {
return match db.get_albums(page, limit).await {
Ok(albums) => HttpResponse::Ok().json(albums),
Err(e) => {
eprintln!("获取相册列表失败: {}", e);