diff --git a/src-tauri/src/auto_launch.rs b/src-tauri/src/auto_launch.rs index 26c1d5c33..c69ae46ad 100644 --- a/src-tauri/src/auto_launch.rs +++ b/src-tauri/src/auto_launch.rs @@ -75,8 +75,7 @@ mod tests { #[cfg(target_os = "macos")] #[test] fn test_get_macos_app_bundle_path_valid() { - let exe_path = - std::path::Path::new("/Applications/CC Switch.app/Contents/MacOS/CC Switch"); + let exe_path = std::path::Path::new("/Applications/CC Switch.app/Contents/MacOS/CC Switch"); let result = get_macos_app_bundle_path(exe_path); assert_eq!( result, @@ -92,7 +91,9 @@ mod tests { let result = get_macos_app_bundle_path(exe_path); assert_eq!( result, - Some(std::path::PathBuf::from("/Users/test/My Apps/CC Switch.app")) + Some(std::path::PathBuf::from( + "/Users/test/My Apps/CC Switch.app" + )) ); } diff --git a/src-tauri/src/database/dao/universal_providers.rs b/src-tauri/src/database/dao/universal_providers.rs index 32c955009..a822e23ee 100644 --- a/src-tauri/src/database/dao/universal_providers.rs +++ b/src-tauri/src/database/dao/universal_providers.rs @@ -12,22 +12,22 @@ const UNIVERSAL_PROVIDERS_KEY: &str = "universal_providers"; impl Database { /// 获取所有统一供应商 - pub fn get_all_universal_providers(&self) -> Result, AppError> { + pub fn get_all_universal_providers( + &self, + ) -> Result, AppError> { let conn = lock_conn!(self.conn); - + let mut stmt = conn .prepare("SELECT value FROM settings WHERE key = ?") .map_err(|e| AppError::Database(e.to_string()))?; - + let result: Option = stmt .query_row([UNIVERSAL_PROVIDERS_KEY], |row| row.get(0)) .ok(); - + match result { - Some(json) => { - serde_json::from_str(&json) - .map_err(|e| AppError::Database(format!("解析统一供应商数据失败: {e}"))) - } + Some(json) => serde_json::from_str(&json) + .map_err(|e| AppError::Database(format!("解析统一供应商数据失败: {e}"))), None => Ok(HashMap::new()), } } @@ -62,14 +62,13 @@ impl Database { ) -> Result<(), AppError> { let conn = lock_conn!(self.conn); let json = to_json_string(providers)?; - + conn.execute( "INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)", [UNIVERSAL_PROVIDERS_KEY, &json], ) .map_err(|e| AppError::Database(e.to_string()))?; - + Ok(()) } } - diff --git a/src-tauri/src/database/schema.rs b/src-tauri/src/database/schema.rs index 0a1429110..52187ae47 100644 --- a/src-tauri/src/database/schema.rs +++ b/src-tauri/src/database/schema.rs @@ -233,6 +233,24 @@ impl Database { [], ); + // 尝试添加基础配置列到 proxy_config 表(兼容 v3.9.0-2 升级) + let _ = conn.execute( + "ALTER TABLE proxy_config ADD COLUMN proxy_enabled INTEGER NOT NULL DEFAULT 0", + [], + ); + let _ = conn.execute( + "ALTER TABLE proxy_config ADD COLUMN listen_address TEXT NOT NULL DEFAULT '127.0.0.1'", + [], + ); + let _ = conn.execute( + "ALTER TABLE proxy_config ADD COLUMN listen_port INTEGER NOT NULL DEFAULT 5000", + [], + ); + let _ = conn.execute( + "ALTER TABLE proxy_config ADD COLUMN enable_logging INTEGER NOT NULL DEFAULT 1", + [], + ); + // 尝试添加超时配置列到 proxy_config 表 let _ = conn.execute( "ALTER TABLE proxy_config ADD COLUMN streaming_first_byte_timeout INTEGER NOT NULL DEFAULT 30", @@ -247,6 +265,14 @@ impl Database { [], ); + // 兼容:若旧版 proxy_config 仍为单例结构(无 app_type),则在启动时直接转换为三行结构 + // 说明:user_version=2 时不会再触发 v1->v2 迁移,但新代码查询依赖 app_type 列。 + if Self::table_exists(conn, "proxy_config")? + && !Self::has_column(conn, "proxy_config", "app_type")? + { + Self::migrate_proxy_config_to_per_app(conn)?; + } + // 确保 in_failover_queue 列存在(对于已存在的 v2 数据库) Self::add_column_if_missing( conn, @@ -411,6 +437,32 @@ impl Database { // 添加代理超时配置字段 if Self::table_exists(conn, "proxy_config")? { + // 兼容旧版本缺失的基础字段 + Self::add_column_if_missing( + conn, + "proxy_config", + "proxy_enabled", + "INTEGER NOT NULL DEFAULT 0", + )?; + Self::add_column_if_missing( + conn, + "proxy_config", + "listen_address", + "TEXT NOT NULL DEFAULT '127.0.0.1'", + )?; + Self::add_column_if_missing( + conn, + "proxy_config", + "listen_port", + "INTEGER NOT NULL DEFAULT 5000", + )?; + Self::add_column_if_missing( + conn, + "proxy_config", + "enable_logging", + "INTEGER NOT NULL DEFAULT 1", + )?; + Self::add_column_if_missing( conn, "proxy_config", diff --git a/src-tauri/src/database/tests.rs b/src-tauri/src/database/tests.rs index 74172fe58..de20a8b86 100644 --- a/src-tauri/src/database/tests.rs +++ b/src-tauri/src/database/tests.rs @@ -201,6 +201,53 @@ fn migration_aligns_column_defaults_and_types() { ); } +#[test] +fn create_tables_repairs_legacy_proxy_config_singleton_to_per_app() { + let conn = Connection::open_in_memory().expect("open memory db"); + + // 模拟测试版 v2:user_version=2,但 proxy_config 仍是单例结构(无 app_type) + Database::set_user_version(&conn, 2).expect("set user_version"); + conn.execute_batch( + r#" + CREATE TABLE proxy_config ( + id INTEGER PRIMARY KEY, + enabled INTEGER NOT NULL DEFAULT 0, + listen_address TEXT NOT NULL DEFAULT '127.0.0.1', + listen_port INTEGER NOT NULL DEFAULT 5000, + max_retries INTEGER NOT NULL DEFAULT 3, + request_timeout INTEGER NOT NULL DEFAULT 300, + enable_logging INTEGER NOT NULL DEFAULT 1, + target_app TEXT NOT NULL DEFAULT 'claude', + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) + ); + INSERT INTO proxy_config (id, enabled) VALUES (1, 1); + "#, + ) + .expect("seed legacy proxy_config"); + + Database::create_tables_on_conn(&conn).expect("create tables should repair proxy_config"); + + assert!( + Database::has_column(&conn, "proxy_config", "app_type").expect("check app_type"), + "proxy_config should be migrated to per-app structure" + ); + + let count: i32 = conn + .query_row("SELECT COUNT(*) FROM proxy_config", [], |r| r.get(0)) + .expect("count rows"); + assert_eq!(count, 3, "per-app proxy_config should have 3 rows"); + + // 新结构下应能按 app_type 查询 + let _: i32 = conn + .query_row( + "SELECT COUNT(*) FROM proxy_config WHERE app_type = 'claude'", + [], + |r| r.get(0), + ) + .expect("query by app_type"); +} + #[test] fn dry_run_does_not_write_to_disk() { // Create minimal valid config for migration