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
This commit is contained in:
@@ -6,20 +6,22 @@ use tauri::State;
|
|||||||
use tauri_plugin_dialog::DialogExt;
|
use tauri_plugin_dialog::DialogExt;
|
||||||
|
|
||||||
use crate::error::AppError;
|
use crate::error::AppError;
|
||||||
use crate::services::ConfigService;
|
use crate::services::provider::ProviderService;
|
||||||
use crate::store::AppState;
|
use crate::store::AppState;
|
||||||
|
|
||||||
/// 导出配置文件
|
/// 导出数据库为 SQL 备份
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn export_config_to_file(
|
pub async fn export_config_to_file(
|
||||||
#[allow(non_snake_case)] filePath: String,
|
#[allow(non_snake_case)] filePath: String,
|
||||||
|
state: State<'_, AppState>,
|
||||||
) -> Result<Value, String> {
|
) -> Result<Value, String> {
|
||||||
|
let db = state.db.clone();
|
||||||
tauri::async_runtime::spawn_blocking(move || {
|
tauri::async_runtime::spawn_blocking(move || {
|
||||||
let target_path = PathBuf::from(&filePath);
|
let target_path = PathBuf::from(&filePath);
|
||||||
ConfigService::export_config_to_path(&target_path)?;
|
db.export_sql(&target_path)?;
|
||||||
Ok::<_, AppError>(json!({
|
Ok::<_, AppError>(json!({
|
||||||
"success": true,
|
"success": true,
|
||||||
"message": "Configuration exported successfully",
|
"message": "SQL exported successfully",
|
||||||
"filePath": filePath
|
"filePath": filePath
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
@@ -28,65 +30,49 @@ pub async fn export_config_to_file(
|
|||||||
.map_err(|e: AppError| e.to_string())
|
.map_err(|e: AppError| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 从文件导入配置
|
/// 从 SQL 备份导入数据库
|
||||||
/// TODO: 需要重构以使用数据库而不是 JSON 配置
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn import_config_from_file(
|
pub async fn import_config_from_file(
|
||||||
#[allow(non_snake_case)] _filePath: String,
|
#[allow(non_snake_case)] filePath: String,
|
||||||
_state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Value, String> {
|
) -> Result<Value, String> {
|
||||||
// TODO: 实现基于数据库的导入逻辑
|
let db = state.db.clone();
|
||||||
// 当前暂时禁用此功能
|
let db_for_state = db.clone();
|
||||||
Err("配置导入功能正在重构中,暂时不可用".to_string())
|
tauri::async_runtime::spawn_blocking(move || {
|
||||||
|
|
||||||
/* 旧的实现,需要重构:
|
|
||||||
let (new_config, backup_id) = tauri::async_runtime::spawn_blocking(move || {
|
|
||||||
let path_buf = PathBuf::from(&filePath);
|
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
|
.await
|
||||||
.map_err(|e| format!("导入配置失败: {e}"))?
|
.map_err(|e| format!("导入配置失败: {e}"))?
|
||||||
.map_err(|e: AppError| e.to_string())?;
|
.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
|
|
||||||
}))
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 同步当前供应商配置到对应的 live 文件
|
|
||||||
/// TODO: 需要重构以使用数据库而不是 JSON 配置
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn sync_current_providers_live(_state: State<'_, AppState>) -> Result<Value, String> {
|
pub async fn sync_current_providers_live(state: State<'_, AppState>) -> Result<Value, String> {
|
||||||
// TODO: 实现基于数据库的同步逻辑
|
let db = state.db.clone();
|
||||||
// 当前暂时禁用此功能
|
tauri::async_runtime::spawn_blocking(move || {
|
||||||
Err("配置同步功能正在重构中,暂时不可用".to_string())
|
let app_state = AppState::new(db);
|
||||||
|
ProviderService::sync_current_from_db(&app_state)?;
|
||||||
/* 旧的实现,需要重构:
|
Ok::<_, AppError>(json!({
|
||||||
{
|
|
||||||
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,
|
"success": true,
|
||||||
"message": "Live configuration synchronized"
|
"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<R: tauri::Runtime>(
|
|||||||
let dialog = app.dialog();
|
let dialog = app.dialog();
|
||||||
let result = dialog
|
let result = dialog
|
||||||
.file()
|
.file()
|
||||||
.add_filter("JSON", &["json"])
|
.add_filter("SQL", &["sql"])
|
||||||
.set_file_name(&defaultName)
|
.set_file_name(&defaultName)
|
||||||
.blocking_save_file();
|
.blocking_save_file();
|
||||||
|
|
||||||
@@ -113,7 +99,7 @@ pub async fn open_file_dialog<R: tauri::Runtime>(
|
|||||||
let dialog = app.dialog();
|
let dialog = app.dialog();
|
||||||
let result = dialog
|
let result = dialog
|
||||||
.file()
|
.file()
|
||||||
.add_filter("JSON", &["json"])
|
.add_filter("SQL", &["sql"])
|
||||||
.blocking_pick_file();
|
.blocking_pick_file();
|
||||||
|
|
||||||
Ok(result.map(|p| p.to_string()))
|
Ok(result.map(|p| p.to_string()))
|
||||||
|
|||||||
@@ -570,6 +570,7 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crate::settings::bind_db(db.clone());
|
||||||
let app_state = AppState::new(db);
|
let app_state = AppState::new(db);
|
||||||
|
|
||||||
// 检查是否需要首次导入(数据库为空)
|
// 检查是否需要首次导入(数据库为空)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use crate::config::{
|
|||||||
};
|
};
|
||||||
use crate::error::AppError;
|
use crate::error::AppError;
|
||||||
use crate::provider::{Provider, UsageData, UsageResult};
|
use crate::provider::{Provider, UsageData, UsageResult};
|
||||||
|
use crate::services::mcp::McpService;
|
||||||
use crate::settings::{self, CustomEndpoint};
|
use crate::settings::{self, CustomEndpoint};
|
||||||
use crate::store::AppState;
|
use crate::store::AppState;
|
||||||
use crate::usage_script;
|
use crate::usage_script;
|
||||||
@@ -550,6 +551,30 @@ impl ProviderService {
|
|||||||
Ok(())
|
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(
|
pub fn list(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
|
|||||||
Reference in New Issue
Block a user