From ffaa6c4a17f7313bd1621eb4028a32bee52ac6ae Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Sat, 24 Jan 2026 17:22:15 +0800 Subject: [PATCH] =?UTF-8?q?fix(oauth):=20=E4=BF=AE=E5=A4=8D=20OAuth=20?= =?UTF-8?q?=E4=BB=A4=E7=89=8C=E5=88=B7=E6=96=B0=E6=97=B6=20missing=5Fproje?= =?UTF-8?q?ct=5Fid=20=E8=AF=AF=E6=8A=A5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题描述: 在网络波动环境下,LoadCodeAssist 临时失败会被错误地标记为 "missing_project_id" 不可重试错误,导致账户被禁用。但实际上 账户配置正确,手动刷新后即可恢复。 解决方案: 1. 为 LoadCodeAssist 增加重试机制(最多4次,指数退避) 2. 区分真正的配置缺失和临时网络故障: - 如果之前有 project_id,本次获取失败只保留旧值,不报错 - 只有从未获取过 project_id 且本次也失败时,才标记为缺失 3. 优化错误判断逻辑,避免误报 改进效果: - 提高在复杂网络环境(如家宽代理)下的鲁棒性 - 减少因临时网络波动导致的服务中断 - 保持真正配置错误的检测能力 Co-Authored-By: Claude Sonnet 4.5 --- .../service/antigravity_oauth_service.go | 55 ++++++++++++++++--- .../service/antigravity_token_refresher.go | 13 ++++- .../internal/service/token_refresh_service.go | 3 +- 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/backend/internal/service/antigravity_oauth_service.go b/backend/internal/service/antigravity_oauth_service.go index 52293cd5..3850fa57 100644 --- a/backend/internal/service/antigravity_oauth_service.go +++ b/backend/internal/service/antigravity_oauth_service.go @@ -237,21 +237,60 @@ func (s *AntigravityOAuthService) RefreshAccountToken(ctx context.Context, accou tokenInfo.Email = existingEmail } - // 每次刷新都调用 LoadCodeAssist 获取 project_id - client := antigravity.NewClient(proxyURL) - loadResp, _, err := client.LoadCodeAssist(ctx, tokenInfo.AccessToken) - if err != nil || loadResp == nil || loadResp.CloudAICompanionProject == "" { - // LoadCodeAssist 失败或返回空,保留原有 project_id,标记缺失 - existingProjectID := strings.TrimSpace(account.GetCredential("project_id")) + // 每次刷新都调用 LoadCodeAssist 获取 project_id,失败时重试 + existingProjectID := strings.TrimSpace(account.GetCredential("project_id")) + projectID, loadErr := s.loadProjectIDWithRetry(ctx, tokenInfo.AccessToken, proxyURL, 3) + + if loadErr != nil { + // LoadCodeAssist 失败,保留原有 project_id tokenInfo.ProjectID = existingProjectID - tokenInfo.ProjectIDMissing = true + // 只有从未获取过 project_id 且本次也获取失败时,才标记为真正缺失 + // 如果之前有 project_id,本次只是临时故障,不应标记为错误 + if existingProjectID == "" { + tokenInfo.ProjectIDMissing = true + } } else { - tokenInfo.ProjectID = loadResp.CloudAICompanionProject + tokenInfo.ProjectID = projectID } return tokenInfo, nil } +// loadProjectIDWithRetry 带重试机制获取 project_id +// 返回 project_id 和错误,失败时会重试指定次数 +func (s *AntigravityOAuthService) loadProjectIDWithRetry(ctx context.Context, accessToken, proxyURL string, maxRetries int) (string, error) { + var lastErr error + + for attempt := 0; attempt <= maxRetries; attempt++ { + if attempt > 0 { + // 指数退避:1s, 2s, 4s + backoff := time.Duration(1< 8*time.Second { + backoff = 8 * time.Second + } + time.Sleep(backoff) + } + + client := antigravity.NewClient(proxyURL) + loadResp, _, err := client.LoadCodeAssist(ctx, accessToken) + + if err == nil && loadResp != nil && loadResp.CloudAICompanionProject != "" { + return loadResp.CloudAICompanionProject, nil + } + + // 记录错误 + if err != nil { + lastErr = err + } else if loadResp == nil { + lastErr = fmt.Errorf("LoadCodeAssist 返回空响应") + } else { + lastErr = fmt.Errorf("LoadCodeAssist 返回空 project_id") + } + } + + return "", fmt.Errorf("获取 project_id 失败 (重试 %d 次后): %w", maxRetries, lastErr) +} + // BuildAccountCredentials 构建账户凭证 func (s *AntigravityOAuthService) BuildAccountCredentials(tokenInfo *AntigravityTokenInfo) map[string]any { creds := map[string]any{ diff --git a/backend/internal/service/antigravity_token_refresher.go b/backend/internal/service/antigravity_token_refresher.go index a07c86e6..2beb9e84 100644 --- a/backend/internal/service/antigravity_token_refresher.go +++ b/backend/internal/service/antigravity_token_refresher.go @@ -3,6 +3,7 @@ package service import ( "context" "fmt" + "log" "time" ) @@ -61,9 +62,17 @@ func (r *AntigravityTokenRefresher) Refresh(ctx context.Context, account *Accoun } } - // 如果 project_id 获取失败,返回 credentials 但同时返回错误让账户被标记 + // 如果 project_id 获取失败但之前有 project_id,不返回错误(只是临时网络故障) + // 只有真正缺失 project_id(从未获取过)时才返回错误 if tokenInfo.ProjectIDMissing { - return newCredentials, fmt.Errorf("missing_project_id: 账户缺少project id,可能无法使用Antigravity") + // 检查是否保留了旧的 project_id + if tokenInfo.ProjectID != "" { + // 有旧的 project_id,只是本次获取失败,记录警告但不返回错误 + log.Printf("[AntigravityTokenRefresher] Account %d: LoadCodeAssist 临时失败,保留旧 project_id", account.ID) + } else { + // 真正缺失 project_id,返回错误 + return newCredentials, fmt.Errorf("missing_project_id: 账户缺少project id,可能无法使用Antigravity") + } } return newCredentials, nil diff --git a/backend/internal/service/token_refresh_service.go b/backend/internal/service/token_refresh_service.go index 7364bd33..6ef92bbf 100644 --- a/backend/internal/service/token_refresh_service.go +++ b/backend/internal/service/token_refresh_service.go @@ -237,7 +237,8 @@ func (s *TokenRefreshService) refreshWithRetry(ctx context.Context, account *Acc } // isNonRetryableRefreshError 判断是否为不可重试的刷新错误 -// 这些错误通常表示凭证已失效,需要用户重新授权 +// 这些错误通常表示凭证已失效或配置确实缺失,需要用户重新授权 +// 注意:missing_project_id 错误只在真正缺失(从未获取过)时返回,临时获取失败不会返回此错误 func isNonRetryableRefreshError(err error) bool { if err == nil { return false