feat(douyu): 代理写读分离 — 仅兑换/锁单/支付/开通/充值/送礼走代理
此前代理配置页开启后所有斗鱼任务(含查积分/刷列表等只读任务)统一走代理: - API 模式每任务 fetch 新代理, 查询任务白白消耗代理额度 - 查询任务失败面被代理抖动放大, 但查询本不怕 IP 频控 新策略 (DOUYU_PROXY_TASK_TYPES 白名单): - 写操作/消耗类: lock/pay/exchange_goods, exchange_esports/xpd_goods, create_elite/esports/gold_qr, donate_* 走代理 (静态模式或 API 每任务新代理) - 读操作类: query_*/refresh_*/get_*_bind_qr 等始终直连, 不消耗额度 - 判型用批次 task_type (同批次同类型, 与任务一致); 总开关仍为代理配置页 测试: 新增读任务直连/白名单覆盖断言, 三模式行为断言保留, 101 单测通过
This commit is contained in:
@@ -6,7 +6,7 @@ import asyncio
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
from loguru import logger
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -23,16 +23,35 @@ from ..models import (
|
||||
)
|
||||
from .douyu_service import DOUYU_CONFIG_FIELDS, douyu_config_value, ensure_douyu_config, douyu_task_payload
|
||||
|
||||
# 支付/到账轮询(手册与充值共用)
|
||||
DOUYU_PAYMENT_POLL_SECONDS = 600
|
||||
DOUYU_PAYMENT_POLL_INTERVAL = 5
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .douyu_runner import DouyuBatchRunner
|
||||
|
||||
# 支付/到账轮询(手册与充值共用)
|
||||
DOUYU_PAYMENT_POLL_SECONDS = 600
|
||||
DOUYU_PAYMENT_POLL_INTERVAL = 5
|
||||
|
||||
# 走代理的任务类型:写操作/消耗类(兑换、锁单、支付、开通、充值、送礼)。
|
||||
# 查询/刷新/绑定/扫码等读操作始终直连——它们不怕 IP 频控,走代理只会浪费代理额度。
|
||||
DOUYU_PROXY_TASK_TYPES = {
|
||||
# 兑换链路(IP 频控重点, 对应"精英手册火爆")
|
||||
"lock_goods",
|
||||
"pay_locked_order",
|
||||
"exchange_goods",
|
||||
"exchange_esports_goods",
|
||||
"exchange_xpd_goods",
|
||||
# 消耗类写操作: 开通手册 / 充值 / 送礼
|
||||
"create_elite_qr",
|
||||
"create_esports_qr",
|
||||
"create_gold_qr",
|
||||
"donate_elite_gift",
|
||||
"donate_esports_chicken_gift",
|
||||
"donate_esports_firework_gift",
|
||||
}
|
||||
|
||||
|
||||
class DouyuBatchRunnerCore:
|
||||
"""斗鱼任务执行器公共基础:批次状态、日志、任务落库与客户端构造。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
db: Session,
|
||||
@@ -54,14 +73,14 @@ class DouyuBatchRunnerCore:
|
||||
self._counter_lock = threading.Lock()
|
||||
self._started = 0
|
||||
# 代理接入 (配置源 = 代理配置页, 与 CK 检测/虎牙注册同款):
|
||||
# 开启后斗鱼任务(精英手册兑换/和平小店/电竞等)出网统一走代理, 应对 IP 频控
|
||||
# 仅兑换/锁单/支付/充值/送礼等写操作任务走代理, 查询/刷新/绑定类直连
|
||||
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', '')}")
|
||||
logger.info(f"[douyu] 写操作任务将走静态代理: {self._static_proxies.get('https', '')}")
|
||||
elif self._proxy_fetcher:
|
||||
logger.info("[douyu] 任务将按任务从代理 API 取新代理")
|
||||
logger.info("[douyu] 写操作任务将按任务从代理 API 取新代理")
|
||||
|
||||
def _create_proxy_fetcher(self) -> ProxyFetcher | None:
|
||||
"""API 代理模式: 每个任务从代理 API 取 1 个新代理 (与虎牙注册链同款)."""
|
||||
@@ -90,7 +109,13 @@ class DouyuBatchRunnerCore:
|
||||
return {"http": http or https, "https": https or http}
|
||||
|
||||
def _proxies_for_task(self) -> dict[str, str] | None:
|
||||
"""取本任务出网代理: 静态优先; API 模式每任务取新代理, 失败降级直连并告警."""
|
||||
"""取本任务出网代理。
|
||||
|
||||
写/读分离:仅兑换/锁单/支付/开通/充值/送礼等写操作任务走代理(应对 IP 频控);
|
||||
查询/刷新/绑定/扫码等读操作任务始终直连,不消耗代理额度。
|
||||
"""
|
||||
if self.task_type not in DOUYU_PROXY_TASK_TYPES:
|
||||
return None
|
||||
if self._static_proxies:
|
||||
return self._static_proxies
|
||||
if self._proxy_fetcher:
|
||||
@@ -287,7 +312,8 @@ class DouyuBatchRunnerCore:
|
||||
return {**payload, **self.payload}
|
||||
|
||||
def _client(self, cookie: str) -> DouyuActivityClient:
|
||||
"""任务 HTTP 客户端: 会话级代理 (覆盖精英手册/和平小店/电竞等全部请求)."""
|
||||
"""任务 HTTP 客户端:写操作任务注入会话级代理(兑换/锁单/支付/充值/送礼),
|
||||
读操作任务直连;见 DOUYU_PROXY_TASK_TYPES。"""
|
||||
return DouyuActivityClient(
|
||||
cookie,
|
||||
logger=lambda msg: self._push_log("debug", msg),
|
||||
|
||||
Reference in New Issue
Block a user