fix(douyu): 精英宝典兑换等斗鱼任务接入代理 — 此前全部裸连

问题: DouyuBatchRunner/_client 创建的 DouyuActivityClient 无 proxies,
trust_env=False 下斗鱼任务(精英宝典兑换/和平小店/电竞/送礼等全部 45 个
请求点)直连本机出口 IP, 代理配置页形同虚设。

- activity_client: __init__ 支持 proxies (session 级, 一次覆盖全部请求)
- douyu_runner: 装载全局代理配置 (与 CK 检测/虎牙注册同源 代理配置页):
  · 静态模式: 配置页手填 http/https → 全任务同一代理
  · API 模式: 每任务从代理 API 取新代理 (ProxyFetcher, 含白名单同步,
    1.5s 最小间隔防打爆), 失败降级直连并推送告警日志
- 验证: 77 单测 OK; 三种模式(静态/API/关闭)行为断言通过
This commit is contained in:
yml2213
2026-08-30 10:25:16 +08:00
parent 054af84144
commit a05ae245bf
2 changed files with 61 additions and 2 deletions
+4
View File
@@ -65,12 +65,16 @@ class DouyuActivityClient:
*, *,
logger: Callable[[str], None] | None = None, logger: Callable[[str], None] | None = None,
timeout: tuple[float, float] = (8, 20), timeout: tuple[float, float] = (8, 20),
proxies: dict[str, str] | None = None,
): ):
self.cookie = normalize_douyu_cookie(cookie) self.cookie = normalize_douyu_cookie(cookie)
self.timeout = timeout self.timeout = timeout
self.logger = logger or (lambda _msg: None) self.logger = logger or (lambda _msg: None)
self.session = requests.Session() self.session = requests.Session()
# trust_env=False: 不读环境变量代理, 只用显式传入的 proxies (代理配置页控制)
self.session.trust_env = False self.session.trust_env = False
if proxies:
self.session.proxies.update(proxies)
self.session.headers.update({ self.session.headers.update({
"User-Agent": PC_UA, "User-Agent": PC_UA,
"Accept": "application/json, text/plain, */*", "Accept": "application/json, text/plain, */*",
+57 -2
View File
@@ -22,8 +22,9 @@ from core.douyu import (
FishFinRechargeError, FishFinRechargeError,
) )
from core.douyu.proxy_fetcher import ProxyFetcher
from ..database import SessionLocal from ..database import SessionLocal
from ..models import Account, DouyuEsportsGoodsSnapshot, DouyuGoodsSnapshot, DouyuTask, DouyuXpdGoodsSnapshot from ..models import Account, DouyuEsportsGoodsSnapshot, DouyuGoodsSnapshot, DouyuTask, DouyuXpdGoodsSnapshot, ProxyConfig as ProxyConfigModel
from .douyu_service import ( from .douyu_service import (
DOUYU_CONFIG_FIELDS, DOUYU_CONFIG_FIELDS,
account_uid, account_uid,
@@ -72,6 +73,55 @@ class DouyuBatchRunner:
self._stop = threading.Event() self._stop = threading.Event()
self._counter_lock = threading.Lock() self._counter_lock = threading.Lock()
self._started = 0 self._started = 0
# 代理接入 (与 CK 检测/虎牙注册同一配置源): 代理配置页开启后,
# 斗鱼任务(含精英宝典兑换)的出网请求统一走代理, 不再裸连
self._proxy_cfg = db.query(ProxyConfigModel).first() if db else None
self._proxy_fetcher = self._create_proxy_fetcher()
self._static_proxies = self._resolve_static_proxies()
if self._static_proxies:
logger.info(f"[douyu] 任务将走静态代理: {self._static_proxies.get('https','')}")
elif self._proxy_fetcher:
logger.info("[douyu] 任务将按任务从代理 API 取新代理")
def _create_proxy_fetcher(self) -> ProxyFetcher | None:
"""API 代理模式: 每个任务从代理 API 取 1 个新代理 (与虎牙注册链同款)."""
cfg = self._proxy_cfg
if not cfg or not cfg.enabled or not cfg.api_url:
return None
wl_platform = getattr(cfg, "whitelist_platform", None) or "xiequ"
wl_credentials = getattr(cfg, "whitelist_credentials", None)
if not wl_credentials and getattr(cfg, "whitelist_uid", "") and getattr(cfg, "whitelist_ukey", ""):
wl_credentials = {"uid": cfg.whitelist_uid, "ukey": cfg.whitelist_ukey}
return ProxyFetcher(
api_url=cfg.api_url,
whitelist_platform=wl_platform,
whitelist_credentials=wl_credentials if cfg.whitelist_enabled else None,
stop_event=self._stop,
)
def _resolve_static_proxies(self) -> dict[str, str] | None:
"""静态代理模式: 代理配置页手填的 http/https 地址."""
cfg = self._proxy_cfg
if not cfg or not cfg.enabled:
return None
http, https = (cfg.http or "").strip(), (cfg.https or "").strip()
if not http and not https:
return None
return {"http": http or https, "https": https or http}
def _proxies_for_task(self) -> dict[str, str] | None:
"""取本任务出网代理: 静态优先; API 模式每任务取新代理, 失败降级直连并告警."""
if self._static_proxies:
return self._static_proxies
if self._proxy_fetcher:
try:
proxy_url = self._proxy_fetcher.fetch_new_proxy()
if proxy_url:
return {"http": proxy_url, "https": proxy_url}
self._push_log("warning", "代理 API 未返回可用代理, 本任务降级直连")
except Exception as exc:
self._push_log("warning", f"取代理失败, 本任务降级直连: {exc}")
return None
def stop(self): def stop(self):
self._stop.set() self._stop.set()
@@ -876,7 +926,12 @@ class DouyuBatchRunner:
return {**payload, **self.payload} return {**payload, **self.payload}
def _client(self, cookie: str) -> DouyuActivityClient: def _client(self, cookie: str) -> DouyuActivityClient:
return DouyuActivityClient(cookie, logger=lambda msg: self._push_log("debug", msg)) """任务 HTTP 客户端: 会话级代理 (覆盖精英宝典/和平小店/电竞等全部请求)."""
return DouyuActivityClient(
cookie,
logger=lambda msg: self._push_log("debug", msg),
proxies=self._proxies_for_task(),
)
def _fetch_bind_info_candidates( def _fetch_bind_info_candidates(
self, self,