feat(auth): Add retry logic with exponential backoff for S2A and Codex API
- Add exponential backoff with jitter to S2A OAuth submission (3 attempts) - Implement 5xx error retry mechanism in Codex API workspace selection (5 attempts) - Add 429 rate limit handling with retry support in Codex API - Improve team member processing with staggered delays to avoid concurrent conflicts - Add per-attempt proxy rotation to avoid reusing failed proxies - Enhance retry delay calculation with random jitter to prevent thundering herd - Update logging to display retry attempts and delay durations - Improve error messages with HTTP status codes and response body snippets - Refactor retry loops to use consistent exponential backoff pattern across modules
This commit is contained in:
@@ -113,9 +113,9 @@ func GenerateS2AAuthURL(s2aAPIBase, s2aAdminKey string, proxyID *int) (*S2AAuthU
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// SubmitS2AOAuth 提交 OAuth code 到 S2A 入库
|
||||
// SubmitS2AOAuth 提交 OAuth code 到 S2A 入库(带重试)
|
||||
func SubmitS2AOAuth(s2aAPIBase, s2aAdminKey, sessionID, code, name string, concurrency, priority int, groupIDs []int, proxyID *int) (*S2ACreateFromOAuthResponse, error) {
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
httpClient := &http.Client{Timeout: 30 * time.Second}
|
||||
|
||||
apiURL := s2aAPIBase + "/api/v1/admin/openai/create-from-oauth"
|
||||
|
||||
@@ -130,29 +130,50 @@ func SubmitS2AOAuth(s2aAPIBase, s2aAdminKey, sessionID, code, name string, concu
|
||||
}
|
||||
body, _ := json.Marshal(payload)
|
||||
|
||||
req, _ := http.NewRequest("POST", apiURL, bytes.NewReader(body))
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Api-Key", s2aAdminKey)
|
||||
var lastErr error
|
||||
for attempt := 0; attempt < 3; attempt++ {
|
||||
if attempt > 0 {
|
||||
time.Sleep(time.Duration(2+attempt*2) * time.Second) // 4s, 6s
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
req, _ := http.NewRequest("POST", apiURL, bytes.NewReader(body))
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Api-Key", s2aAdminKey)
|
||||
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
lastErr = fmt.Errorf("请求失败: %v", err)
|
||||
continue // 网络错误可重试
|
||||
}
|
||||
|
||||
var result S2ACreateFromOAuthResponse
|
||||
if err := json.Unmarshal(respBody, &result); err != nil {
|
||||
return nil, fmt.Errorf("解析响应失败: %v", err)
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
|
||||
// 5xx 服务端错误可重试
|
||||
if resp.StatusCode >= 500 {
|
||||
lastErr = fmt.Errorf("S2A 服务端错误 HTTP %d: %s", resp.StatusCode, string(respBody[:min(200, len(respBody))]))
|
||||
continue
|
||||
}
|
||||
|
||||
// 非 200 的其他错误不重试
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("S2A HTTP %d: %s", resp.StatusCode, string(respBody[:min(200, len(respBody))]))
|
||||
}
|
||||
|
||||
var result S2ACreateFromOAuthResponse
|
||||
if err := json.Unmarshal(respBody, &result); err != nil {
|
||||
return nil, fmt.Errorf("解析响应失败 (HTTP %d): %v, body: %s", resp.StatusCode, err, string(respBody[:min(200, len(respBody))]))
|
||||
}
|
||||
|
||||
if result.Code != 0 {
|
||||
return nil, fmt.Errorf("S2A 入库失败: %s (code=%d)", result.Message, result.Code)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
if result.Code != 0 {
|
||||
return nil, fmt.Errorf("S2A 入库失败: %s", result.Message)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
return nil, fmt.Errorf("S2A 入库失败 (重试耗尽): %v", lastErr)
|
||||
}
|
||||
|
||||
// VerifyS2AAccount 验证账号入库状态
|
||||
|
||||
Reference in New Issue
Block a user