完成虎牙宝典WSS绑定与快照隔离重构
This commit is contained in:
@@ -39,3 +39,5 @@ web/frontend/dist/
|
||||
.tmp_reverse/
|
||||
apks/
|
||||
work/
|
||||
|
||||
analysis/
|
||||
|
||||
@@ -31,6 +31,7 @@ class HuyaEliteWssSession:
|
||||
self.guid = HuyaHttpClient._resolve_cookie_guid(self.cookie)
|
||||
self.kind = kind
|
||||
self.logger = logger or (lambda _message: None)
|
||||
self.http = HuyaHttpClient(logger=self.logger)
|
||||
self.loop = asyncio.new_event_loop()
|
||||
self.client: HuyaWssClient | None = None
|
||||
self._closed = False
|
||||
@@ -111,6 +112,27 @@ class HuyaEliteWssSession:
|
||||
def get_user_prize_records(self, uid: int, cookie: str, sid: int):
|
||||
return self._run(self.client.get_user_prize_records(uid, cookie, sid))
|
||||
|
||||
def check_user_bind_game_account(self, **kwargs):
|
||||
return self._run(self.client.check_user_bind_game_account(**kwargs))
|
||||
|
||||
def confirm_bind_act_account(self, **kwargs):
|
||||
return self._run(self.client.confirm_bind_act_account(**kwargs))
|
||||
|
||||
def get_live_link_param(self, **kwargs):
|
||||
return self._run(self.client.get_live_link_param(**kwargs))
|
||||
|
||||
def get_user_profile_batch(self, **kwargs):
|
||||
return self._run(self.client.get_user_profile_batch(**kwargs))
|
||||
|
||||
def build_bind_urls(self, *args, **kwargs):
|
||||
return self.http.build_bind_urls(*args, **kwargs)
|
||||
|
||||
def get_livelink_mini_qrcode(self, *args, **kwargs):
|
||||
return self.http.get_livelink_mini_qrcode(*args, **kwargs)
|
||||
|
||||
def get_livelink_qrcode_status(self, *args, **kwargs):
|
||||
return self.http.get_livelink_qrcode_status(*args, **kwargs)
|
||||
|
||||
def get_goods_info(self, **kwargs):
|
||||
return self._run(self.client.get_goods_info(**kwargs))
|
||||
|
||||
|
||||
@@ -800,6 +800,69 @@ class HuyaWssClient:
|
||||
req.sid = int(sid or 0)
|
||||
return await self.call_rpc("webActUI", "getUserPrizeRecords", req, GetUserPrizeRecordsResp)
|
||||
|
||||
async def check_user_bind_game_account(
|
||||
self, uid: int, cookie: str, b_act_id: int, is_use_outer_act_id: int = 1,
|
||||
gid: int = 0, outer_act_id: str = "", scene: str = "",
|
||||
):
|
||||
from .activity_structs import (
|
||||
CheckUserBindGameAccountReq,
|
||||
CheckUserBindGameAccountResp,
|
||||
)
|
||||
|
||||
req = CheckUserBindGameAccountReq()
|
||||
req.userId = self._build_activity_user(uid, cookie)
|
||||
req.gid = int(gid or 0)
|
||||
req.outerActId = str(outer_act_id or "")
|
||||
req.scene = scene or ""
|
||||
req.bActId = int(b_act_id or 0)
|
||||
req.isUseOuterActId = int(is_use_outer_act_id or 0)
|
||||
return await self.call_rpc(
|
||||
"webActUI", "checkUserBindGameAccount", req, CheckUserBindGameAccountResp
|
||||
)
|
||||
|
||||
async def confirm_bind_act_account(
|
||||
self, uid: int, cookie: str, b_act_id: int, gid: int = 0, outer_act_id: str = ""
|
||||
):
|
||||
from .activity_structs import (
|
||||
ConfirmBindActAccountReq,
|
||||
ConfirmBindActAccountResp,
|
||||
)
|
||||
|
||||
req = ConfirmBindActAccountReq()
|
||||
req.userId = self._build_activity_user(uid, cookie)
|
||||
req.gid = int(gid or 0)
|
||||
req.outerActId = str(outer_act_id or "")
|
||||
req.bActId = int(b_act_id or 0)
|
||||
return await self.call_rpc(
|
||||
"webActUI", "confirmBindActAccount", req, ConfirmBindActAccountResp
|
||||
)
|
||||
|
||||
async def get_live_link_param(
|
||||
self, uid: int, cookie: str, b_act_id: int, gid: int = 0,
|
||||
outer_act_id: int = 0, game_auth_scene: str = "",
|
||||
):
|
||||
from .activity_structs import GetLiveLinkParamReq, GetLiveLinkParamResp
|
||||
|
||||
req = GetLiveLinkParamReq()
|
||||
req.userId = self._build_activity_user(uid, cookie)
|
||||
req.gid = int(gid or 0)
|
||||
req.outerActId = int(outer_act_id or 0)
|
||||
req.gameAuthScene = game_auth_scene or ""
|
||||
req.bActId = int(b_act_id or 0)
|
||||
return await self.call_rpc("webActUI", "getLiveLinkParam", req, GetLiveLinkParamResp)
|
||||
|
||||
async def get_user_profile_batch(self, uid: int, cookie: str, target_uids: list[int]):
|
||||
from .activity_structs import GetUserProfileBatchReq, GetUserProfileBatchResp
|
||||
|
||||
req = GetUserProfileBatchReq()
|
||||
req.userId = self._build_activity_user(0, "")
|
||||
req.uidList = [int(item) for item in target_uids if int(item or 0)]
|
||||
if not req.uidList:
|
||||
return None
|
||||
return await self.call_rpc(
|
||||
"huyauserui", "getUserProfileBatch", req, GetUserProfileBatchResp
|
||||
)
|
||||
|
||||
async def list_pay_channels(
|
||||
self, uid: int, guid: str, cookie: str, spu_id: str, sku_id: int = 0,
|
||||
supplier_id: int = 87401,
|
||||
|
||||
@@ -22,7 +22,7 @@ TESTS_DIR = Path(__file__).resolve().parent
|
||||
PROJECT_ROOT = TESTS_DIR.parent
|
||||
VERSIONS_DIR = PROJECT_ROOT / "web" / "backend" / "migrations" / "versions"
|
||||
|
||||
HEAD_REVISION = "20260831_0032"
|
||||
HEAD_REVISION = "20260901_0033"
|
||||
|
||||
# 迁移新增、但不声明在模型里的复合查询索引。
|
||||
EXTRA_INDEXES = (
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
"""按账号和活动隔离虎牙宝典商品快照。"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260901_0033"
|
||||
down_revision: str | None = "20260831_0032"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def _columns(bind, table: str) -> set[str]:
|
||||
return {item["name"] for item in sa.inspect(bind).get_columns(table)}
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
bind = op.get_bind()
|
||||
for table in ("huya_goods_snapshot", "huya_recharge_goods_snapshot"):
|
||||
columns = _columns(bind, table)
|
||||
if "account_id" not in columns:
|
||||
op.add_column(table, sa.Column("account_id", sa.Integer(), nullable=True))
|
||||
op.create_index(f"ix_{table}_account_id", table, ["account_id"])
|
||||
if "sid" not in columns:
|
||||
op.add_column(
|
||||
table,
|
||||
sa.Column("sid", sa.Integer(), nullable=False, server_default="0"),
|
||||
)
|
||||
op.create_index(f"ix_{table}_sid", table, ["sid"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
bind = op.get_bind()
|
||||
for table in ("huya_goods_snapshot", "huya_recharge_goods_snapshot"):
|
||||
columns = _columns(bind, table)
|
||||
if "sid" in columns:
|
||||
op.drop_index(f"ix_{table}_sid", table_name=table)
|
||||
op.drop_column(table, "sid")
|
||||
if "account_id" in columns:
|
||||
op.drop_index(f"ix_{table}_account_id", table_name=table)
|
||||
op.drop_column(table, "account_id")
|
||||
@@ -576,6 +576,8 @@ class HuyaGoodsSnapshot(Base):
|
||||
__tablename__ = "huya_goods_snapshot"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
account_id: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True)
|
||||
sid: Mapped[int] = mapped_column(Integer, default=0, index=True)
|
||||
product_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
|
||||
name: Mapped[str] = mapped_column(String(256), default="")
|
||||
price: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
@@ -592,6 +594,8 @@ class HuyaRechargeGoodsSnapshot(Base):
|
||||
__tablename__ = "huya_recharge_goods_snapshot"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
account_id: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True)
|
||||
sid: Mapped[int] = mapped_column(Integer, default=0, index=True)
|
||||
spu_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
|
||||
sku_id: Mapped[str] = mapped_column(String(64), default="", index=True)
|
||||
name: Mapped[str] = mapped_column(String(256), default="")
|
||||
|
||||
@@ -1823,8 +1823,11 @@ def list_goods(
|
||||
current: User = Depends(require_permission("huya:task")),
|
||||
):
|
||||
"""查看已缓存的虎牙商品快照。"""
|
||||
rows = db.query(HuyaGoodsSnapshot).order_by(HuyaGoodsSnapshot.id.asc()).all()
|
||||
return rows
|
||||
rows = db.query(HuyaGoodsSnapshot).order_by(HuyaGoodsSnapshot.id.desc()).all()
|
||||
latest = {}
|
||||
for row in rows:
|
||||
latest.setdefault((row.sid, row.product_id), row)
|
||||
return list(latest.values())
|
||||
|
||||
|
||||
@router.get("/recharge-goods", response_model=list[HuyaRechargeGoodsOut])
|
||||
@@ -1833,12 +1836,11 @@ def list_recharge_goods(
|
||||
current: User = Depends(require_permission("huya:task")),
|
||||
):
|
||||
"""查看已缓存的虎牙充值商品快照。"""
|
||||
rows = (
|
||||
db.query(HuyaRechargeGoodsSnapshot)
|
||||
.order_by(HuyaRechargeGoodsSnapshot.id.asc())
|
||||
.all()
|
||||
)
|
||||
return rows
|
||||
rows = db.query(HuyaRechargeGoodsSnapshot).order_by(HuyaRechargeGoodsSnapshot.id.desc()).all()
|
||||
latest = {}
|
||||
for row in rows:
|
||||
latest.setdefault((row.sid, row.spu_id), row)
|
||||
return list(latest.values())
|
||||
|
||||
|
||||
@router.post("/tasks/batch")
|
||||
|
||||
@@ -551,6 +551,8 @@ class HuyaTaskOut(BaseModel):
|
||||
|
||||
class HuyaGoodsOut(BaseModel):
|
||||
id: int
|
||||
account_id: int | None = None
|
||||
sid: int = 0
|
||||
product_id: str
|
||||
name: str = ""
|
||||
price: int | None = None
|
||||
@@ -564,6 +566,8 @@ class HuyaGoodsOut(BaseModel):
|
||||
def _serialize(self) -> dict[str, Any]:
|
||||
return {
|
||||
"id": self.id,
|
||||
"account_id": self.account_id,
|
||||
"sid": self.sid,
|
||||
"product_id": self.product_id,
|
||||
"name": self.name,
|
||||
"price": self.price,
|
||||
@@ -575,6 +579,8 @@ class HuyaGoodsOut(BaseModel):
|
||||
|
||||
class HuyaRechargeGoodsOut(BaseModel):
|
||||
id: int
|
||||
account_id: int | None = None
|
||||
sid: int = 0
|
||||
spu_id: str
|
||||
sku_id: str = ""
|
||||
name: str = ""
|
||||
@@ -594,6 +600,8 @@ class HuyaRechargeGoodsOut(BaseModel):
|
||||
def _serialize(self) -> dict[str, Any]:
|
||||
return {
|
||||
"id": self.id,
|
||||
"account_id": self.account_id,
|
||||
"sid": self.sid,
|
||||
"spu_id": self.spu_id,
|
||||
"sku_id": self.sku_id,
|
||||
"name": self.name,
|
||||
|
||||
@@ -8,8 +8,6 @@ from typing import TYPE_CHECKING, Any
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.huya import HuyaHttpClient
|
||||
|
||||
from ..models import HuyaAccount, HuyaTask
|
||||
|
||||
HUYA_BIND_ROLE_POLL_SECONDS = 180
|
||||
@@ -18,6 +16,9 @@ HUYA_BIND_ZT_UUID = "b02faae1"
|
||||
HUYA_BIND_ROOM_ID = "30596253"
|
||||
from .huya_runner_core import HUYA_RECHARGE_SOURCE_ID, HuyaBatchRunnerCore
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.huya import HuyaHttpClient
|
||||
|
||||
|
||||
class BindMixin:
|
||||
"""游戏角色绑定域:绑定状态机、扫码/确认绑定。"""
|
||||
@@ -36,6 +37,7 @@ class BindMixin:
|
||||
def _push_log(self, level: str, message: str) -> None: ...
|
||||
def _mark_task(self, *args: Any, **kwargs: Any) -> None: ...
|
||||
def _update_task_progress(self, *args: Any, **kwargs: Any) -> None: ...
|
||||
def _activity_client(self, uid: int, cookie: str): ...
|
||||
|
||||
@staticmethod
|
||||
def _role_name(bind_status) -> str:
|
||||
@@ -315,9 +317,7 @@ class BindMixin:
|
||||
self._mark_task(worker_db, task, "failed", "账号 Cookie 为空")
|
||||
return
|
||||
|
||||
client: Any = HuyaHttpClient(
|
||||
logger=lambda msg: self._push_log("info", f"[{uid}] {msg}")
|
||||
)
|
||||
client: Any = self._activity_client(uid, cookie)
|
||||
bind_status, bind_query_result = self._resolve_bind_status(
|
||||
client=client,
|
||||
uid=uid,
|
||||
@@ -503,9 +503,7 @@ class BindMixin:
|
||||
self._mark_task(worker_db, task, "failed", "账号 Cookie 为空")
|
||||
return
|
||||
|
||||
client: Any = HuyaHttpClient(
|
||||
logger=lambda msg: self._push_log("info", f"[{uid}] {msg}")
|
||||
)
|
||||
client: Any = self._activity_client(uid, cookie)
|
||||
bind_status, bind_query_result = self._resolve_bind_status(
|
||||
client=client,
|
||||
uid=uid,
|
||||
@@ -577,9 +575,7 @@ class BindMixin:
|
||||
self._mark_task(worker_db, task, "failed", "账号 Cookie 为空")
|
||||
return
|
||||
|
||||
client: Any = HuyaHttpClient(
|
||||
logger=lambda msg: self._push_log("info", f"[{uid}] {msg}")
|
||||
)
|
||||
client: Any = self._activity_client(uid, cookie)
|
||||
role_status, role_query_result = self._resolve_bind_status(
|
||||
client=client,
|
||||
uid=uid,
|
||||
|
||||
@@ -245,10 +245,15 @@ class GoodsMixin:
|
||||
if item.get("product_id") and item.get("name")
|
||||
]
|
||||
now = datetime.now(UTC)
|
||||
worker_db.query(HuyaGoodsSnapshot).delete(synchronize_session=False)
|
||||
worker_db.query(HuyaGoodsSnapshot).filter(
|
||||
HuyaGoodsSnapshot.account_id == account.id,
|
||||
HuyaGoodsSnapshot.sid == sid_int,
|
||||
).delete(synchronize_session=False)
|
||||
for item in goods:
|
||||
worker_db.add(
|
||||
HuyaGoodsSnapshot(
|
||||
account_id=account.id,
|
||||
sid=sid_int,
|
||||
product_id=item["product_id"],
|
||||
name=item["name"],
|
||||
price=item["price"],
|
||||
@@ -298,7 +303,11 @@ class GoodsMixin:
|
||||
|
||||
snapshot = (
|
||||
worker_db.query(HuyaGoodsSnapshot)
|
||||
.filter(HuyaGoodsSnapshot.product_id == str(product_id))
|
||||
.filter(
|
||||
HuyaGoodsSnapshot.account_id == account.id,
|
||||
HuyaGoodsSnapshot.sid == sid_int,
|
||||
HuyaGoodsSnapshot.product_id == str(product_id),
|
||||
)
|
||||
.first()
|
||||
)
|
||||
product_name = str(
|
||||
|
||||
@@ -310,10 +310,15 @@ class RechargeMixin:
|
||||
}
|
||||
goods.append(item)
|
||||
|
||||
worker_db.query(HuyaRechargeGoodsSnapshot).delete(synchronize_session=False)
|
||||
worker_db.query(HuyaRechargeGoodsSnapshot).filter(
|
||||
HuyaRechargeGoodsSnapshot.account_id == account.id,
|
||||
HuyaRechargeGoodsSnapshot.sid == (self._to_int(config_info.get("sid")) or 2203),
|
||||
).delete(synchronize_session=False)
|
||||
for item in goods:
|
||||
worker_db.add(
|
||||
HuyaRechargeGoodsSnapshot(
|
||||
account_id=account.id,
|
||||
sid=self._to_int(config_info.get("sid")) or 2203,
|
||||
spu_id=item["spu_id"],
|
||||
sku_id=item["sku_id"],
|
||||
name=item["name"],
|
||||
@@ -382,7 +387,11 @@ class RechargeMixin:
|
||||
|
||||
snapshot = (
|
||||
worker_db.query(HuyaRechargeGoodsSnapshot)
|
||||
.filter(HuyaRechargeGoodsSnapshot.spu_id == spu_id)
|
||||
.filter(
|
||||
HuyaRechargeGoodsSnapshot.account_id == account.id,
|
||||
HuyaRechargeGoodsSnapshot.sid == (self._to_int(config_info.get("sid")) or 2203),
|
||||
HuyaRechargeGoodsSnapshot.spu_id == spu_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
payload_sku_id = self._to_int(self.payload.get("sku_id"))
|
||||
|
||||
Reference in New Issue
Block a user