diff --git a/src-tauri/src/commands/proxy.rs b/src-tauri/src/commands/proxy.rs index 37be402cf..0e78bf7a4 100644 --- a/src-tauri/src/commands/proxy.rs +++ b/src-tauri/src/commands/proxy.rs @@ -184,17 +184,18 @@ pub async fn reset_circuit_breaker( .await?; // 3. 检查是否应该切回优先级更高的供应商(从 proxy_config 表读取) - let auto_failover_enabled = match db.get_proxy_config_for_app(&app_type).await { - Ok(config) => config.auto_failover_enabled, + // 只有当该应用已被代理接管(enabled=true)且开启了自动故障转移时才执行 + 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 for auto_failover_enabled: {e}, defaulting to disabled" + "[{app_type}] Failed to read proxy_config: {e}, defaulting to disabled" ); - false + (false, false) } }; - if auto_failover_enabled && state.proxy_service.is_running().await { + if app_enabled && auto_failover_enabled && state.proxy_service.is_running().await { // 获取当前供应商 ID let current_id = db .get_current_provider(&app_type) diff --git a/src-tauri/src/proxy/failover_switch.rs b/src-tauri/src/proxy/failover_switch.rs index b1ac5b746..73e2e5948 100644 --- a/src-tauri/src/proxy/failover_switch.rs +++ b/src-tauri/src/proxy/failover_switch.rs @@ -81,6 +81,25 @@ impl FailoverSwitchManager { provider_id: &str, provider_name: &str, ) -> Result { + // 检查该应用是否已被代理接管(enabled=true) + // 只有被接管的应用才允许执行故障转移切换 + 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},跳过切换" + ); + return Ok(false); + } + }; + + if !app_enabled { + log::info!( + "[Failover] {app_type} 未被代理接管(enabled=false),跳过切换" + ); + return Ok(false); + } + log::info!("[Failover] 开始切换供应商: {app_type} -> {provider_name} ({provider_id})"); // 1. 更新数据库 is_current diff --git a/src-tauri/src/proxy/forwarder.rs b/src-tauri/src/proxy/forwarder.rs index 68122e12f..88b8d3a6d 100644 --- a/src-tauri/src/proxy/forwarder.rs +++ b/src-tauri/src/proxy/forwarder.rs @@ -1,6 +1,6 @@ //! 请求转发器 //! -//! 负责将请求转发到上游Provider,支持重试和故障转移 +//! 负责将请求转发到上游Provider,支持故障转移 use super::{ error::*, @@ -31,8 +31,6 @@ pub struct RequestForwarder { client: Client, /// 共享的 ProviderRouter(持有熔断器状态) router: Arc, - /// 单个 Provider 内的最大重试次数 - max_retries: u8, status: Arc>, current_providers: Arc>>, /// 故障转移切换管理器 @@ -48,7 +46,6 @@ impl RequestForwarder { pub fn new( router: Arc, non_streaming_timeout: u64, - max_retries: u8, status: Arc>, current_providers: Arc>>, failover_manager: Arc, @@ -77,7 +74,6 @@ impl RequestForwarder { Self { client, router, - max_retries, status, current_providers, failover_manager, @@ -86,59 +82,6 @@ impl RequestForwarder { } } - /// 对单个 Provider 执行请求(带重试) - /// - /// 在同一个 Provider 上最多重试 max_retries 次,使用指数退避 - async fn forward_with_provider_retry( - &self, - provider: &Provider, - endpoint: &str, - body: &Value, - headers: &axum::http::HeaderMap, - adapter: &dyn ProviderAdapter, - ) -> Result { - let mut last_error = None; - - for attempt in 0..=self.max_retries { - if attempt > 0 { - // 指数退避:100ms, 200ms, 400ms, ... - let delay_ms = 100 * 2u64.pow(attempt as u32 - 1); - log::info!( - "[{}] 重试第 {}/{} 次(等待 {}ms)", - adapter.name(), - attempt, - self.max_retries, - delay_ms - ); - tokio::time::sleep(Duration::from_millis(delay_ms)).await; - } - - match self - .forward(provider, endpoint, body, headers, adapter) - .await - { - Ok(response) => return Ok(response), - Err(e) => { - // 只有“同一 Provider 内可重试”的错误才继续重试 - if !self.should_retry_same_provider(&e) { - return Err(e); - } - - log::debug!( - "[{}] Provider {} 第 {} 次请求失败: {}", - adapter.name(), - provider.name, - attempt + 1, - e - ); - last_error = Some(e); - } - } - } - - Err(last_error.unwrap_or(ProxyError::MaxRetriesExceeded)) - } - /// 转发请求(带故障转移) /// /// # Arguments @@ -224,9 +167,9 @@ impl RequestForwarder { let start = Instant::now(); - // 转发请求(带单 Provider 内重试) + // 转发请求(每个 Provider 只尝试一次,重试由客户端控制) match self - .forward_with_provider_retry(provider, endpoint, &body, &headers, adapter.as_ref()) + .forward(provider, endpoint, &body, &headers, adapter.as_ref()) .await { Ok(response) => { @@ -551,25 +494,6 @@ impl RequestForwarder { } } - /// 分类ProxyError - /// - /// 决定哪些错误应该触发故障转移到下一个 Provider - /// - /// 设计原则:既然用户配置了多个供应商,就应该让所有供应商都尝试一遍。 - /// 只有明确是客户端中断的情况才不重试。 - fn should_retry_same_provider(&self, error: &ProxyError) -> bool { - match error { - // 网络类错误:短暂抖动时同一 Provider 内重试有意义 - ProxyError::Timeout(_) => true, - ProxyError::ForwardFailed(_) => true, - // 上游 HTTP 错误:只对“可能瞬态”的状态码做同 Provider 重试(其余交给 failover) - ProxyError::UpstreamError { status, .. } => { - *status == 408 || *status == 429 || *status >= 500 - } - _ => false, - } - } - fn categorize_proxy_error(&self, error: &ProxyError) -> ErrorCategory { match error { // 网络和上游错误:都应该尝试下一个供应商 @@ -585,7 +509,6 @@ impl RequestForwarder { ProxyError::TransformError(_) => ErrorCategory::Retryable, ProxyError::AuthError(_) => ErrorCategory::Retryable, ProxyError::StreamIdleTimeout(_) => ErrorCategory::Retryable, - ProxyError::MaxRetriesExceeded => ErrorCategory::Retryable, // 无可用供应商:所有供应商都试过了,无法重试 ProxyError::NoAvailableProvider => ErrorCategory::NonRetryable, // 其他错误(数据库/内部错误等):不是换供应商能解决的问题 diff --git a/src-tauri/src/proxy/handler_context.rs b/src-tauri/src/proxy/handler_context.rs index 71a726ef7..5c0548c02 100644 --- a/src-tauri/src/proxy/handler_context.rs +++ b/src-tauri/src/proxy/handler_context.rs @@ -150,31 +150,29 @@ impl RequestContext { /// 使用共享的 ProviderRouter,确保熔断器状态跨请求保持 /// /// 配置生效规则: - /// - 故障转移开启:超时配置和重试次数正常生效(0 表示禁用超时/不重试直接故障转移) - /// - 故障转移关闭:超时配置和重试次数不生效(全部传入 0) + /// - 故障转移开启:超时配置正常生效(0 表示禁用超时) + /// - 故障转移关闭:超时配置不生效(全部传入 0) pub fn create_forwarder(&self, state: &ProxyState) -> RequestForwarder { - let (non_streaming_timeout, max_retries, first_byte_timeout, idle_timeout) = + let (non_streaming_timeout, first_byte_timeout, idle_timeout) = if self.app_config.auto_failover_enabled { - // 故障转移开启:使用配置的值(0 = 禁用超时 / 不重试直接故障转移) + // 故障转移开启:使用配置的值(0 = 禁用超时) ( self.app_config.non_streaming_timeout as u64, - self.app_config.max_retries as u8, self.app_config.streaming_first_byte_timeout as u64, self.app_config.streaming_idle_timeout as u64, ) } else { - // 故障转移关闭:不启用超时和重试配置 + // 故障转移关闭:不启用超时配置 log::info!( - "[{}] Failover disabled, timeout and retry configs are bypassed", + "[{}] Failover disabled, timeout configs are bypassed", self.tag ); - (0, 0, 0, 0) + (0, 0, 0) }; RequestForwarder::new( state.provider_router.clone(), non_streaming_timeout, - max_retries, state.status.clone(), state.current_providers.clone(), state.failover_manager.clone(),