mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-29 01:25:33 +08:00
fix(failover): patch P1-P3 reliability gaps surfaced by team review
- Forwarder buffers non-streaming bodies and primes streaming first chunk before signaling success, so body timeouts and SSE first-chunk failures route through the circuit breaker instead of being recorded as success on response-header arrival - Atomic enable-failover: switch to P1 before persisting the flag, and roll back auto-added queue entries when the switch is rejected (e.g. official providers) - Hot-reload circuit breaker config on per-app proxy config change instead of waiting for a proxy restart - FailoverToggle / FailoverQueueManager / AutoFailoverConfigPanel require proxy takeover for the active app; the backend command also rejects enabling when takeover is off - ProviderHealthBadge consumes the backend is_healthy flag instead of hardcoding the 5-failure threshold Cleanup: - impl From<&AppProxyConfig> for CircuitBreakerConfig and use it from the command layer - Collapse three identical TabsContent blocks into a single map
This commit is contained in:
@@ -86,11 +86,19 @@ pub async fn set_auto_failover_enabled(
|
||||
"[Failover] Setting auto_failover_enabled: app_type='{app_type}', enabled={enabled}"
|
||||
);
|
||||
|
||||
// 强一致语义:开启故障转移后立即切到队列 P1(并确保队列非空)
|
||||
//
|
||||
// 说明:
|
||||
// - 仅在 enabled=true 时执行“切到 P1”
|
||||
// - 若队列为空,则尝试把“当前供应商”自动加入队列作为 P1,避免用户在 UI 上陷入死锁(无法先加队列再开启)
|
||||
// 读取当前配置
|
||||
let mut config = state
|
||||
.db
|
||||
.get_proxy_config_for_app(&app_type)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
if enabled && !config.enabled {
|
||||
return Err("需要先启用该应用的代理接管,再开启故障转移".to_string());
|
||||
}
|
||||
|
||||
// 队列为空时把当前供应商自动加入作为 P1,避免用户陷入"必须先加队列才能开启"的死锁
|
||||
let mut auto_added_provider_id: Option<String> = None;
|
||||
let p1_provider_id = if enabled {
|
||||
let mut queue = state
|
||||
.db
|
||||
@@ -112,6 +120,7 @@ pub async fn set_auto_failover_enabled(
|
||||
.db
|
||||
.add_to_failover_queue(&app_type, ¤t_id)
|
||||
.map_err(|e| e.to_string())?;
|
||||
auto_added_provider_id = Some(current_id);
|
||||
|
||||
queue = state
|
||||
.db
|
||||
@@ -127,12 +136,20 @@ pub async fn set_auto_failover_enabled(
|
||||
String::new()
|
||||
};
|
||||
|
||||
// 读取当前配置
|
||||
let mut config = state
|
||||
.db
|
||||
.get_proxy_config_for_app(&app_type)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
// 开启前先切到 P1。只有切换成功后才写入 auto_failover_enabled=true,
|
||||
// 避免 P1 不可切换(例如 official provider)时留下“开关已开但目标未切”的脏状态。
|
||||
if enabled {
|
||||
if let Err(e) = state
|
||||
.proxy_service
|
||||
.switch_proxy_target(&app_type, &p1_provider_id)
|
||||
.await
|
||||
{
|
||||
if let Some(provider_id) = auto_added_provider_id {
|
||||
let _ = state.db.remove_from_failover_queue(&app_type, &provider_id);
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新 auto_failover_enabled 字段
|
||||
config.auto_failover_enabled = enabled;
|
||||
@@ -144,13 +161,7 @@ pub async fn set_auto_failover_enabled(
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// 开启后立即切到 P1:更新 is_current + 本地 settings + Live 备份(接管模式下)
|
||||
if enabled {
|
||||
state
|
||||
.proxy_service
|
||||
.switch_proxy_target(&app_type, &p1_provider_id)
|
||||
.await?;
|
||||
|
||||
// 发射 provider-switched 事件(让前端刷新当前供应商)
|
||||
let event_data = serde_json::json!({
|
||||
"appType": app_type,
|
||||
|
||||
@@ -133,9 +133,17 @@ pub async fn update_proxy_config_for_app(
|
||||
config: AppProxyConfig,
|
||||
) -> Result<(), String> {
|
||||
let db = &state.db;
|
||||
let app_type = config.app_type.clone();
|
||||
let circuit_config = CircuitBreakerConfig::from(&config);
|
||||
|
||||
db.update_proxy_config_for_app(config)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
state
|
||||
.proxy_service
|
||||
.update_circuit_breaker_config_for_app(&app_type, circuit_config)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_default_cost_multiplier_internal(
|
||||
|
||||
Reference in New Issue
Block a user