fix(relay,store): 审计修复 — 自动恢复可达化 + 类型化错误 + 全路径重连
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
C1: mark_key_429 设 is_active=FALSE,使 select_best_key 自动恢复 路径真正可达。之前 429 只设 cooldown_until,恢复代码为死代码。 H1+H2: 重试查询补全 debug 日志(RPM/TPM 跳过、解密失败)+ 修复 fallthrough 错误信息(RateLimited 而非 NotFound)。 H3+H4+M3+M4+M5: agentStore.ts 提取 classifyAgentError() 类型化错误 分类,覆盖 502/503/401/403/429/500,统一 createClone/ createFromTemplate/updateClone/deleteClone 错误处理,不再泄露原始 错误详情。所有 catch 块添加 log.error。 H5+H6: auth.ts 提取 triggerReconnect() 共享函数,login/loginWithTotp/ restoreSession 三处统一调用。状态检查改为仅 'disconnected' 时触发, 避免 connecting/reconnecting 状态下并发 connect。 M1: toggle_key_active(true) 同步清除 cooldown_until,防止管理员 激活后 key 仍被 cooldown 过滤不可见。
This commit is contained in:
@@ -201,14 +201,23 @@ pub async fn select_best_key(db: &PgPool, provider_id: &str, enc_key: &[u8; 32])
|
||||
|
||||
for (id, key_value, _priority, max_rpm, max_tpm, req_count, token_count) in &retry_rows {
|
||||
if let Some(rpm_limit) = max_rpm {
|
||||
if *rpm_limit > 0 && req_count.unwrap_or(0) >= *rpm_limit { continue; }
|
||||
if *rpm_limit > 0 && req_count.unwrap_or(0) >= *rpm_limit {
|
||||
tracing::debug!("[retry] Reactivated key {} hit RPM limit ({}/{})", id, req_count.unwrap_or(0), rpm_limit);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(tpm_limit) = max_tpm {
|
||||
if *tpm_limit > 0 && token_count.unwrap_or(0) >= *tpm_limit { continue; }
|
||||
if *tpm_limit > 0 && token_count.unwrap_or(0) >= *tpm_limit {
|
||||
tracing::debug!("[retry] Reactivated key {} hit TPM limit ({}/{})", id, token_count.unwrap_or(0), tpm_limit);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let decrypted_kv = match decrypt_key_value(key_value, enc_key) {
|
||||
Ok(v) => v,
|
||||
Err(_) => continue,
|
||||
Err(e) => {
|
||||
tracing::warn!("[retry] Reactivated key {} decryption failed: {}", id, e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let selection = KeySelection {
|
||||
key: PoolKey { id: id.clone(), key_value: decrypted_kv, priority: *_priority, max_rpm: *max_rpm, max_tpm: *max_tpm },
|
||||
@@ -220,6 +229,12 @@ pub async fn select_best_key(db: &PgPool, provider_id: &str, enc_key: &[u8; 32])
|
||||
});
|
||||
return Ok(selection);
|
||||
}
|
||||
|
||||
// 所有恢复的 Key 仍被 RPM/TPM 限制或解密失败
|
||||
tracing::warn!("Provider {} 恢复的 Key 全部不可用(RPM/TPM 超限或解密失败)", provider_id);
|
||||
return Err(SaasError::RateLimited(
|
||||
format!("Provider {} 恢复的 Key 仍在限流中,请稍后重试", provider_id)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,14 +301,14 @@ pub async fn mark_key_429(
|
||||
let now = chrono::Utc::now();
|
||||
|
||||
sqlx::query(
|
||||
"UPDATE provider_keys SET last_429_at = $1, cooldown_until = $2, updated_at = $3
|
||||
"UPDATE provider_keys SET last_429_at = $1, cooldown_until = $2, is_active = FALSE, updated_at = $3
|
||||
WHERE id = $4"
|
||||
)
|
||||
.bind(&now).bind(&cooldown).bind(&now).bind(key_id)
|
||||
.execute(db).await?;
|
||||
|
||||
tracing::warn!(
|
||||
"Key {} 收到 429,冷却至 {}",
|
||||
"Key {} 收到 429,标记 is_active=FALSE,冷却至 {}",
|
||||
key_id,
|
||||
cooldown
|
||||
);
|
||||
@@ -372,9 +387,16 @@ pub async fn toggle_key_active(
|
||||
active: bool,
|
||||
) -> SaasResult<()> {
|
||||
let now = chrono::Utc::now();
|
||||
sqlx::query(
|
||||
"UPDATE provider_keys SET is_active = $1, updated_at = $2 WHERE id = $3"
|
||||
).bind(active).bind(&now).bind(key_id).execute(db).await?;
|
||||
// When activating, clear cooldown so the key is immediately selectable
|
||||
if active {
|
||||
sqlx::query(
|
||||
"UPDATE provider_keys SET is_active = $1, cooldown_until = NULL, updated_at = $2 WHERE id = $3"
|
||||
).bind(active).bind(&now).bind(key_id).execute(db).await?;
|
||||
} else {
|
||||
sqlx::query(
|
||||
"UPDATE provider_keys SET is_active = $1, updated_at = $2 WHERE id = $3"
|
||||
).bind(active).bind(&now).bind(key_id).execute(db).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user