feat(oauth): 允许替换已失效的活跃 OAuth 账号并同步 status_snapshot

- 活跃但 token 已过期或刷新失败的重复账号视为可替换
- 清除失效标记、刷新配额时同步更新 status_snapshot.oauth
- oauth_invalid 清除接口同时识别 invalid_at 与 invalid_reason 两种标记
- 批量导入任务状态区分 created_count / replaced_count,前端据此展示新增/替换统计
- 补充重复替换场景的集成测试,用量测试等待超时从 10s 提升到 30s 以适应并行压力
This commit is contained in:
fawney19
2026-04-20 22:59:02 +08:00
parent cf7d129595
commit c5c56ff92f
15 changed files with 965 additions and 44 deletions

View File

@@ -1,5 +1,7 @@
use super::{AppState, GatewayError, LocalMutationOutcome, LocalProviderDeleteTaskState};
use crate::handlers::shared::sync_provider_key_oauth_status_snapshot;
use aether_data_contracts::repository::{candidates, global_models, provider_catalog};
use std::time::{SystemTime, UNIX_EPOCH};
impl AppState {
pub fn has_provider_catalog_data_reader(&self) -> bool {
@@ -633,10 +635,29 @@ impl AppState {
&self,
key_id: &str,
) -> Result<bool, GatewayError> {
self.data
.clear_provider_catalog_key_oauth_invalid_marker(key_id)
let Some(mut key) = self
.data
.list_provider_catalog_keys_by_ids(&[key_id.to_string()])
.await
.map_err(|err| GatewayError::Internal(err.to_string()))
.map_err(|err| GatewayError::Internal(err.to_string()))?
.into_iter()
.next()
else {
return Ok(false);
};
key.oauth_invalid_at_unix_secs = None;
key.oauth_invalid_reason = None;
key.status_snapshot =
sync_provider_key_oauth_status_snapshot(key.status_snapshot.as_ref(), &key);
key.updated_at_unix_secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.ok()
.map(|duration| duration.as_secs());
self.update_provider_catalog_key(&key)
.await
.map(|updated| updated.is_some())
}
pub(crate) fn put_provider_delete_task(&self, task: LocalProviderDeleteTaskState) {