From 7b11ed04bd5238dad49f688c491a208cbe1c6a51 Mon Sep 17 00:00:00 2001 From: imeepos Date: Sun, 13 Jul 2025 19:04:11 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=8C=81=E4=B9=85=E5=8C=96=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题修复: - 修复软件重新打开后项目数据丢失的问题 - 修复 is_active 字段数据类型不一致导致的查询问题 - 修复数据库事务提交和数据持久化问题 技术修复: - 改进数据库连接配置,使用 DELETE 模式确保数据立即写入 - 修复 is_active 字段的布尔值存储和读取逻辑 - 添加数据库迁移机制,自动修复历史数据 - 增强数据库路径管理,确保数据存储在正确位置 数据库优化: - 使用事务确保数据一致性 - 添加数据验证和错误处理 - 优化数据库 PRAGMA 设置提高可靠性 - 支持多种数据类型的兼容性读取 测试验证: - 验证项目创建后数据正确保存 - 验证应用重启后数据正确加载 - 验证数据库迁移正确执行 - 确保所有现有项目数据完整性 --- .../data/repositories/project_repository.rs | 42 ++++++- .../src-tauri/src/infrastructure/database.rs | 106 ++++++++++++++++-- apps/desktop/src-tauri/src/lib.rs | 3 +- .../presentation/commands/system_commands.rs | 9 +- 4 files changed, 145 insertions(+), 15 deletions(-) diff --git a/apps/desktop/src-tauri/src/data/repositories/project_repository.rs b/apps/desktop/src-tauri/src/data/repositories/project_repository.rs index a6a1c12..ff3e1e4 100644 --- a/apps/desktop/src-tauri/src/data/repositories/project_repository.rs +++ b/apps/desktop/src-tauri/src/data/repositories/project_repository.rs @@ -18,7 +18,13 @@ impl ProjectRepository { /// 创建项目 pub fn create(&self, project: &Project) -> Result<()> { let conn = self.connection.lock().unwrap(); - conn.execute( + + + + // 开始事务 + let tx = conn.unchecked_transaction()?; + + let result = tx.execute( "INSERT INTO projects (id, name, path, description, created_at, updated_at, is_active) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", [ @@ -28,9 +34,15 @@ impl ProjectRepository { project.description.as_deref().unwrap_or(""), project.created_at.to_rfc3339().as_str(), project.updated_at.to_rfc3339().as_str(), - project.is_active.to_string().as_str(), + if project.is_active { "1" } else { "0" }, ], )?; + + // 提交事务 + tx.commit()?; + + + Ok(()) } @@ -75,6 +87,16 @@ impl ProjectRepository { /// 获取所有活跃项目 pub fn find_all_active(&self) -> Result> { let conn = self.connection.lock().unwrap(); + + // 首先检查表是否存在 + let table_exists: i64 = conn.query_row( + "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='projects'", + [], + |row| row.get(0) + )?; + + + let mut stmt = conn.prepare( "SELECT id, name, path, description, created_at, updated_at, is_active FROM projects WHERE is_active = 1 ORDER BY updated_at DESC" @@ -89,6 +111,7 @@ impl ProjectRepository { projects.push(project?); } + Ok(projects) } @@ -137,12 +160,19 @@ impl ProjectRepository { fn row_to_project(&self, row: &Row) -> Result { let created_at_str: String = row.get(4)?; let updated_at_str: String = row.get(5)?; - let is_active_str: String = row.get(6)?; - + + // 尝试读取 is_active 字段,支持多种数据类型 + let is_active = match row.get::<_, rusqlite::types::Value>(6)? { + rusqlite::types::Value::Integer(i) => i != 0, + rusqlite::types::Value::Text(s) => s == "1" || s.to_lowercase() == "true", + rusqlite::types::Value::Real(f) => f != 0.0, + _ => true, // 默认为 true + }; + let created_at = DateTime::parse_from_rfc3339(&created_at_str) .map_err(|_| rusqlite::Error::InvalidColumnType(4, "created_at".to_string(), rusqlite::types::Type::Text))? .with_timezone(&Utc); - + let updated_at = DateTime::parse_from_rfc3339(&updated_at_str) .map_err(|_| rusqlite::Error::InvalidColumnType(5, "updated_at".to_string(), rusqlite::types::Type::Text))? .with_timezone(&Utc); @@ -157,7 +187,7 @@ impl ProjectRepository { description, created_at, updated_at, - is_active: is_active_str == "1" || is_active_str.to_lowercase() == "true", + is_active, }) } } diff --git a/apps/desktop/src-tauri/src/infrastructure/database.rs b/apps/desktop/src-tauri/src/infrastructure/database.rs index fdf59d0..fc27edb 100644 --- a/apps/desktop/src-tauri/src/infrastructure/database.rs +++ b/apps/desktop/src-tauri/src/infrastructure/database.rs @@ -13,21 +13,31 @@ impl Database { /// 遵循安全第一原则,确保数据库文件的安全存储 pub fn new() -> Result { let db_path = Self::get_database_path(); - + + // 打印数据库路径用于调试 + println!("Initializing database at: {}", db_path.display()); + // 确保数据库目录存在 if let Some(parent) = db_path.parent() { std::fs::create_dir_all(parent).map_err(|e| { + eprintln!("Failed to create database directory: {}", e); rusqlite::Error::SqliteFailure( rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_CANTOPEN), Some(format!("Failed to create database directory: {}", e)), ) })?; } - - let connection = Connection::open(db_path)?; - - // 启用外键约束 + + let connection = Connection::open(&db_path)?; + println!("Database connection established successfully"); + + // 配置数据库设置 connection.execute("PRAGMA foreign_keys = ON", [])?; + connection.pragma_update(None, "journal_mode", "DELETE")?; // 使用 DELETE 模式而不是 WAL + connection.pragma_update(None, "synchronous", "FULL")?; // 确保数据立即写入磁盘 + connection.pragma_update(None, "cache_size", "10000")?; // 增加缓存大小 + + println!("Database pragmas configured"); let database = Database { connection: Arc::new(Mutex::new(connection)), @@ -35,6 +45,9 @@ impl Database { // 初始化数据库表 database.initialize_tables()?; + + // 运行数据库迁移 + database.run_migrations()?; Ok(database) } @@ -92,13 +105,92 @@ impl Database { Ok(()) } + /// 运行数据库迁移 + fn run_migrations(&self) -> Result<()> { + let conn = self.connection.lock().unwrap(); + + // 修复 is_active 字段的数据类型问题 + println!("Running database migrations..."); + + // 检查是否需要迁移(如果有字符串类型的 is_active 值) + let needs_migration: i64 = conn.query_row( + "SELECT COUNT(*) FROM projects WHERE is_active = 'true' OR is_active = 'false'", + [], + |row| row.get(0) + ).unwrap_or(0); + + if needs_migration > 0 { + println!("Found {} rows that need migration", needs_migration); + + // 将 "true" 字符串转换为 1 + let updated_rows = conn.execute( + "UPDATE projects SET is_active = 1 WHERE is_active = 'true'", + [], + )?; + + if updated_rows > 0 { + println!("Updated {} rows: converted 'true' to 1", updated_rows); + } + + // 将 "false" 字符串转换为 0 + let updated_rows = conn.execute( + "UPDATE projects SET is_active = 0 WHERE is_active = 'false'", + [], + )?; + + if updated_rows > 0 { + println!("Updated {} rows: converted 'false' to 0", updated_rows); + } + } else { + // 如果所有项目的 is_active 都是 0,可能是之前的迁移错误,恢复为 1 + let all_inactive: i64 = conn.query_row( + "SELECT COUNT(*) FROM projects WHERE is_active = 0", + [], + |row| row.get(0) + ).unwrap_or(0); + + let total_projects: i64 = conn.query_row( + "SELECT COUNT(*) FROM projects", + [], + |row| row.get(0) + ).unwrap_or(0); + + if all_inactive == total_projects && total_projects > 0 { + println!("All {} projects are inactive, restoring to active state", total_projects); + let updated_rows = conn.execute( + "UPDATE projects SET is_active = 1", + [], + )?; + println!("Restored {} projects to active state", updated_rows); + } + } + + println!("Database migrations completed"); + Ok(()) + } + /// 获取数据库文件路径 /// 遵循安全存储原则,将数据库存储在应用数据目录 fn get_database_path() -> PathBuf { + // 优先使用应用数据目录 if let Some(data_dir) = dirs::data_dir() { - data_dir.join("mixvideo").join("mixvideo.db") + let app_dir = data_dir.join("mixvideo"); + // 确保目录存在 + if let Err(e) = std::fs::create_dir_all(&app_dir) { + eprintln!("Failed to create app data directory: {}", e); + // 如果创建失败,使用当前目录 + return PathBuf::from("mixvideo.db"); + } + app_dir.join("mixvideo.db") } else { - PathBuf::from(".").join("mixvideo.db") + // 备用方案:使用当前目录 + PathBuf::from("mixvideo.db") } } + + /// 获取数据库路径的调试信息 + pub fn get_database_path_info() -> String { + let path = Self::get_database_path(); + format!("Database path: {}", path.display()) + } } diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index e2597d7..6534ce9 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -30,7 +30,8 @@ pub fn run() { commands::system_commands::select_directory, commands::system_commands::get_app_info, commands::system_commands::validate_directory, - commands::system_commands::get_directory_name + commands::system_commands::get_directory_name, + commands::system_commands::get_database_info ]) .setup(|app| { // 初始化应用状态 diff --git a/apps/desktop/src-tauri/src/presentation/commands/system_commands.rs b/apps/desktop/src-tauri/src/presentation/commands/system_commands.rs index d7b2c2f..e805f74 100644 --- a/apps/desktop/src-tauri/src/presentation/commands/system_commands.rs +++ b/apps/desktop/src-tauri/src/presentation/commands/system_commands.rs @@ -59,10 +59,17 @@ pub async fn validate_directory(path: String) -> Result { #[command] pub async fn get_directory_name(path: String) -> Result { use std::path::Path; - + let path = Path::new(&path); match path.file_name() { Some(name) => Ok(name.to_string_lossy().to_string()), None => Err("无效的目录路径".to_string()), } } + +/// 获取数据库信息命令(调试用) +#[command] +pub async fn get_database_info() -> Result { + use crate::infrastructure::database::Database; + Ok(Database::get_database_path_info()) +}