From 6a7c2df2d2ab41d978c4f1d1bf9cfffedec72290 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 11 Dec 2025 11:57:53 +0800 Subject: [PATCH] fix(proxy): sync live config tokens to database before takeover When proxy takeover is activated, tokens are replaced with placeholders in live config files. However, the proxy reads tokens from the database, not from live files. If the user's token only exists in the live config (e.g., manually added), the database won't have it, causing auth failures. Changes: - Add sync_live_to_providers() to sync tokens from live configs to DB - Add update_provider_settings_config() DAO method for partial updates - Use "PROXY_MANAGED" placeholder instead of empty string to avoid "missing API key" warnings in Claude Code status bar - Integrate sync step into start_with_takeover() flow before clearing tokens The new takeover flow: 1. setup_proxy_targets() 2. backup_live_configs() 3. sync_live_to_providers() <- NEW 4. takeover_live_configs() 5. Start proxy server --- src-tauri/src/database/dao/providers.rs | 20 +++ src-tauri/src/services/proxy.rs | 170 +++++++++++++++++++++--- 2 files changed, 174 insertions(+), 16 deletions(-) diff --git a/src-tauri/src/database/dao/providers.rs b/src-tauri/src/database/dao/providers.rs index 63ccb70dc..b9cf8de8a 100644 --- a/src-tauri/src/database/dao/providers.rs +++ b/src-tauri/src/database/dao/providers.rs @@ -483,6 +483,26 @@ impl Database { Ok(targets) } + /// 更新供应商的 settings_config(仅更新配置,不改变其他字段) + pub fn update_provider_settings_config( + &self, + app_type: &str, + provider_id: &str, + settings_config: &serde_json::Value, + ) -> Result<(), AppError> { + let conn = lock_conn!(self.conn); + conn.execute( + "UPDATE providers SET settings_config = ?1 WHERE id = ?2 AND app_type = ?3", + params![ + serde_json::to_string(settings_config).unwrap(), + provider_id, + app_type + ], + ) + .map_err(|e| AppError::Database(e.to_string()))?; + Ok(()) + } + /// 添加自定义端点 pub fn add_custom_endpoint( &self, diff --git a/src-tauri/src/services/proxy.rs b/src-tauri/src/services/proxy.rs index 2d3449875..9a2da2432 100644 --- a/src-tauri/src/services/proxy.rs +++ b/src-tauri/src/services/proxy.rs @@ -71,16 +71,19 @@ impl ProxyService { // 2. 备份各应用的 Live 配置 self.backup_live_configs().await?; - // 3. 接管各应用的 Live 配置(写入代理地址) + // 3. 同步 Live 配置中的 Token 到数据库(确保代理能读到最新的 Token) + self.sync_live_to_providers().await?; + + // 4. 接管各应用的 Live 配置(写入代理地址,清空 Token) self.takeover_live_configs().await?; - // 4. 设置接管状态 + // 5. 设置接管状态 self.db .set_live_takeover_active(true) .await .map_err(|e| format!("设置接管状态失败: {e}"))?; - // 5. 启动代理服务器 + // 6. 启动代理服务器 match self.start().await { Ok(info) => Ok(info), Err(e) => { @@ -102,12 +105,7 @@ impl ProxyService { if let Ok(Some(provider_id)) = self.db.get_current_provider(app_type) { // 设置为代理目标 if let Err(e) = self.db.set_proxy_target(&provider_id, app_type, true).await { - log::warn!( - "设置 {} 的代理目标 {} 失败: {}", - app_type, - provider_id, - e - ); + log::warn!("设置 {} 的代理目标 {} 失败: {}", app_type, provider_id, e); } else { log::info!( "已将 {} 的当前供应商 {} 设置为代理目标", @@ -123,6 +121,138 @@ impl ProxyService { Ok(()) } + /// 同步 Live 配置中的 Token 到数据库 + /// + /// 在清空 Live Token 之前调用,确保数据库中的 Provider 配置有最新的 Token。 + /// 这样代理才能从数据库读取到正确的认证信息。 + async fn sync_live_to_providers(&self) -> Result<(), String> { + // Claude: 同步 ANTHROPIC_AUTH_TOKEN + if let Ok(live_config) = self.read_claude_live() { + if let Some(provider_id) = self.db.get_current_provider("claude").ok().flatten() { + if let Ok(Some(mut provider)) = self.db.get_provider_by_id(&provider_id, "claude") { + // 从 live 配置提取 token + if let Some(env) = live_config.get("env") { + if let Some(token) = + env.get("ANTHROPIC_AUTH_TOKEN").and_then(|v| v.as_str()) + { + if !token.is_empty() { + // 更新 provider 的 settings_config + if let Some(env_obj) = provider + .settings_config + .get_mut("env") + .and_then(|v| v.as_object_mut()) + { + env_obj + .insert("ANTHROPIC_AUTH_TOKEN".to_string(), json!(token)); + } else { + provider.settings_config["env"] = json!({ + "ANTHROPIC_AUTH_TOKEN": token + }); + } + // 保存到数据库 + if let Err(e) = self.db.update_provider_settings_config( + "claude", + &provider_id, + &provider.settings_config, + ) { + log::warn!("同步 Claude Token 到数据库失败: {e}"); + } else { + log::info!( + "已同步 Claude Token 到数据库 (provider: {})", + provider_id + ); + } + } + } + } + } + } + } + + // Codex: 同步 OPENAI_API_KEY + if let Ok(live_config) = self.read_codex_live() { + if let Some(provider_id) = self.db.get_current_provider("codex").ok().flatten() { + if let Ok(Some(mut provider)) = self.db.get_provider_by_id(&provider_id, "codex") { + // 从 live 配置提取 token + if let Some(auth) = live_config.get("auth") { + if let Some(token) = auth.get("OPENAI_API_KEY").and_then(|v| v.as_str()) { + if !token.is_empty() { + // 更新 provider 的 settings_config + if let Some(auth_obj) = provider + .settings_config + .get_mut("auth") + .and_then(|v| v.as_object_mut()) + { + auth_obj.insert("OPENAI_API_KEY".to_string(), json!(token)); + } else { + provider.settings_config["auth"] = json!({ + "OPENAI_API_KEY": token + }); + } + // 保存到数据库 + if let Err(e) = self.db.update_provider_settings_config( + "codex", + &provider_id, + &provider.settings_config, + ) { + log::warn!("同步 Codex Token 到数据库失败: {e}"); + } else { + log::info!( + "已同步 Codex Token 到数据库 (provider: {})", + provider_id + ); + } + } + } + } + } + } + } + + // Gemini: 同步 GOOGLE_API_KEY + if let Ok(live_config) = self.read_gemini_live() { + if let Some(provider_id) = self.db.get_current_provider("gemini").ok().flatten() { + if let Ok(Some(mut provider)) = self.db.get_provider_by_id(&provider_id, "gemini") { + // 从 live 配置提取 token + if let Some(env) = live_config.get("env") { + if let Some(token) = env.get("GOOGLE_API_KEY").and_then(|v| v.as_str()) { + if !token.is_empty() { + // 更新 provider 的 settings_config + if let Some(env_obj) = provider + .settings_config + .get_mut("env") + .and_then(|v| v.as_object_mut()) + { + env_obj.insert("GOOGLE_API_KEY".to_string(), json!(token)); + } else { + provider.settings_config["env"] = json!({ + "GOOGLE_API_KEY": token + }); + } + // 保存到数据库 + if let Err(e) = self.db.update_provider_settings_config( + "gemini", + &provider_id, + &provider.settings_config, + ) { + log::warn!("同步 Gemini Token 到数据库失败: {e}"); + } else { + log::info!( + "已同步 Gemini Token 到数据库 (provider: {})", + provider_id + ); + } + } + } + } + } + } + } + + log::info!("Live 配置 Token 同步完成"); + Ok(()) + } + /// 停止代理服务器 pub async fn stop(&self) -> Result<(), String> { if let Some(server) = self.server.write().await.take() { @@ -214,35 +344,43 @@ impl ProxyService { let proxy_url = format!("http://{}:{}", config.listen_address, config.listen_port); - // Claude: 修改 ANTHROPIC_BASE_URL + // Claude: 修改 ANTHROPIC_BASE_URL,使用占位符替代真实 Token(代理会注入真实 Token) if let Ok(mut live_config) = self.read_claude_live() { if let Some(env) = live_config.get_mut("env").and_then(|v| v.as_object_mut()) { env.insert("ANTHROPIC_BASE_URL".to_string(), json!(&proxy_url)); + // 使用占位符,避免 Claude Code 显示缺少 key 的警告 + env.insert("ANTHROPIC_AUTH_TOKEN".to_string(), json!("PROXY_MANAGED")); } else { live_config["env"] = json!({ - "ANTHROPIC_BASE_URL": &proxy_url + "ANTHROPIC_BASE_URL": &proxy_url, + "ANTHROPIC_AUTH_TOKEN": "PROXY_MANAGED" }); } self.write_claude_live(&live_config)?; log::info!("Claude Live 配置已接管,代理地址: {}", proxy_url); } - // Codex: 修改 OPENAI_BASE_URL + // Codex: 修改 OPENAI_BASE_URL,使用占位符替代真实 Token(代理会注入真实 Token) if let Ok(mut live_config) = self.read_codex_live() { if let Some(auth) = live_config.get_mut("auth").and_then(|v| v.as_object_mut()) { auth.insert("OPENAI_BASE_URL".to_string(), json!(&proxy_url)); + // 使用占位符,避免显示缺少 key 的警告 + auth.insert("OPENAI_API_KEY".to_string(), json!("PROXY_MANAGED")); } self.write_codex_live(&live_config)?; log::info!("Codex Live 配置已接管,代理地址: {}", proxy_url); } - // Gemini: 修改 GEMINI_API_BASE + // Gemini: 修改 GEMINI_API_BASE,使用占位符替代真实 Token(代理会注入真实 Token) if let Ok(mut live_config) = self.read_gemini_live() { if let Some(env) = live_config.get_mut("env").and_then(|v| v.as_object_mut()) { env.insert("GEMINI_API_BASE".to_string(), json!(&proxy_url)); + // 使用占位符,避免显示缺少 key 的警告 + env.insert("GOOGLE_API_KEY".to_string(), json!("PROXY_MANAGED")); } else { live_config["env"] = json!({ - "GEMINI_API_BASE": &proxy_url + "GEMINI_API_BASE": &proxy_url, + "GOOGLE_API_KEY": "PROXY_MANAGED" }); } self.write_gemini_live(&live_config)?; @@ -296,8 +434,8 @@ impl ProxyService { provider_id: &str, ) -> Result<(), String> { // 更新数据库中的 is_current 标记 - let app_type_enum = AppType::from_str(app_type) - .map_err(|_| format!("无效的应用类型: {app_type}"))?; + let app_type_enum = + AppType::from_str(app_type).map_err(|_| format!("无效的应用类型: {app_type}"))?; self.db .set_current_provider(app_type_enum.as_str(), provider_id)