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