From 52199f39c123fe171e21d08fa931b4ebd10d73ea Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Wed, 31 Dec 2025 14:37:33 +0800 Subject: [PATCH] refactor(proxy): distinguish circuit-open from no-provider errors --- src-tauri/src/commands/proxy.rs | 4 +--- src-tauri/src/error.rs | 4 ++++ src-tauri/src/proxy/error.rs | 12 ++++++++++++ src-tauri/src/proxy/error_mapper.rs | 8 ++++++++ src-tauri/src/proxy/failover_switch.rs | 8 ++------ src-tauri/src/proxy/handler_context.rs | 8 +++++++- src-tauri/src/proxy/provider_router.rs | 26 +++++++++++++++----------- 7 files changed, 49 insertions(+), 21 deletions(-) diff --git a/src-tauri/src/commands/proxy.rs b/src-tauri/src/commands/proxy.rs index 0e78bf7a4..56f587842 100644 --- a/src-tauri/src/commands/proxy.rs +++ b/src-tauri/src/commands/proxy.rs @@ -188,9 +188,7 @@ pub async fn reset_circuit_breaker( let (app_enabled, auto_failover_enabled) = match db.get_proxy_config_for_app(&app_type).await { Ok(config) => (config.enabled, config.auto_failover_enabled), Err(e) => { - log::error!( - "[{app_type}] Failed to read proxy_config: {e}, defaulting to disabled" - ); + log::error!("[{app_type}] Failed to read proxy_config: {e}, defaulting to disabled"); (false, false) } }; diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs index e9eafd35b..9d8c622f1 100644 --- a/src-tauri/src/error.rs +++ b/src-tauri/src/error.rs @@ -52,6 +52,10 @@ pub enum AppError { }, #[error("数据库错误: {0}")] Database(String), + #[error("所有供应商已熔断,无可用渠道")] + AllProvidersCircuitOpen, + #[error("未配置供应商")] + NoProvidersConfigured, } impl AppError { diff --git a/src-tauri/src/proxy/error.rs b/src-tauri/src/proxy/error.rs index 316ffdc3f..1b0531a3a 100644 --- a/src-tauri/src/proxy/error.rs +++ b/src-tauri/src/proxy/error.rs @@ -23,6 +23,12 @@ pub enum ProxyError { #[error("无可用的Provider")] NoAvailableProvider, + #[error("所有供应商已熔断,无可用渠道")] + AllProvidersCircuitOpen, + + #[error("未配置供应商")] + NoProvidersConfigured, + #[allow(dead_code)] #[error("Provider不健康: {0}")] ProviderUnhealthy(String), @@ -111,6 +117,12 @@ impl IntoResponse for ProxyError { ProxyError::NoAvailableProvider => { (StatusCode::SERVICE_UNAVAILABLE, self.to_string()) } + ProxyError::AllProvidersCircuitOpen => { + (StatusCode::SERVICE_UNAVAILABLE, self.to_string()) + } + ProxyError::NoProvidersConfigured => { + (StatusCode::SERVICE_UNAVAILABLE, self.to_string()) + } ProxyError::ProviderUnhealthy(_) => { (StatusCode::SERVICE_UNAVAILABLE, self.to_string()) } diff --git a/src-tauri/src/proxy/error_mapper.rs b/src-tauri/src/proxy/error_mapper.rs index b8b44e9a3..f98c9608a 100644 --- a/src-tauri/src/proxy/error_mapper.rs +++ b/src-tauri/src/proxy/error_mapper.rs @@ -27,6 +27,12 @@ pub fn map_proxy_error_to_status(error: &ProxyError) -> u16 { // 无可用 Provider:503 Service Unavailable ProxyError::NoAvailableProvider => 503, + // 所有供应商已熔断:503 Service Unavailable + ProxyError::AllProvidersCircuitOpen => 503, + + // 未配置供应商:503 Service Unavailable + ProxyError::NoProvidersConfigured => 503, + // 重试耗尽:503 Service Unavailable ProxyError::MaxRetriesExceeded => 503, @@ -57,6 +63,8 @@ pub fn get_error_message(error: &ProxyError) -> String { ProxyError::Timeout(msg) => format!("请求超时: {msg}"), ProxyError::ForwardFailed(msg) => format!("转发失败: {msg}"), ProxyError::NoAvailableProvider => "无可用 Provider".to_string(), + ProxyError::AllProvidersCircuitOpen => "所有供应商已熔断,无可用渠道".to_string(), + ProxyError::NoProvidersConfigured => "未配置供应商".to_string(), ProxyError::MaxRetriesExceeded => "所有 Provider 都失败,重试耗尽".to_string(), ProxyError::ProviderUnhealthy(msg) => format!("Provider 不健康: {msg}"), ProxyError::DatabaseError(msg) => format!("数据库错误: {msg}"), diff --git a/src-tauri/src/proxy/failover_switch.rs b/src-tauri/src/proxy/failover_switch.rs index 73e2e5948..e0bb8a00d 100644 --- a/src-tauri/src/proxy/failover_switch.rs +++ b/src-tauri/src/proxy/failover_switch.rs @@ -86,17 +86,13 @@ impl FailoverSwitchManager { let app_enabled = match self.db.get_proxy_config_for_app(app_type).await { Ok(config) => config.enabled, Err(e) => { - log::warn!( - "[Failover] 无法读取 {app_type} 配置: {e},跳过切换" - ); + log::warn!("[Failover] 无法读取 {app_type} 配置: {e},跳过切换"); return Ok(false); } }; if !app_enabled { - log::info!( - "[Failover] {app_type} 未被代理接管(enabled=false),跳过切换" - ); + log::info!("[Failover] {app_type} 未被代理接管(enabled=false),跳过切换"); return Ok(false); } diff --git a/src-tauri/src/proxy/handler_context.rs b/src-tauri/src/proxy/handler_context.rs index 5c0548c02..724618a50 100644 --- a/src-tauri/src/proxy/handler_context.rs +++ b/src-tauri/src/proxy/handler_context.rs @@ -95,7 +95,13 @@ impl RequestContext { .provider_router .select_providers(app_type_str) .await - .map_err(|e| ProxyError::DatabaseError(e.to_string()))?; + .map_err(|e| match e { + crate::error::AppError::AllProvidersCircuitOpen => { + ProxyError::AllProvidersCircuitOpen + } + crate::error::AppError::NoProvidersConfigured => ProxyError::NoProvidersConfigured, + _ => ProxyError::DatabaseError(e.to_string()), + })?; let provider = providers .first() diff --git a/src-tauri/src/proxy/provider_router.rs b/src-tauri/src/proxy/provider_router.rs index 0a344beb9..12373587e 100644 --- a/src-tauri/src/proxy/provider_router.rs +++ b/src-tauri/src/proxy/provider_router.rs @@ -34,6 +34,8 @@ impl ProviderRouter { /// - 故障转移开启时:完全按照故障转移队列顺序返回,忽略当前供应商设置 pub async fn select_providers(&self, app_type: &str) -> Result, AppError> { let mut result = Vec::new(); + let mut total_providers = 0usize; + let mut circuit_open_count = 0usize; // 检查该应用的自动故障转移开关是否开启(从 proxy_config 表读取) let auto_failover_enabled = match self.db.get_proxy_config_for_app(app_type).await { @@ -53,15 +55,10 @@ impl ProviderRouter { if auto_failover_enabled { // 故障转移开启:使用 in_failover_queue 标记的供应商,按 sort_index 排序 let failover_providers = self.db.get_failover_providers(app_type)?; - log::debug!( - "[{}] Found {} failover queue provider(s)", - app_type, - failover_providers.len() - ); + total_providers = failover_providers.len(); + log::debug!("[{app_type}] Found {total_providers} failover queue provider(s)"); log::info!( - "[{}] Failover enabled, using queue order ({} items)", - app_type, - failover_providers.len() + "[{app_type}] Failover enabled, using queue order ({total_providers} items)" ); for provider in failover_providers { @@ -87,6 +84,7 @@ impl ProviderRouter { ); result.push(provider); } else { + circuit_open_count += 1; log::debug!( "[{}] Queue provider {} circuit breaker open (state: {:?}), skipping", app_type, @@ -108,6 +106,7 @@ impl ProviderRouter { current.name, current.id ); + total_providers = 1; result.push(current); } else { log::debug!( @@ -120,9 +119,14 @@ impl ProviderRouter { } if result.is_empty() { - return Err(AppError::Config(format!( - "No available provider for {app_type} (all circuit breakers open or no providers configured)" - ))); + // 区分两种情况:全部熔断 vs 未配置供应商 + if total_providers > 0 && circuit_open_count == total_providers { + log::warn!("[{app_type}] 所有 {total_providers} 个供应商均已熔断,无可用渠道"); + return Err(AppError::AllProvidersCircuitOpen); + } else { + log::warn!("[{app_type}] 未配置供应商或故障转移队列为空"); + return Err(AppError::NoProvidersConfigured); + } } log::info!(