fix: Sub2ApiWebsiteClient.list_accounts 支持分页拉取,避免只拿到默认第一页 20 条导致误建重复账号

This commit is contained in:
SmartUp Developer
2026-07-02 11:04:46 +08:00
parent fe3f9f4a4c
commit 00af34c487
2 changed files with 131 additions and 11 deletions
+77 -11
View File
@@ -334,17 +334,83 @@ class Sub2ApiWebsiteClient:
return None return None
def list_accounts(self, endpoint: str = "/accounts") -> list[dict[str, Any]] | None: def list_accounts(self, endpoint: str = "/accounts") -> list[dict[str, Any]] | None:
"""拉取远端账号列表。成功返回账号 dict 列表,失败返回 None。""" """拉取远端账号列表。支持分页拉取,成功返回账号 dict 列表,失败返回 None。"""
try: base_path = endpoint
resp = self._request("GET", endpoint) query_params = {}
except Exception: if "?" in endpoint:
logger.warning("account list fetch failed for %s", endpoint, exc_info=True) base_path, query_str = endpoint.split("?", 1)
return None for part in query_str.split("&"):
items = self._unwrap_list(resp) if "=" in part:
if items is None: k, v = part.split("=", 1)
logger.warning("account list unexpected format for %s", endpoint) query_params[k] = v
return None
return [item for item in items if isinstance(item, dict)] page_size = 100
if "page_size" in query_params:
try:
page_size = int(query_params["page_size"])
except ValueError:
pass
page = 1
all_items = []
while True:
params = dict(query_params)
params["page"] = str(page)
params["page_size"] = str(page_size)
qp_str = "&".join(f"{k}={v}" for k, v in params.items())
current_path = f"{base_path}?{qp_str}"
try:
resp = self._request("GET", current_path)
except Exception:
logger.warning("account list fetch failed for %s", current_path, exc_info=True)
return None
items = self._unwrap_list(resp)
if items is None:
logger.warning("account list unexpected format for %s", current_path)
return None
all_items.extend([item for item in items if isinstance(item, dict)])
if isinstance(resp, dict):
# 尝试从顶层或嵌套的 data 字典中提取分页字段
meta = resp
data_val = resp.get("data")
if isinstance(data_val, dict) and any(k in data_val for k in ("pages", "total", "page_size")):
meta = data_val
pages = meta.get("pages")
if pages is not None:
try:
pages_val = int(pages)
if page >= pages_val:
break
page += 1
continue
except (ValueError, TypeError):
pass
total = meta.get("total")
current_page_size = meta.get("page_size")
if total is not None and current_page_size is not None:
try:
total_val = int(total)
page_size_val = int(current_page_size)
import math
calculated_pages = math.ceil(total_val / page_size_val)
if page >= calculated_pages:
break
page += 1
continue
except (ValueError, TypeError):
pass
break
return all_items
def _get_account_ids(self, endpoint: str = "/accounts") -> set[str] | None: def _get_account_ids(self, endpoint: str = "/accounts") -> set[str] | None:
"""拉取远端账号列表。成功返回 ID 集合(可能为空),解析失败返回 None。""" """拉取远端账号列表。成功返回 ID 集合(可能为空),解析失败返回 None。"""
+54
View File
@@ -272,3 +272,57 @@ def test_get_groups_all_404_no_raw_url(monkeypatch):
assert "接口不存在" in msg assert "接口不存在" in msg
assert "http://" not in msg assert "http://" not in msg
assert "MDN" not in msg assert "MDN" not in msg
def test_list_accounts_pagination():
from app.services.website_client import Sub2ApiWebsiteClient
requests_called = []
def handler(req: httpx.Request) -> httpx.Response:
requests_called.append(req)
url_str = str(req.url)
if "page=1" in url_str:
return httpx.Response(200, json={
"total": 3,
"page": 1,
"page_size": 2,
"pages": 2,
"data": [
{"id": "a1", "name": "acc1"},
{"id": "a2", "name": "acc2"},
]
}, request=req)
elif "page=2" in url_str:
return httpx.Response(200, json={
"total": 3,
"page": 2,
"page_size": 2,
"pages": 2,
"data": [
{"id": "a3", "name": "acc3"},
]
}, request=req)
return httpx.Response(404, json={"error": "Not Found"}, request=req)
client = Sub2ApiWebsiteClient(
base_url="https://target.example",
api_prefix="/api/v1/admin",
auth_type="api_key",
auth_config={"key": "admin-key"},
)
client._client = httpx.Client(transport=httpx.MockTransport(handler))
try:
accounts = client.list_accounts("/accounts")
assert accounts == [
{"id": "a1", "name": "acc1"},
{"id": "a2", "name": "acc2"},
{"id": "a3", "name": "acc3"},
]
assert len(requests_called) == 2
assert "page_size=100" in str(requests_called[0].url)
assert "page=1" in str(requests_called[0].url)
assert "page=2" in str(requests_called[1].url)
finally:
client.close()