//! 数据库模块 - SQLite 数据持久化 //! //! 此模块提供应用的核心数据存储功能,包括: //! - 供应商配置管理 //! - MCP 服务器配置 //! - 提示词管理 //! - Skills 管理 //! - 通用设置存储 //! //! ## 架构设计 //! //! ```text //! database/ //! ├── mod.rs - Database 结构体 + 初始化 //! ├── schema.rs - 表结构定义 + Schema 迁移 //! ├── backup.rs - SQL 导入导出 + 快照备份 //! ├── migration.rs - JSON → SQLite 数据迁移 //! └── dao/ - 数据访问对象 //! ├── providers.rs //! ├── mcp.rs //! ├── prompts.rs //! ├── skills.rs //! └── settings.rs //! ``` mod backup; mod dao; mod migration; mod schema; #[cfg(test)] mod tests; // DAO 类型导出供外部使用 pub use dao::FailoverQueueItem; use crate::config::get_app_config_dir; use crate::error::AppError; use rusqlite::Connection; use serde::Serialize; use std::sync::Mutex; // DAO 方法通过 impl Database 提供,无需额外导出 /// 数据库备份保留数量 const DB_BACKUP_RETAIN: usize = 10; /// 当前 Schema 版本号 /// 每次修改表结构时递增,并在 schema.rs 中添加相应的迁移逻辑 pub(crate) const SCHEMA_VERSION: i32 = 5; /// 安全地序列化 JSON,避免 unwrap panic pub(crate) fn to_json_string(value: &T) -> Result { serde_json::to_string(value) .map_err(|e| AppError::Config(format!("JSON serialization failed: {e}"))) } /// 安全地获取 Mutex 锁,避免 unwrap panic macro_rules! lock_conn { ($mutex:expr) => { $mutex .lock() .map_err(|e| AppError::Database(format!("Mutex lock failed: {}", e)))? }; } // 导出宏供子模块使用 pub(crate) use lock_conn; /// 数据库连接封装 /// /// 使用 Mutex 包装 Connection 以支持在多线程环境(如 Tauri State)中共享。 /// rusqlite::Connection 本身不是 Sync 的,因此需要这层包装。 pub struct Database { pub(crate) conn: Mutex, } impl Database { /// 初始化数据库连接并创建表 /// /// 数据库文件位于 `~/.cc-switch/cc-switch.db` pub fn init() -> Result { let db_path = get_app_config_dir().join("cc-switch.db"); // 确保父目录存在 if let Some(parent) = db_path.parent() { std::fs::create_dir_all(parent).map_err(|e| AppError::io(parent, e))?; } let conn = Connection::open(&db_path).map_err(|e| AppError::Database(e.to_string()))?; // 启用外键约束 conn.execute("PRAGMA foreign_keys = ON;", []) .map_err(|e| AppError::Database(e.to_string()))?; let db = Self { conn: Mutex::new(conn), }; db.create_tables()?; db.apply_schema_migrations()?; db.ensure_model_pricing_seeded()?; Ok(db) } /// 创建内存数据库(用于测试) pub fn memory() -> Result { let conn = Connection::open_in_memory().map_err(|e| AppError::Database(e.to_string()))?; // 启用外键约束 conn.execute("PRAGMA foreign_keys = ON;", []) .map_err(|e| AppError::Database(e.to_string()))?; let db = Self { conn: Mutex::new(conn), }; db.create_tables()?; db.ensure_model_pricing_seeded()?; Ok(db) } /// 检查 MCP 服务器表是否为空 pub fn is_mcp_table_empty(&self) -> Result { let conn = lock_conn!(self.conn); let count: i64 = conn .query_row("SELECT COUNT(*) FROM mcp_servers", [], |row| row.get(0)) .map_err(|e| AppError::Database(e.to_string()))?; Ok(count == 0) } /// 检查提示词表是否为空 pub fn is_prompts_table_empty(&self) -> Result { let conn = lock_conn!(self.conn); let count: i64 = conn .query_row("SELECT COUNT(*) FROM prompts", [], |row| row.get(0)) .map_err(|e| AppError::Database(e.to_string()))?; Ok(count == 0) } }