From fd25c9949f8c31def321530702fc47a00ea16c8f Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Mon, 24 Nov 2025 00:46:00 +0800 Subject: [PATCH] refactor(backend): migrate import/export to use SQL backup - Reimplement export_config_to_file to use database.export_sql - Reimplement import_config_from_file to use database.import_sql - Add sync_current_from_db to sync live configs after import - Add settings database binding on app initialization - Remove deprecated JSON-based config import logic --- src-tauri/src/commands/import_export.rs | 96 +++++++++++-------------- src-tauri/src/lib.rs | 1 + src-tauri/src/services/provider.rs | 25 +++++++ 3 files changed, 67 insertions(+), 55 deletions(-) diff --git a/src-tauri/src/commands/import_export.rs b/src-tauri/src/commands/import_export.rs index 9d237a9..fe2fde1 100644 --- a/src-tauri/src/commands/import_export.rs +++ b/src-tauri/src/commands/import_export.rs @@ -6,20 +6,22 @@ use tauri::State; use tauri_plugin_dialog::DialogExt; use crate::error::AppError; -use crate::services::ConfigService; +use crate::services::provider::ProviderService; use crate::store::AppState; -/// 导出配置文件 +/// 导出数据库为 SQL 备份 #[tauri::command] pub async fn export_config_to_file( #[allow(non_snake_case)] filePath: String, + state: State<'_, AppState>, ) -> Result { + let db = state.db.clone(); tauri::async_runtime::spawn_blocking(move || { let target_path = PathBuf::from(&filePath); - ConfigService::export_config_to_path(&target_path)?; + db.export_sql(&target_path)?; Ok::<_, AppError>(json!({ "success": true, - "message": "Configuration exported successfully", + "message": "SQL exported successfully", "filePath": filePath })) }) @@ -28,65 +30,49 @@ pub async fn export_config_to_file( .map_err(|e: AppError| e.to_string()) } -/// 从文件导入配置 -/// TODO: 需要重构以使用数据库而不是 JSON 配置 +/// 从 SQL 备份导入数据库 #[tauri::command] pub async fn import_config_from_file( - #[allow(non_snake_case)] _filePath: String, - _state: State<'_, AppState>, + #[allow(non_snake_case)] filePath: String, + state: State<'_, AppState>, ) -> Result { - // TODO: 实现基于数据库的导入逻辑 - // 当前暂时禁用此功能 - Err("配置导入功能正在重构中,暂时不可用".to_string()) - - /* 旧的实现,需要重构: - let (new_config, backup_id) = tauri::async_runtime::spawn_blocking(move || { + let db = state.db.clone(); + let db_for_state = db.clone(); + tauri::async_runtime::spawn_blocking(move || { let path_buf = PathBuf::from(&filePath); - ConfigService::load_config_for_import(&path_buf) + let backup_id = db.import_sql(&path_buf)?; + + // 导入后同步当前供应商到各自的 live 配置 + let app_state = AppState::new(db_for_state); + if let Err(err) = ProviderService::sync_current_from_db(&app_state) { + log::warn!("导入后同步 live 配置失败: {err}"); + } + + Ok::<_, AppError>(json!({ + "success": true, + "message": "SQL imported successfully", + "backupId": backup_id + })) }) .await .map_err(|e| format!("导入配置失败: {e}"))? - .map_err(|e: AppError| e.to_string())?; - - { - let mut guard = state - .config - .write() - .map_err(|e| AppError::from(e).to_string())?; - *guard = new_config; - } - - Ok(json!({ - "success": true, - "message": "Configuration imported successfully", - "backupId": backup_id - })) - */ + .map_err(|e: AppError| e.to_string()) } -/// 同步当前供应商配置到对应的 live 文件 -/// TODO: 需要重构以使用数据库而不是 JSON 配置 #[tauri::command] -pub async fn sync_current_providers_live(_state: State<'_, AppState>) -> Result { - // TODO: 实现基于数据库的同步逻辑 - // 当前暂时禁用此功能 - Err("配置同步功能正在重构中,暂时不可用".to_string()) - - /* 旧的实现,需要重构: - { - let mut config_state = state - .config - .write() - .map_err(|e| AppError::from(e).to_string())?; - ConfigService::sync_current_providers_to_live(&mut config_state) - .map_err(|e| e.to_string())?; - } - - Ok(json!({ - "success": true, - "message": "Live configuration synchronized" - })) - */ +pub async fn sync_current_providers_live(state: State<'_, AppState>) -> Result { + let db = state.db.clone(); + tauri::async_runtime::spawn_blocking(move || { + let app_state = AppState::new(db); + ProviderService::sync_current_from_db(&app_state)?; + Ok::<_, AppError>(json!({ + "success": true, + "message": "Live configuration synchronized" + })) + }) + .await + .map_err(|e| format!("同步当前供应商失败: {e}"))? + .map_err(|e: AppError| e.to_string()) } /// 保存文件对话框 @@ -98,7 +84,7 @@ pub async fn save_file_dialog( let dialog = app.dialog(); let result = dialog .file() - .add_filter("JSON", &["json"]) + .add_filter("SQL", &["sql"]) .set_file_name(&defaultName) .blocking_save_file(); @@ -113,7 +99,7 @@ pub async fn open_file_dialog( let dialog = app.dialog(); let result = dialog .file() - .add_filter("JSON", &["json"]) + .add_filter("SQL", &["sql"]) .blocking_pick_file(); Ok(result.map(|p| p.to_string())) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index db06c3a..f9fa2d5 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -570,6 +570,7 @@ pub fn run() { } } + crate::settings::bind_db(db.clone()); let app_state = AppState::new(db); // 检查是否需要首次导入(数据库为空) diff --git a/src-tauri/src/services/provider.rs b/src-tauri/src/services/provider.rs index 84296c1..40cbfb8 100644 --- a/src-tauri/src/services/provider.rs +++ b/src-tauri/src/services/provider.rs @@ -12,6 +12,7 @@ use crate::config::{ }; use crate::error::AppError; use crate::provider::{Provider, UsageData, UsageResult}; +use crate::services::mcp::McpService; use crate::settings::{self, CustomEndpoint}; use crate::store::AppState; use crate::usage_script; @@ -550,6 +551,30 @@ impl ProviderService { Ok(()) } + /// 将数据库中的当前供应商同步到对应 live 配置 + pub fn sync_current_from_db(state: &AppState) -> Result<(), AppError> { + for app_type in [AppType::Claude, AppType::Codex, AppType::Gemini] { + let current_id = match state.db.get_current_provider(app_type.as_str())? { + Some(id) => id, + None => continue, + }; + let providers = state.db.get_all_providers(app_type.as_str())?; + if let Some(provider) = providers.get(¤t_id) { + Self::write_live_snapshot(&app_type, provider)?; + } else { + log::warn!( + "无法同步 live 配置: 当前供应商 {} ({}) 未找到", + current_id, + app_type.as_str() + ); + } + } + + // MCP 同步 + McpService::sync_all_enabled(state)?; + Ok(()) + } + /// 列出指定应用下的所有供应商 pub fn list( state: &AppState,