use rusqlite::Connection; use std::path::PathBuf; use std::sync::{Arc, Mutex}; use anyhow::{Result, anyhow}; use std::ops::{Deref, DerefMut}; use crate::infrastructure::connection_pool::{ConnectionPool, ConnectionPoolConfig, PooledConnectionHandle}; /// 统一的数据库连接句柄 /// 可以是单连接模式的 MutexGuard 或连接池模式的 PooledConnectionHandle pub enum ConnectionHandle<'a> { /// 单连接模式(兼容模式) Single(std::sync::MutexGuard<'a, Connection>), /// 连接池模式(推荐) Pooled(PooledConnectionHandle), } impl<'a> Deref for ConnectionHandle<'a> { type Target = Connection; fn deref(&self) -> &Self::Target { match self { ConnectionHandle::Single(conn) => conn, ConnectionHandle::Pooled(conn) => conn.as_ref(), } } } impl<'a> DerefMut for ConnectionHandle<'a> { fn deref_mut(&mut self) -> &mut Self::Target { match self { ConnectionHandle::Single(conn) => conn, ConnectionHandle::Pooled(conn) => conn.as_mut(), } } } /// 数据库管理器 /// 遵循 Tauri 开发规范的数据库设计模式 /// 支持连接池以提高并发性能,实现读写分离 pub struct Database { // 主连接(用于写操作和复杂事务) connection: Arc>, // 只读连接(专门用于读操作,避免锁竞争) read_connection: Arc>, // 连接池支持 pool: Option>, } /// 数据库连接模式 #[derive(Debug, Clone)] pub enum ConnectionMode { /// 单连接模式(兼容模式) Single, /// 连接池模式(推荐) Pool(ConnectionPoolConfig), } impl Database { /// 创建新的数据库实例 /// 遵循安全第一原则,确保数据库文件的安全存储 pub fn new() -> Result { let app_data_dir = dirs::data_dir() .ok_or_else(|| anyhow!("无法获取应用数据目录"))? .join("mixvideo"); std::fs::create_dir_all(&app_data_dir)?; let db_path = app_data_dir.join("mixvideo.db"); Self::new_with_path(db_path.to_str().unwrap()) } /// 创建带连接池的数据库实例(推荐用于生产环境) pub fn new_with_pool() -> Result { let app_data_dir = dirs::data_dir() .ok_or_else(|| anyhow!("无法获取应用数据目录"))? .join("mixvideo"); std::fs::create_dir_all(&app_data_dir)?; let db_path = app_data_dir.join("mixvideo.db"); // 检查数据库是否被锁定 if let Err(e) = Self::check_database_lock(&db_path) { eprintln!("数据库锁检查失败: {}", e); // 尝试清理锁文件 Self::cleanup_database_locks(&db_path)?; } Self::new_with_path_and_pool(db_path.to_str().unwrap(), Some(ConnectionPoolConfig::default())) } /// 检查数据库锁状态 fn check_database_lock(db_path: &PathBuf) -> Result<()> { // 检查 WAL 和 SHM 文件 let wal_path = db_path.with_extension("db-wal"); let shm_path = db_path.with_extension("db-shm"); if wal_path.exists() || shm_path.exists() { println!("检测到 WAL/SHM 文件,数据库可能正在被其他进程使用"); } // 尝试打开数据库进行快速检查 match Connection::open(db_path) { Ok(conn) => { // 尝试执行一个简单的查询 match conn.execute("SELECT 1", []) { Ok(_) => { println!("数据库连接检查通过"); Ok(()) }, Err(e) => Err(anyhow!("数据库查询失败: {}", e)) } }, Err(e) => Err(anyhow!("无法打开数据库: {}", e)) } } /// 清理数据库锁文件 fn cleanup_database_locks(db_path: &PathBuf) -> Result<()> { let wal_path = db_path.with_extension("db-wal"); let shm_path = db_path.with_extension("db-shm"); if wal_path.exists() { println!("清理 WAL 文件: {:?}", wal_path); std::fs::remove_file(&wal_path).ok(); // 忽略错误 } if shm_path.exists() { println!("清理 SHM 文件: {:?}", shm_path); std::fs::remove_file(&shm_path).ok(); // 忽略错误 } Ok(()) } /// 使用指定路径创建数据库实例(主要用于测试) pub fn new_with_path(db_path: &str) -> Result { Self::new_with_path_and_pool(db_path, None) } /// 使用指定路径和连接池配置创建数据库实例 pub fn new_with_path_and_pool(db_path: &str, pool_config: Option) -> Result { let db_path = std::path::PathBuf::from(db_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)?; println!("Database connection established successfully"); // 遵循 Tauri 开发规范的安全第一原则 // 设置数据库安全配置 connection.pragma_update(None, "secure_delete", "ON")?; connection.pragma_update(None, "temp_store", "MEMORY")?; // 配置数据库设置 connection.pragma_update(None, "foreign_keys", "ON")?; connection.pragma_update(None, "journal_mode", "WAL")?; // 使用 WAL 模式以支持并发访问 connection.pragma_update(None, "synchronous", "NORMAL")?; // 使用 NORMAL 以提高性能 connection.pragma_update(None, "cache_size", "10000")?; // 增加缓存大小 connection.pragma_update(None, "busy_timeout", "5000")?; // 设置忙等待超时为5秒 println!("Database pragmas configured"); // 创建连接池(如果配置了) let pool = if let Some(config) = pool_config { println!("Initializing connection pool with min={}, max={} connections", config.min_connections, config.max_connections); let pool = ConnectionPool::new(db_path.to_string_lossy().to_string(), config)?; println!("Connection pool initialized successfully"); Some(pool) } else { println!("Using single connection mode (no pool)"); None }; // 创建专用的只读连接 let read_connection = Connection::open(&db_path)?; read_connection.pragma_update(None, "secure_delete", "ON")?; read_connection.pragma_update(None, "temp_store", "MEMORY")?; read_connection.pragma_update(None, "foreign_keys", "ON")?; read_connection.pragma_update(None, "journal_mode", "WAL")?; read_connection.pragma_update(None, "synchronous", "NORMAL")?; read_connection.pragma_update(None, "cache_size", "10000")?; read_connection.pragma_update(None, "busy_timeout", "5000")?; // 只读连接设置为只读模式 read_connection.pragma_update(None, "query_only", "ON")?; println!("只读数据库连接创建成功"); let database = Database { connection: Arc::new(Mutex::new(connection)), read_connection: Arc::new(Mutex::new(read_connection)), pool, }; // 初始化数据库表 database.initialize_tables()?; // 运行数据库迁移 database.run_migrations()?; Ok(database) } /// 获取数据库连接(用于写操作) pub fn get_connection(&self) -> Arc> { Arc::clone(&self.connection) } /// 获取只读数据库连接(用于读操作) /// 这个连接专门用于查询操作,避免与写操作竞争锁 pub fn get_read_connection(&self) -> Arc> { Arc::clone(&self.read_connection) } /// 检查数据库连接状态 pub fn check_connection_status(&self) -> String { match self.connection.try_lock() { Ok(_) => "连接可用".to_string(), Err(std::sync::TryLockError::Poisoned(_)) => "连接已损坏".to_string(), Err(std::sync::TryLockError::WouldBlock) => "连接被占用".to_string(), } } /// 尝试非阻塞获取数据库连接(写连接) /// 如果连接被占用,立即返回 None pub fn try_get_connection(&self) -> Option> { match self.connection.try_lock() { Ok(conn) => Some(conn), Err(_) => None, } } /// 尝试非阻塞获取只读数据库连接 /// 如果连接被占用,立即返回 None pub fn try_get_read_connection(&self) -> Option> { match self.read_connection.try_lock() { Ok(conn) => Some(conn), Err(_) => None, } } /// 获取最佳的只读连接 /// 优先使用专用只读连接,如果不可用则尝试主连接 pub fn get_best_read_connection(&self) -> Result { // 首先尝试专用只读连接 match self.read_connection.try_lock() { Ok(conn) => { println!("使用专用只读连接"); return Ok(ConnectionHandle::Single(conn)); }, Err(_) => { println!("只读连接被占用,尝试连接池"); } } // 如果只读连接被占用,尝试连接池 if let Some(pool) = &self.pool { match pool.try_acquire()? { Some(conn) => { println!("使用连接池连接进行读操作"); return Ok(ConnectionHandle::Pooled(conn)); }, None => { println!("连接池无可用连接,尝试主连接"); } } } // 最后尝试主连接(非阻塞) match self.connection.try_lock() { Ok(conn) => { println!("使用主连接进行读操作"); Ok(ConnectionHandle::Single(conn)) }, Err(_) => { println!("所有连接都被占用,读操作失败"); Err(anyhow!("所有数据库连接都被占用")) } } } /// 检查是否启用了连接池 pub fn has_pool(&self) -> bool { self.pool.is_some() } /// 从连接池获取连接(推荐) /// 如果连接池未启用,返回错误 pub fn acquire_from_pool(&self) -> Result { match &self.pool { Some(pool) => pool.acquire().map_err(|e| anyhow!("获取连接池连接失败: {}", e)), None => Err(anyhow!("连接池未启用")), } } /// 尝试从连接池获取连接(非阻塞) /// 如果连接池未启用或无可用连接,返回 None pub fn try_acquire_from_pool(&self) -> Result> { match &self.pool { Some(pool) => pool.try_acquire().map_err(|e| anyhow!("尝试获取连接池连接失败: {}", e)), None => Ok(None), } } /// 获取连接(自动选择最佳方式) /// 如果连接池可用,优先使用连接池 /// 否则使用单连接模式 pub fn get_best_connection(&self) -> Result { if let Some(pool) = &self.pool { // 优先使用连接池 match pool.try_acquire()? { Some(conn) => return Ok(ConnectionHandle::Pooled(conn)), None => { // 连接池中没有可用连接,尝试获取主连接 println!("连接池中没有可用连接,尝试获取主连接"); } } } // 回退到单连接模式 match self.connection.try_lock() { Ok(conn) => Ok(ConnectionHandle::Single(conn)), Err(_) => Err(anyhow!("所有数据库连接都被占用")), } } /// 执行数据库操作的辅助方法,自动处理锁的获取和释放 /// 这是推荐的数据库访问方式,可以避免锁竞争问题 pub fn with_connection(&self, operation: F) -> Result where F: FnOnce(&Connection) -> Result, { // 直接使用 connection 字段,避免额外的 Arc::clone match self.connection.try_lock() { Ok(conn) => { // 成功获取锁,执行操作 operation(&*conn) }, Err(_) => { // 锁被占用,等待一小段时间后重试 std::thread::sleep(std::time::Duration::from_millis(10)); // 使用阻塞方式获取锁,但有错误处理 let conn = self.connection.lock().map_err(|e| { eprintln!("数据库连接锁获取失败: {}", e); rusqlite::Error::SqliteFailure( rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_BUSY), Some("数据库连接被锁定".to_string()), ) })?; operation(&*conn) } } } /// 执行只读查询的辅助方法 pub fn query(&self, operation: F) -> Result where F: FnOnce(&Connection) -> Result, { self.with_connection(operation) } /// 执行写入操作的辅助方法 pub fn execute(&self, operation: F) -> Result where F: FnOnce(&Connection) -> Result, { self.with_connection(operation) } /// 初始化数据库表结构 /// 遵循模块化设计原则,清晰的表结构定义 fn initialize_tables(&self) -> Result<()> { let conn = self.connection.lock().unwrap(); // 创建项目表 conn.execute( "CREATE TABLE IF NOT EXISTS projects ( id TEXT PRIMARY KEY, name TEXT NOT NULL, path TEXT NOT NULL UNIQUE, description TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, is_active BOOLEAN DEFAULT 1 )", [], )?; // 创建项目配置表 conn.execute( "CREATE TABLE IF NOT EXISTS project_configs ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL, config_key TEXT NOT NULL, config_value TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE, UNIQUE(project_id, config_key) )", [], )?; // 创建素材表 conn.execute( "CREATE TABLE IF NOT EXISTS materials ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL, model_id TEXT, name TEXT NOT NULL, original_path TEXT NOT NULL, file_size INTEGER NOT NULL, md5_hash TEXT NOT NULL, material_type TEXT NOT NULL, processing_status TEXT NOT NULL DEFAULT 'Pending', metadata TEXT, scene_detection TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, processed_at DATETIME, error_message TEXT, FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE, UNIQUE(project_id, md5_hash) )", [], )?; // 创建素材片段表 conn.execute( "CREATE TABLE IF NOT EXISTS material_segments ( id TEXT PRIMARY KEY, material_id TEXT NOT NULL, segment_index INTEGER NOT NULL, start_time REAL NOT NULL, end_time REAL NOT NULL, duration REAL NOT NULL, file_path TEXT NOT NULL, file_size INTEGER NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (material_id) REFERENCES materials (id) ON DELETE CASCADE )", [], )?; // 创建模特表 conn.execute( "CREATE TABLE IF NOT EXISTS models ( id TEXT PRIMARY KEY, name TEXT NOT NULL, stage_name TEXT, gender TEXT NOT NULL, age INTEGER, height INTEGER, weight INTEGER, measurements TEXT, description TEXT, tags TEXT, avatar_path TEXT, contact_info TEXT, social_media TEXT, status TEXT NOT NULL DEFAULT 'Active', rating REAL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, is_active BOOLEAN DEFAULT 1 )", [], )?; // 创建模特照片表 conn.execute( "CREATE TABLE IF NOT EXISTS model_photos ( id TEXT PRIMARY KEY, model_id TEXT NOT NULL, file_path TEXT NOT NULL, file_name TEXT NOT NULL, file_size INTEGER NOT NULL, photo_type TEXT NOT NULL, description TEXT, tags TEXT, is_cover BOOLEAN DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (model_id) REFERENCES models (id) ON DELETE CASCADE )", [], )?; // 创建AI分类表 conn.execute( "CREATE TABLE IF NOT EXISTS ai_classifications ( id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, prompt_text TEXT NOT NULL, description TEXT, is_active INTEGER DEFAULT 1, sort_order INTEGER DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP )", [], )?; // 创建视频分类记录表 conn.execute( "CREATE TABLE IF NOT EXISTS video_classification_records ( id TEXT PRIMARY KEY, segment_id TEXT NOT NULL, material_id TEXT NOT NULL, project_id TEXT NOT NULL, category TEXT NOT NULL, confidence REAL NOT NULL, reasoning TEXT NOT NULL, features TEXT NOT NULL, product_match INTEGER NOT NULL, quality_score REAL NOT NULL, gemini_file_uri TEXT, raw_response TEXT, status TEXT NOT NULL, error_message TEXT, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, FOREIGN KEY (segment_id) REFERENCES material_segments (id) ON DELETE CASCADE, FOREIGN KEY (material_id) REFERENCES materials (id) ON DELETE CASCADE, FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE )", [], )?; // 创建视频分类任务表 conn.execute( "CREATE TABLE IF NOT EXISTS video_classification_tasks ( id TEXT PRIMARY KEY, segment_id TEXT NOT NULL, material_id TEXT NOT NULL, project_id TEXT NOT NULL, video_file_path TEXT NOT NULL, status TEXT NOT NULL, priority INTEGER DEFAULT 0, retry_count INTEGER DEFAULT 0, max_retries INTEGER DEFAULT 3, gemini_file_uri TEXT, prompt_text TEXT, error_message TEXT, started_at DATETIME, completed_at DATETIME, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, FOREIGN KEY (segment_id) REFERENCES material_segments (id) ON DELETE CASCADE, FOREIGN KEY (material_id) REFERENCES materials (id) ON DELETE CASCADE, FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE )", [], )?; // 创建模板表 conn.execute( "CREATE TABLE IF NOT EXISTS templates ( id TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT, canvas_width INTEGER NOT NULL, canvas_height INTEGER NOT NULL, canvas_ratio TEXT NOT NULL, duration INTEGER NOT NULL, fps REAL NOT NULL, import_status TEXT NOT NULL DEFAULT 'Pending', source_file_path TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, is_active BOOLEAN DEFAULT 1 )", [], )?; // 创建模板素材表 conn.execute( "CREATE TABLE IF NOT EXISTS template_materials ( id TEXT PRIMARY KEY, template_id TEXT NOT NULL, original_id TEXT NOT NULL, name TEXT NOT NULL, material_type TEXT NOT NULL, original_path TEXT NOT NULL, remote_url TEXT, file_size INTEGER, duration INTEGER, width INTEGER, height INTEGER, upload_status TEXT NOT NULL DEFAULT 'Pending', file_exists BOOLEAN DEFAULT FALSE, upload_success BOOLEAN DEFAULT FALSE, metadata TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (template_id) REFERENCES templates (id) ON DELETE CASCADE )", [], )?; // 创建轨道表 conn.execute( "CREATE TABLE IF NOT EXISTS tracks ( id TEXT PRIMARY KEY, template_id TEXT NOT NULL, name TEXT NOT NULL, track_type TEXT NOT NULL, track_index INTEGER NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (template_id) REFERENCES templates (id) ON DELETE CASCADE )", [], )?; // 创建轨道片段表 conn.execute( "CREATE TABLE IF NOT EXISTS track_segments ( id TEXT PRIMARY KEY, track_id TEXT NOT NULL, template_material_id TEXT, name TEXT NOT NULL, start_time INTEGER NOT NULL, end_time INTEGER NOT NULL, duration INTEGER NOT NULL, segment_index INTEGER NOT NULL, properties TEXT, matching_rule TEXT DEFAULT '\"FixedMaterial\"', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (track_id) REFERENCES tracks (id) ON DELETE CASCADE, FOREIGN KEY (template_material_id) REFERENCES template_materials (id) ON DELETE SET NULL )", [], )?; // 创建项目-模板绑定表 conn.execute( "CREATE TABLE IF NOT EXISTS project_template_bindings ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL, template_id TEXT NOT NULL, binding_name TEXT, description TEXT, priority INTEGER DEFAULT 0, is_active INTEGER DEFAULT 1, binding_type TEXT NOT NULL, binding_status TEXT NOT NULL, metadata TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE, FOREIGN KEY (template_id) REFERENCES templates (id) ON DELETE CASCADE, UNIQUE (project_id, template_id) )", [], )?; // 创建模板匹配结果表 conn.execute( "CREATE TABLE IF NOT EXISTS template_matching_results ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL, template_id TEXT NOT NULL, binding_id TEXT NOT NULL, result_name TEXT NOT NULL, description TEXT, total_segments INTEGER NOT NULL DEFAULT 0, matched_segments INTEGER NOT NULL DEFAULT 0, failed_segments INTEGER NOT NULL DEFAULT 0, success_rate REAL NOT NULL DEFAULT 0.0, used_materials INTEGER NOT NULL DEFAULT 0, used_models INTEGER NOT NULL DEFAULT 0, matching_duration_ms INTEGER NOT NULL DEFAULT 0, quality_score REAL, status TEXT NOT NULL DEFAULT 'Success', metadata TEXT, export_count INTEGER NOT NULL DEFAULT 0, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, is_active INTEGER DEFAULT 1, FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE, FOREIGN KEY (template_id) REFERENCES templates (id) ON DELETE CASCADE, FOREIGN KEY (binding_id) REFERENCES project_template_bindings (id) ON DELETE CASCADE )", [], )?; // 创建匹配片段结果表 conn.execute( "CREATE TABLE IF NOT EXISTS matching_segment_results ( id TEXT PRIMARY KEY, matching_result_id TEXT NOT NULL, track_segment_id TEXT NOT NULL, track_segment_name TEXT NOT NULL, material_segment_id TEXT NOT NULL, material_id TEXT NOT NULL, material_name TEXT NOT NULL, model_id TEXT, model_name TEXT, match_score REAL NOT NULL, match_reason TEXT NOT NULL, segment_duration INTEGER NOT NULL, start_time INTEGER NOT NULL, end_time INTEGER NOT NULL, properties TEXT, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, FOREIGN KEY (matching_result_id) REFERENCES template_matching_results (id) ON DELETE CASCADE, FOREIGN KEY (material_segment_id) REFERENCES material_segments (id) ON DELETE CASCADE, FOREIGN KEY (material_id) REFERENCES materials (id) ON DELETE CASCADE, FOREIGN KEY (model_id) REFERENCES models (id) ON DELETE SET NULL )", [], )?; // 创建匹配失败片段结果表 conn.execute( "CREATE TABLE IF NOT EXISTS matching_failed_segment_results ( id TEXT PRIMARY KEY, matching_result_id TEXT NOT NULL, track_segment_id TEXT NOT NULL, track_segment_name TEXT NOT NULL, matching_rule_type TEXT NOT NULL, matching_rule_data TEXT, failure_reason TEXT NOT NULL, failure_details TEXT, segment_duration INTEGER NOT NULL, start_time INTEGER NOT NULL, end_time INTEGER NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, FOREIGN KEY (matching_result_id) REFERENCES template_matching_results (id) ON DELETE CASCADE )", [], )?; // 创建导出记录表 conn.execute( "CREATE TABLE IF NOT EXISTS export_records ( id TEXT PRIMARY KEY, matching_result_id TEXT NOT NULL, project_id TEXT NOT NULL, template_id TEXT NOT NULL, export_type TEXT NOT NULL, export_format TEXT NOT NULL, file_path TEXT NOT NULL, file_size INTEGER, export_status TEXT NOT NULL DEFAULT 'Success', export_duration_ms INTEGER NOT NULL DEFAULT 0, error_message TEXT, metadata TEXT, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, is_active INTEGER DEFAULT 1, FOREIGN KEY (matching_result_id) REFERENCES template_matching_results (id) ON DELETE CASCADE, FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE, FOREIGN KEY (template_id) REFERENCES templates (id) ON DELETE CASCADE )", [], )?; // 创建素材使用记录表 conn.execute( "CREATE TABLE IF NOT EXISTS material_usage_records ( id TEXT PRIMARY KEY, material_segment_id TEXT NOT NULL, material_id TEXT NOT NULL, project_id TEXT NOT NULL, template_matching_result_id TEXT NOT NULL, template_id TEXT NOT NULL, binding_id TEXT NOT NULL, track_segment_id TEXT NOT NULL, usage_type TEXT NOT NULL DEFAULT 'TemplateMatching', usage_context TEXT, created_at DATETIME NOT NULL, FOREIGN KEY (material_segment_id) REFERENCES material_segments (id) ON DELETE CASCADE, FOREIGN KEY (material_id) REFERENCES materials (id) ON DELETE CASCADE, FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE, FOREIGN KEY (template_matching_result_id) REFERENCES template_matching_results (id) ON DELETE CASCADE )", [], )?; // 创建性能监控表 conn.execute( "CREATE TABLE IF NOT EXISTS performance_metrics ( id INTEGER PRIMARY KEY AUTOINCREMENT, metric_name TEXT NOT NULL, metric_value REAL NOT NULL, recorded_at DATETIME DEFAULT CURRENT_TIMESTAMP )", [], )?; // 创建服装分析表 conn.execute( "CREATE TABLE IF NOT EXISTS outfit_analyses ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL, image_path TEXT NOT NULL, image_name TEXT NOT NULL, analysis_status TEXT NOT NULL DEFAULT 'Pending', analysis_result TEXT, error_message TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, analyzed_at DATETIME, FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE )", [], )?; // 创建服装单品表 conn.execute( "CREATE TABLE IF NOT EXISTS outfit_items ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL, analysis_id TEXT, name TEXT NOT NULL, category TEXT NOT NULL, brand TEXT, model TEXT, color_primary TEXT NOT NULL, color_secondary TEXT, styles TEXT NOT NULL, design_elements TEXT, size_info TEXT, material_info TEXT, price REAL, purchase_date DATETIME, image_urls TEXT, tags TEXT, notes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE, FOREIGN KEY (analysis_id) REFERENCES outfit_analyses (id) ON DELETE SET NULL )", [], )?; // 创建服装搭配表 conn.execute( "CREATE TABLE IF NOT EXISTS outfit_matchings ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL, matching_name TEXT NOT NULL, matching_type TEXT NOT NULL, items TEXT NOT NULL, score_details TEXT NOT NULL, suggestions TEXT, occasion_tags TEXT, season_tags TEXT, style_description TEXT NOT NULL, color_palette TEXT, is_favorite BOOLEAN DEFAULT 0, wear_count INTEGER DEFAULT 0, last_worn_date DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE )", [], )?; // 创建索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_projects_name ON projects (name)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_projects_created_at ON projects (created_at)", [], )?; // 创建素材表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_materials_project_id ON materials (project_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_materials_md5_hash ON materials (md5_hash)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_materials_type ON materials (material_type)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_materials_model_id ON materials (model_id)", [], )?; // 创建模板表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_templates_import_status ON templates (import_status)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_templates_created_at ON templates (created_at)", [], )?; // 创建项目-模板绑定表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_project_template_bindings_project_id ON project_template_bindings (project_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_project_template_bindings_template_id ON project_template_bindings (template_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_project_template_bindings_binding_type ON project_template_bindings (binding_type)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_project_template_bindings_priority ON project_template_bindings (priority)", [], )?; // 创建模板匹配结果表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_template_matching_results_project_id ON template_matching_results (project_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_template_matching_results_template_id ON template_matching_results (template_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_template_matching_results_binding_id ON template_matching_results (binding_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_template_matching_results_status ON template_matching_results (status)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_template_matching_results_created_at ON template_matching_results (created_at)", [], )?; // 创建匹配片段结果表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_matching_segment_results_matching_result_id ON matching_segment_results (matching_result_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_matching_segment_results_material_id ON matching_segment_results (material_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_matching_segment_results_model_id ON matching_segment_results (model_id)", [], )?; // 创建匹配失败片段结果表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_matching_failed_segment_results_matching_result_id ON matching_failed_segment_results (matching_result_id)", [], )?; // 创建导出记录表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_export_records_matching_result_id ON export_records (matching_result_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_export_records_project_id ON export_records (project_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_export_records_template_id ON export_records (template_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_export_records_export_type ON export_records (export_type)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_export_records_export_status ON export_records (export_status)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_export_records_created_at ON export_records (created_at)", [], )?; // 创建服装分析表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_outfit_analyses_project_id ON outfit_analyses (project_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_outfit_analyses_status ON outfit_analyses (analysis_status)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_outfit_analyses_created_at ON outfit_analyses (created_at)", [], )?; // 创建服装单品表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_outfit_items_project_id ON outfit_items (project_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_outfit_items_analysis_id ON outfit_items (analysis_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_outfit_items_category ON outfit_items (category)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_outfit_items_brand ON outfit_items (brand)", [], )?; // 创建服装搭配表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_outfit_matchings_project_id ON outfit_matchings (project_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_outfit_matchings_type ON outfit_matchings (matching_type)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_outfit_matchings_favorite ON outfit_matchings (is_favorite)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_outfit_matchings_created_at ON outfit_matchings (created_at)", [], )?; // 添加新字段(如果不存在)- 数据库迁移 let _ = conn.execute( "ALTER TABLE template_materials ADD COLUMN file_exists BOOLEAN DEFAULT FALSE", [], ); let _ = conn.execute( "ALTER TABLE template_materials ADD COLUMN upload_success BOOLEAN DEFAULT FALSE", [], ); // 为素材表添加模特ID字段 let _ = conn.execute( "ALTER TABLE materials ADD COLUMN model_id TEXT", [], ); // 为素材表添加模特ID索引 let _ = conn.execute( "CREATE INDEX IF NOT EXISTS idx_materials_model_id ON materials (model_id)", [], ); // 检查是否需要移除模板表的 project_id 字段 // 只有在表中存在 project_id 字段时才需要重建表 let has_project_id_column = conn.prepare("SELECT project_id FROM templates LIMIT 1").is_ok(); if has_project_id_column { println!("Migrating templates table to remove project_id column..."); // SQLite 不支持 DROP COLUMN,需要重建表 let _ = conn.execute( "CREATE TABLE IF NOT EXISTS templates_new ( id TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT, canvas_width INTEGER NOT NULL, canvas_height INTEGER NOT NULL, canvas_ratio TEXT NOT NULL, duration INTEGER NOT NULL, fps REAL NOT NULL, import_status TEXT NOT NULL DEFAULT 'Pending', source_file_path TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, is_active BOOLEAN DEFAULT 1 )", [], ); // 迁移数据(排除 project_id) let _ = conn.execute( "INSERT OR IGNORE INTO templates_new SELECT id, name, description, canvas_width, canvas_height, canvas_ratio, duration, fps, import_status, source_file_path, created_at, updated_at, is_active FROM templates", [], ); // 删除旧表并重命名新表 let _ = conn.execute("DROP TABLE IF EXISTS templates_old", []); let _ = conn.execute("ALTER TABLE templates RENAME TO templates_old", []); let _ = conn.execute("ALTER TABLE templates_new RENAME TO templates", []); println!("Templates table migration completed"); } // 检查是否需要修复轨道片段表的外键约束问题 // 只有在表结构需要更新时才重建表 let needs_track_segments_migration = conn.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='track_segments_old'").is_err(); if needs_track_segments_migration { println!("Migrating track_segments table structure..."); // 重建 track_segments 表,恢复正确的 template_material_id 外键约束 let _ = conn.execute( "CREATE TABLE IF NOT EXISTS track_segments_new ( id TEXT PRIMARY KEY, track_id TEXT NOT NULL, template_material_id TEXT, name TEXT NOT NULL, start_time INTEGER NOT NULL, end_time INTEGER NOT NULL, duration INTEGER NOT NULL, segment_index INTEGER NOT NULL, properties TEXT, matching_rule TEXT DEFAULT '\"FixedMaterial\"', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (track_id) REFERENCES tracks (id) ON DELETE CASCADE, FOREIGN KEY (template_material_id) REFERENCES template_materials (id) ON DELETE SET NULL )", [], ); // 迁移轨道片段数据 // 首先检查旧表是否有 matching_rule 字段 let has_matching_rule = conn.prepare("SELECT matching_rule FROM track_segments LIMIT 1").is_ok(); if has_matching_rule { // 如果有 matching_rule 字段,包含它在迁移中 let _ = conn.execute( "INSERT OR IGNORE INTO track_segments_new SELECT id, track_id, template_material_id, name, start_time, end_time, duration, segment_index, properties, matching_rule, created_at, updated_at FROM track_segments", [], ); } else { // 如果没有 matching_rule 字段,使用默认值 let _ = conn.execute( "INSERT OR IGNORE INTO track_segments_new SELECT id, track_id, template_material_id, name, start_time, end_time, duration, segment_index, properties, '\"FixedMaterial\"', created_at, updated_at FROM track_segments", [], ); } // 删除旧表并重命名新表 let _ = conn.execute("DROP TABLE IF EXISTS track_segments_old", []); let _ = conn.execute("ALTER TABLE track_segments RENAME TO track_segments_old", []); let _ = conn.execute("ALTER TABLE track_segments_new RENAME TO track_segments", []); println!("Track segments table migration completed"); } // 创建模板素材表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_template_materials_template_id ON template_materials (template_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_template_materials_upload_status ON template_materials (upload_status)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_template_materials_original_id ON template_materials (original_id)", [], )?; // 创建轨道表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_tracks_template_id ON tracks (template_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_tracks_track_index ON tracks (track_index)", [], )?; // 创建轨道片段表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_track_segments_track_id ON track_segments (track_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_track_segments_material_id ON track_segments (template_material_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_track_segments_segment_index ON track_segments (segment_index)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_materials_status ON materials (processing_status)", [], )?; // 创建素材片段表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_segments_material_id ON material_segments (material_id)", [], )?; // 创建模特表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_models_name ON models (name)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_models_gender ON models (gender)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_models_status ON models (status)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_models_rating ON models (rating)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_models_created_at ON models (created_at)", [], )?; // 创建模特照片表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_model_photos_model_id ON model_photos (model_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_model_photos_type ON model_photos (photo_type)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_model_photos_cover ON model_photos (is_cover)", [], )?; // 创建视频分类记录表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_video_classification_records_segment_id ON video_classification_records (segment_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_video_classification_records_material_id ON video_classification_records (material_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_video_classification_records_project_id ON video_classification_records (project_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_video_classification_records_category ON video_classification_records (category)", [], )?; // 创建视频分类任务表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_video_classification_tasks_segment_id ON video_classification_tasks (segment_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_video_classification_tasks_material_id ON video_classification_tasks (material_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_video_classification_tasks_project_id ON video_classification_tasks (project_id)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_video_classification_tasks_status ON video_classification_tasks (status)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_video_classification_tasks_priority ON video_classification_tasks (priority)", [], )?; // 创建AI分类表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_ai_classifications_name ON ai_classifications (name)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_ai_classifications_active ON ai_classifications (is_active)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_ai_classifications_sort_order ON ai_classifications (sort_order)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_ai_classifications_created_at ON ai_classifications (created_at)", [], )?; // 修复AI分类表的is_active字段类型(从BOOLEAN改为INTEGER) // 检查表是否存在且字段类型不正确 let table_info: Result, bool)>, rusqlite::Error> = conn.prepare("PRAGMA table_info(ai_classifications)")? .query_map([], |row| { Ok(( row.get::<_, i32>(0)?, // cid row.get::<_, String>(1)?, // name row.get::<_, String>(2)?, // type row.get::<_, bool>(3)?, // notnull row.get::<_, Option>(4)?, // dflt_value row.get::<_, bool>(5)?, // pk )) })? .collect(); if let Ok(columns) = table_info { // 检查is_active字段是否为BOOLEAN类型 let has_boolean_is_active = columns.iter().any(|(_, name, type_name, _, _, _)| { name == "is_active" && type_name.to_uppercase() == "BOOLEAN" }); if has_boolean_is_active { println!("检测到is_active字段为BOOLEAN类型,开始修复..."); // 创建新表 conn.execute( "CREATE TABLE ai_classifications_new ( id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, prompt_text TEXT NOT NULL, description TEXT, is_active INTEGER DEFAULT 1, sort_order INTEGER DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP )", [], )?; // 复制数据,将BOOLEAN转换为INTEGER conn.execute( "INSERT INTO ai_classifications_new SELECT id, name, prompt_text, description, CASE WHEN is_active THEN 1 ELSE 0 END as is_active, sort_order, created_at, updated_at FROM ai_classifications", [], )?; // 删除旧表 conn.execute("DROP TABLE ai_classifications", [])?; // 重命名新表 conn.execute("ALTER TABLE ai_classifications_new RENAME TO ai_classifications", [])?; // 重新创建索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_ai_classifications_name ON ai_classifications (name)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_ai_classifications_active ON ai_classifications (is_active)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_ai_classifications_sort_order ON ai_classifications (sort_order)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_ai_classifications_created_at ON ai_classifications (created_at)", [], )?; println!("AI分类表结构修复完成"); } } 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); } } // 添加模特关联字段到素材表 let has_model_id_column = conn.prepare("SELECT model_id FROM materials LIMIT 1").is_ok(); if !has_model_id_column { println!("Adding model_id column to materials table"); match conn.execute( "ALTER TABLE materials ADD COLUMN model_id TEXT", [], ) { Ok(_) => { println!("Successfully added model_id column to materials table"); // 创建模特关联索引 match conn.execute( "CREATE INDEX IF NOT EXISTS idx_materials_model_id ON materials (model_id)", [], ) { Ok(_) => println!("Successfully created index on model_id column"), Err(e) => println!("Warning: Failed to create index on model_id column: {}", e), } } Err(e) => { println!("Error adding model_id column to materials table: {}", e); return Err(e.into()); } } } else { println!("model_id column already exists in materials table"); } // 添加片段匹配规则字段到轨道片段表 let has_matching_rule_column = conn.prepare("SELECT matching_rule FROM track_segments LIMIT 1").is_ok(); if !has_matching_rule_column { println!("Adding matching_rule column to track_segments table"); conn.execute( "ALTER TABLE track_segments ADD COLUMN matching_rule TEXT DEFAULT '\"FixedMaterial\"'", [], )?; println!("Added matching_rule column to track_segments table"); } // 添加缩略图路径字段到素材片段表 let has_thumbnail_path_column = conn.prepare("SELECT thumbnail_path FROM material_segments LIMIT 1").is_ok(); if !has_thumbnail_path_column { println!("Adding thumbnail_path column to material_segments table"); conn.execute( "ALTER TABLE material_segments ADD COLUMN thumbnail_path TEXT", [], )?; println!("Added thumbnail_path column to material_segments table"); } // 添加缩略图路径字段到素材表 let has_material_thumbnail_path_column = conn.prepare("SELECT thumbnail_path FROM materials LIMIT 1").is_ok(); if !has_material_thumbnail_path_column { println!("Adding thumbnail_path column to materials table"); conn.execute( "ALTER TABLE materials ADD COLUMN thumbnail_path TEXT", [], )?; println!("Added thumbnail_path column to materials table"); } // 添加素材使用状态字段到素材片段表 let has_usage_count_column = conn.prepare("SELECT usage_count FROM material_segments LIMIT 1").is_ok(); if !has_usage_count_column { println!("Adding usage tracking columns to material_segments table"); conn.execute( "ALTER TABLE material_segments ADD COLUMN usage_count INTEGER DEFAULT 0", [], )?; conn.execute( "ALTER TABLE material_segments ADD COLUMN is_used BOOLEAN DEFAULT 0", [], )?; conn.execute( "ALTER TABLE material_segments ADD COLUMN last_used_at DATETIME", [], )?; println!("Added usage tracking columns to material_segments table"); } // 添加导出次数字段到模板匹配结果表 let has_export_count_column = conn.prepare("SELECT export_count FROM template_matching_results LIMIT 1").is_ok(); if !has_export_count_column { println!("Adding export_count column to template_matching_results table"); conn.execute( "ALTER TABLE template_matching_results ADD COLUMN export_count INTEGER DEFAULT 0", [], )?; println!("Added export_count column to template_matching_results table"); } // 暂时禁用自动清理,避免启动时卡住 // self.cleanup_invalid_projects()?; println!("Database migrations completed"); Ok(()) } /// 获取数据库文件路径 /// 遵循安全存储原则,将数据库存储在应用数据目录 fn get_database_path() -> PathBuf { // 优先使用应用数据目录 if let Some(data_dir) = dirs::data_dir() { 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("mixvideo.db") } } /// 获取数据库路径的调试信息 pub fn get_database_path_info() -> String { let path = Self::get_database_path(); format!("Database path: {}", path.display()) } }