mirror of
https://github.com/zc-zhangchen/any-auto-register.git
synced 2026-05-17 11:16:47 +08:00
fix(chatgpt): 为远端状态同步增加重试机制
This commit is contained in:
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import json
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Optional
|
||||
|
||||
@@ -11,6 +12,8 @@ from platforms.chatgpt.status_probe import CODEX_USER_AGENT, extract_chatgpt_acc
|
||||
from services.chatgpt_account_state import is_account_deactivated_message
|
||||
|
||||
DEFAULT_CLIPROXYAPI_BASE_URL = "http://127.0.0.1:8317"
|
||||
SYNC_RETRY_ATTEMPTS = 3
|
||||
SYNC_RETRY_DELAY_SECONDS = 0.4
|
||||
|
||||
|
||||
def _utcnow_iso() -> str:
|
||||
@@ -124,6 +127,35 @@ def _request_json(method: str, path: str, *, api_url: str | None = None, api_key
|
||||
return response.text
|
||||
|
||||
|
||||
def _is_retryable_sync_error(exc: Exception) -> bool:
|
||||
text = str(exc or "").strip().lower()
|
||||
if not text:
|
||||
return False
|
||||
markers = (
|
||||
"无法连接",
|
||||
"请求超时",
|
||||
"connection",
|
||||
"timeout",
|
||||
"timed out",
|
||||
)
|
||||
return any(marker in text for marker in markers)
|
||||
|
||||
|
||||
def _retry_sync_call(func, *, attempts: int = SYNC_RETRY_ATTEMPTS):
|
||||
last_error = None
|
||||
for attempt in range(1, max(1, attempts) + 1):
|
||||
try:
|
||||
return func()
|
||||
except Exception as exc:
|
||||
last_error = exc
|
||||
if attempt >= attempts or not _is_retryable_sync_error(exc):
|
||||
raise
|
||||
time.sleep(SYNC_RETRY_DELAY_SECONDS)
|
||||
if last_error is not None:
|
||||
raise last_error
|
||||
raise RuntimeError("sync retry failed without captured error")
|
||||
|
||||
|
||||
def list_auth_files(*, api_url: str | None = None, api_key: str | None = None) -> list[dict[str, Any]]:
|
||||
data = _request_json("GET", "/v0/management/auth-files", api_url=api_url, api_key=api_key)
|
||||
files = data.get("files", []) if isinstance(data, dict) else []
|
||||
@@ -240,7 +272,7 @@ def sync_chatgpt_cliproxyapi_status(
|
||||
) -> dict[str, Any]:
|
||||
synced_at = _utcnow_iso()
|
||||
try:
|
||||
files = list_auth_files(api_url=api_url, api_key=api_key)
|
||||
files = _retry_sync_call(lambda: list_auth_files(api_url=api_url, api_key=api_key))
|
||||
except Exception as exc:
|
||||
return {
|
||||
"uploaded": False,
|
||||
@@ -279,7 +311,11 @@ def sync_chatgpt_cliproxyapi_status(
|
||||
"chatgpt_subscription_active_until": str(((matched.get("id_token") or {}).get("chatgpt_subscription_active_until") if isinstance(matched.get("id_token"), dict) else "") or "").strip(),
|
||||
}
|
||||
try:
|
||||
remote.update(_probe_remote_auth(remote["auth_index"], account_id, api_url=api_url, api_key=api_key))
|
||||
remote.update(
|
||||
_retry_sync_call(
|
||||
lambda: _probe_remote_auth(remote["auth_index"], account_id, api_url=api_url, api_key=api_key)
|
||||
)
|
||||
)
|
||||
except Exception as exc:
|
||||
remote.update(
|
||||
{
|
||||
|
||||
@@ -25,6 +25,46 @@ class CliproxyapiSyncTests(unittest.TestCase):
|
||||
self.assertEqual(result["remote_state"], "unreachable")
|
||||
self.assertIn("无法连接", result["message"])
|
||||
|
||||
def test_sync_retries_list_auth_files_until_success(self):
|
||||
account = DummyAccount(email="demo@example.com", user_id="acct-123")
|
||||
auth_files = [
|
||||
{
|
||||
"name": "demo@example.com.json",
|
||||
"provider": "codex",
|
||||
"email": "demo@example.com",
|
||||
"auth_index": "auth-001",
|
||||
"status": "active",
|
||||
"status_message": "",
|
||||
"unavailable": False,
|
||||
}
|
||||
]
|
||||
|
||||
with mock.patch(
|
||||
"services.cliproxyapi_sync.list_auth_files",
|
||||
side_effect=[
|
||||
RuntimeError("CLIProxyAPI 无法连接,请确认服务已启动或 API URL 是否正确:http://127.0.0.1:8317"),
|
||||
RuntimeError("CLIProxyAPI 请求超时:http://127.0.0.1:8317"),
|
||||
auth_files,
|
||||
],
|
||||
) as list_mock:
|
||||
with mock.patch(
|
||||
"services.cliproxyapi_sync._probe_remote_auth",
|
||||
return_value={
|
||||
"last_probe_at": "2026-03-31T00:00:00Z",
|
||||
"last_probe_status_code": 200,
|
||||
"last_probe_error_code": "",
|
||||
"last_probe_message": "ok",
|
||||
"remote_state": "usable",
|
||||
},
|
||||
):
|
||||
with mock.patch("services.cliproxyapi_sync.time.sleep") as sleep_mock:
|
||||
result = sync_chatgpt_cliproxyapi_status(account, api_url="http://127.0.0.1:8317", api_key="demo")
|
||||
|
||||
self.assertTrue(result["uploaded"])
|
||||
self.assertEqual(result["remote_state"], "usable")
|
||||
self.assertEqual(list_mock.call_count, 3)
|
||||
self.assertEqual(sleep_mock.call_count, 2)
|
||||
|
||||
def test_sync_returns_not_found_when_remote_auth_missing(self):
|
||||
account = DummyAccount()
|
||||
|
||||
@@ -97,6 +137,43 @@ class CliproxyapiSyncTests(unittest.TestCase):
|
||||
self.assertEqual(result["last_probe_error_code"], "account_deactivated")
|
||||
self.assertEqual(result["remote_state"], "account_deactivated")
|
||||
|
||||
def test_sync_retries_remote_probe_until_success(self):
|
||||
account = DummyAccount(email="demo@example.com", user_id="acct-123")
|
||||
auth_files = [
|
||||
{
|
||||
"name": "demo@example.com.json",
|
||||
"provider": "codex",
|
||||
"email": "demo@example.com",
|
||||
"auth_index": "auth-001",
|
||||
"status": "active",
|
||||
"status_message": "",
|
||||
"unavailable": False,
|
||||
}
|
||||
]
|
||||
|
||||
with mock.patch("services.cliproxyapi_sync.list_auth_files", return_value=auth_files):
|
||||
with mock.patch(
|
||||
"services.cliproxyapi_sync._probe_remote_auth",
|
||||
side_effect=[
|
||||
RuntimeError("CLIProxyAPI 请求超时:http://127.0.0.1:8317"),
|
||||
RuntimeError("CLIProxyAPI 无法连接,请确认服务已启动或 API URL 是否正确:http://127.0.0.1:8317"),
|
||||
{
|
||||
"last_probe_at": "2026-03-31T00:00:00Z",
|
||||
"last_probe_status_code": 200,
|
||||
"last_probe_error_code": "",
|
||||
"last_probe_message": "ok",
|
||||
"remote_state": "usable",
|
||||
},
|
||||
],
|
||||
) as probe_mock:
|
||||
with mock.patch("services.cliproxyapi_sync.time.sleep") as sleep_mock:
|
||||
result = sync_chatgpt_cliproxyapi_status(account, api_url="http://127.0.0.1:8317", api_key="demo")
|
||||
|
||||
self.assertTrue(result["uploaded"])
|
||||
self.assertEqual(result["remote_state"], "usable")
|
||||
self.assertEqual(probe_mock.call_count, 3)
|
||||
self.assertEqual(sleep_mock.call_count, 2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user