feat: bzzoiro 多 API Key 轮换,遇限流自动切换
新增 KeyRing 轮换环(src/data/key_ring.py): - 支持逗号/分号/换行分隔多个 key,单 key 场景零开销 - 遇到 429 自动标记当前 key 冷却(默认 60s)并立即切换到下一个 key - 全部 key 冷却时等待最早恢复的 key,避免无谓重试 - 热更新 key 列表(增删 key 无需重启) - 进程级单例,按 base URL 隔离 后端集成: - bzzoiro._fetch_json_async 接入 KeyRing,429 立即轮换(不等待) - 新增 /admin/keyring/status 端点展示 key 环状态 - 新增 /admin/keyring/cooldown/reset 紧急重置冷却 - BZZOIRO_KEY 配置描述提示多 key 支持 - mask_value 多 key 显示数量(如 "3 个 key(末段 …XXXX)") - 清理已移除 injuries 的 API_FOOTBALL_KEY 配置项 前端: - 数据源页新增 Key Ring 状态面板(每个 key 可用/冷却状态 + 重置按钮) - 配置项脱敏展示支持多 key 计数 测试: 新增 24 个 KeyRing 单元测试 + 2 个 bzzoiro 轮换集成测试,全部通过。 Co-Authored-By: new-provider/LongCat-2.0 <<EMAIL>>
This commit is contained in:
co-authored by
new-provider/LongCat-2.0 <
parent
f05dc1ae15
commit
f52ec8b963
@@ -29,6 +29,7 @@ from src.core.runtime_config import (
|
||||
)
|
||||
from src.db.base import AsyncSession, get_db_read
|
||||
from src.db.models import League, Match, MatchStats, Standing
|
||||
from src.data.key_ring import get_key_ring, parse_keys
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -346,6 +347,30 @@ async def ingest_status(db: AsyncSession = Depends(get_db_read)):
|
||||
return {"sources": [bzzoiro]}
|
||||
|
||||
|
||||
@router.get("/keyring/status")
|
||||
async def keyring_status():
|
||||
"""KeyRing 运行状态:当前使用的 key、冷却状态、轮转信息(供管理后台展示)。"""
|
||||
base = (await get_runtime_value("BZZOIRO_BASE")).rstrip("/")
|
||||
raw_keys = await get_runtime_value("BZZOIRO_KEY")
|
||||
ring = get_key_ring(base, raw_keys)
|
||||
st = ring.stats()
|
||||
st["base_url"] = base
|
||||
st["cooldown_seconds"] = ring._cooldown
|
||||
st["has_multiple"] = ring.has_multiple
|
||||
st["active_key"] = ring.active_key
|
||||
return st
|
||||
|
||||
|
||||
@router.post("/keyring/cooldown/reset")
|
||||
async def keyring_reset_cooldown():
|
||||
"""手动重置所有 key 的冷却状态(用于紧急恢复)。"""
|
||||
base = (await get_runtime_value("BZZOIRO_BASE")).rstrip("/")
|
||||
raw_keys = await get_runtime_value("BZZOIRO_KEY")
|
||||
ring = get_key_ring(base, raw_keys)
|
||||
ring._blocked_until.clear()
|
||||
return {"ok": True, "message": "已重置所有 key 冷却状态", "stats": ring.stats()}
|
||||
|
||||
|
||||
def _last_failure_log(source: str) -> dict | None:
|
||||
"""从系统日志缓冲中查找某数据源的最近一次错误(仅作参考,非专用失败表)。"""
|
||||
entries = get_entries(min_level="ERROR", keyword=source, limit=5)
|
||||
|
||||
Reference in New Issue
Block a user