fix(nox-api): address duplicate creation risks, pending upgrading issues, cache warm error handling, test speedups, and frontend display support for pending keys

This commit is contained in:
liumangmang
2026-06-30 17:25:18 +08:00
parent 4fa6a79c5a
commit edc00444dc
4 changed files with 196 additions and 22 deletions
+24 -18
View File
@@ -426,8 +426,10 @@ def _ensure_group_key(
existing = client.find_smartup_group_key(gid, stable_name, prefix)
if existing is not None:
found_by_name = stable_name
except Exception:
existing = None
except Exception as exc:
# 远端查询失败,不要假定不存在,直接抛出异常,让外层捕获并返回 failed
raise UpstreamError(f"查询远端 Key 失败: {exc}") from exc
if existing:
key_id = str(existing.get("id") or "")
key_value = _extract_plaintext_key(existing)
@@ -443,9 +445,18 @@ def _ensure_group_key(
if found_by_name != lookup_name:
row.key_name = found_by_name
row.raw_json = json.dumps(existing, ensure_ascii=False)
# pending 行回填成功后升级为 exists
row.status = "exists"
row.error = None
# pending 行仅在成功回填明文 key 后升级为 exists
if row.status == "created_pending_key":
if key_value:
row.status = "exists"
row.error = None
else:
# 保持 pending,不升级
row.status = "created_pending_key"
row.error = "远端找到 Token,但未能获取到明文 Key,待下次回填"
else:
row.status = "exists"
row.error = None
row.updated_at = datetime.now(timezone.utc)
db.add(row)
db.commit()
@@ -471,8 +482,8 @@ def _ensure_group_key(
for candidate_name in names_to_search:
try:
existing = client.find_smartup_group_key(gid, candidate_name, prefix)
except Exception:
existing = None
except Exception as exc:
raise UpstreamError(f"查询远端 Key 失败: {exc}") from exc
if existing:
found_name = candidate_name
break
@@ -620,18 +631,13 @@ def generate_keys_by_groups(
try:
client.login()
groups = client.get_available_groups(u.groups_endpoint)
except Exception as exc:
raise HTTPException(502, str(exc))
# New-API/Nox 上游:批量前预热 token 列表缓存(一次 /api/token/ 拉取),
# 避免每个分组都调 /api/token/search 触发 Nox 搜索限流(10 次/60 秒)。
if _is_new_api_user_upstream(u) or _is_nox_api_upstream(u):
try:
# New-API/Nox 上游:批量前预热 token 列表缓存(一次 /api/token/ 拉取),
# 避免每个分组都调 /api/token/search 触发 Nox 搜索限流(10 次/60 秒)。
# 如果预热缓存失败(例如 429 或连接错误),直接抛出异常以 abort 批量操作,避免无缓存的重复创建
if (_is_new_api_user_upstream(u) or _is_nox_api_upstream(u)) and hasattr(client, "warm_token_list_cache"):
client.warm_token_list_cache()
except Exception as exc:
logger.warning(
"generate_keys_by_groups: warm_token_list_cache failed for %s: %s", uid, exc
)
except Exception as exc:
raise HTTPException(502, f"上游连接或缓存预热失败: {exc}")
for group in groups:
gid = _group_id(group)
+4 -2
View File
@@ -380,12 +380,14 @@ _RATE_LIMIT_BACKOFFS = (2, 5, 10, 20)
_RATE_LIMIT_MAX_WAIT = 60
def _retry_on_429(fn: Callable[[], Any], backoffs: tuple[int, ...] = _RATE_LIMIT_BACKOFFS) -> Any:
def _retry_on_429(fn: Callable[[], Any], backoffs: tuple[float, ...] | None = None) -> Any:
"""执行 fn(),若遇 429 则按退避序列重试,超限后抛 UpstreamError。
fn 应是 HTTP 调用;429 响应会被 httpx.HTTPStatusError 捕获(raise_for_status 之后)
或者 fn 内部已经解析为 UpstreamError 且 message 含 429。
"""
if backoffs is None:
backoffs = _RATE_LIMIT_BACKOFFS
total_waited = 0
for attempt, wait in enumerate(backoffs):
try:
@@ -783,7 +785,7 @@ class UpstreamClient:
缓存以 token name 为 key;同名取最新(id 最大)。
"""
all_tokens = self._list_all_new_api_tokens()
all_tokens = _retry_on_429(lambda: self._list_all_new_api_tokens())
cache: dict[str, dict[str, Any]] = {}
for t in all_tokens:
name = str(t.get("name") or t.get("key_name") or "")