refactor(backend): phase 4 - extract provider service layer

Architecture improvements:
- Extract ProviderService with switch/backfill/write methods
- Reduce command layer from 160 to 13 lines via delegation
- Separate business logic (services) from state management (commands)
- Introduce precise error handling with structured validation

Refactoring details:
- Split Codex/Claude switching into symmetric private methods
- Add multi-layer validation for Codex auth field (existence + type)
- Extract import_config_from_path for command and test reuse
- Expose export_config_to_file and ProviderService in public API

Test coverage:
- Add 10+ integration tests for Claude/Codex switching flows
- Cover import/export success and failure scenarios (JSON parse, missing file)
- Verify state consistency on error paths (current remains unchanged)
- Test snapshot backfill for both old and new providers after switching
This commit is contained in:
Jason
2025-10-28 10:47:48 +08:00
parent 8e980e6974
commit c2e8855a0f
8 changed files with 596 additions and 175 deletions

View File

@@ -10,6 +10,7 @@ use crate::codex_config;
use crate::config::get_claude_settings_path;
use crate::error::AppError;
use crate::provider::{Provider, ProviderMeta};
use crate::services::ProviderService;
use crate::speedtest;
use crate::store::AppState;
@@ -312,160 +313,15 @@ pub async fn delete_provider(
/// 切换供应商
fn switch_provider_internal(state: &AppState, app_type: AppType, id: &str) -> Result<(), AppError> {
use serde_json::Value;
let mut config = state
.config
.lock()
.map_err(|e| AppError::Message(format!("获取锁失败: {}", e)))?;
.map_err(AppError::from)?;
let provider = {
let manager = config
.get_manager_mut(&app_type)
.ok_or_else(|| AppError::Message(format!("应用类型不存在: {:?}", app_type)))?;
manager
.providers
.get(id)
.cloned()
.ok_or_else(|| AppError::Message(format!("供应商不存在: {}", id)))?
};
match app_type {
AppType::Codex => {
if !{
let cur = config
.get_manager_mut(&app_type)
.ok_or_else(|| AppError::Message(format!("应用类型不存在: {:?}", app_type)))?;
cur.current.is_empty()
} {
let auth_path = codex_config::get_codex_auth_path();
let config_path = codex_config::get_codex_config_path();
if auth_path.exists() {
let auth: Value = crate::config::read_json_file(&auth_path)?;
let config_str = if config_path.exists() {
std::fs::read_to_string(&config_path).map_err(|e| {
AppError::Message(format!(
"读取 config.toml 失败: {}: {}",
config_path.display(),
e
))
})?
} else {
String::new()
};
let live = serde_json::json!({
"auth": auth,
"config": config_str,
});
let cur_id2 = {
let m = config
.get_manager(&app_type)
.ok_or_else(|| AppError::Message(format!("应用类型不存在: {:?}", app_type)))?;
m.current.clone()
};
let m = config
.get_manager_mut(&app_type)
.ok_or_else(|| AppError::Message(format!("应用类型不存在: {:?}", app_type)))?;
if let Some(cur) = m.providers.get_mut(&cur_id2) {
cur.settings_config = live;
}
}
}
let auth = provider
.settings_config
.get("auth")
.ok_or_else(|| AppError::Message("目标供应商缺少 auth 配置".to_string()))?;
let cfg_text = provider
.settings_config
.get("config")
.and_then(|v| v.as_str());
crate::codex_config::write_codex_live_atomic(auth, cfg_text)?;
}
AppType::Claude => {
use crate::config::{read_json_file, write_json_file};
let settings_path = get_claude_settings_path();
if settings_path.exists() {
let cur_id = {
let m = config
.get_manager(&app_type)
.ok_or_else(|| AppError::Message(format!("应用类型不存在: {:?}", app_type)))?;
m.current.clone()
};
if !cur_id.is_empty() {
if let Ok(live) = read_json_file::<serde_json::Value>(&settings_path) {
let m = config
.get_manager_mut(&app_type)
.ok_or_else(|| AppError::Message(format!("应用类型不存在: {:?}", app_type)))?;
if let Some(cur) = m.providers.get_mut(&cur_id) {
cur.settings_config = live;
}
}
}
}
if let Some(parent) = settings_path.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| AppError::Message(format!("创建目录失败: {}", e)))?;
}
write_json_file(&settings_path, &provider.settings_config)?;
if settings_path.exists() {
if let Ok(live_after) = read_json_file::<serde_json::Value>(&settings_path) {
let m = config
.get_manager_mut(&app_type)
.ok_or_else(|| AppError::Message(format!("应用类型不存在: {:?}", app_type)))?;
if let Some(target) = m.providers.get_mut(id) {
target.settings_config = live_after;
}
}
}
}
}
{
let manager = config
.get_manager_mut(&app_type)
.ok_or_else(|| AppError::Message(format!("应用类型不存在: {:?}", app_type)))?;
manager.current = id.to_string();
}
if let AppType::Codex = app_type {
crate::mcp::sync_enabled_to_codex(&config)?;
let cfg_text_after = crate::codex_config::read_and_validate_codex_config_text()?;
let cur_id = {
let m = config
.get_manager(&app_type)
.ok_or_else(|| AppError::Message(format!("应用类型不存在: {:?}", app_type)))?;
m.current.clone()
};
let m = config
.get_manager_mut(&app_type)
.ok_or_else(|| AppError::Message(format!("应用类型不存在: {:?}", app_type)))?;
if let Some(p) = m.providers.get_mut(&cur_id) {
if let Some(obj) = p.settings_config.as_object_mut() {
obj.insert(
"config".to_string(),
serde_json::Value::String(cfg_text_after),
);
}
}
}
log::info!("成功切换到供应商");
ProviderService::switch(&mut config, app_type, id)?;
drop(config);
state.save()?;
Ok(())
state.save()
}
#[doc(hidden)]