fix: 修复项目数据持久化问题
问题修复: - 修复软件重新打开后项目数据丢失的问题 - 修复 is_active 字段数据类型不一致导致的查询问题 - 修复数据库事务提交和数据持久化问题 技术修复: - 改进数据库连接配置,使用 DELETE 模式确保数据立即写入 - 修复 is_active 字段的布尔值存储和读取逻辑 - 添加数据库迁移机制,自动修复历史数据 - 增强数据库路径管理,确保数据存储在正确位置 数据库优化: - 使用事务确保数据一致性 - 添加数据验证和错误处理 - 优化数据库 PRAGMA 设置提高可靠性 - 支持多种数据类型的兼容性读取 测试验证: - 验证项目创建后数据正确保存 - 验证应用重启后数据正确加载 - 验证数据库迁移正确执行 - 确保所有现有项目数据完整性
This commit is contained in:
parent
42c5dcef8e
commit
7b11ed04bd
|
|
@ -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<Vec<Project>> {
|
||||
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<Project> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,21 +13,31 @@ impl Database {
|
|||
/// 遵循安全第一原则,确保数据库文件的安全存储
|
||||
pub fn new() -> Result<Self> {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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| {
|
||||
// 初始化应用状态
|
||||
|
|
|
|||
|
|
@ -59,10 +59,17 @@ pub async fn validate_directory(path: String) -> Result<bool, String> {
|
|||
#[command]
|
||||
pub async fn get_directory_name(path: String) -> Result<String, String> {
|
||||
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<String, String> {
|
||||
use crate::infrastructure::database::Database;
|
||||
Ok(Database::get_database_path_info())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue