fix(huya): 刷新开通商品不再依赖已下线的活动任务接口
真机(2026-10-15)实测 webActUI.getActTaskDetail(act 25135) 返回 601「活动已下线」,刷新开通商品第一步就失败,下拉框一直为空。 - 商品来源改为内置候选(积分中心页面「赠送指定礼物得积分」那一排, 全部经 getGoodsInfoV5 真机验证 code=200):精英宝典 hy-8328015 ¥30、 精英币 hy-8327356 ¥0.1、精英徽章 hy-5872824 ¥1、精英令 hy-5879346 ¥10、 精英令*10 hy-5874269 ¥100、精英令*100 hy-5874270 ¥1000; 旧版精英宝典 hy-5879340 页面已不再使用,从候选移除 - 活动任务详情降级为可选补充:返回非 200 只记 warning 并写入 result.task_detail_error,不再让整次刷新失败;失败原因以商品详情为准 - 刷新时清掉该账号全部旧商品行(不限 sid),避免同名商品重复 - 列表接口只返回账号级快照并过滤 2026-07-04 的全局种子行, 同一 spu_id 保留最新一行(原先 精英宝典 会出现两次) - 前端:下拉标签带 spu 便于区分,按内置顺序排序,默认自动选中第一项 - 新增 tests/test_huya_recharge_goods_source.py(6 条):活动下线响应字节 回归、下线不阻断刷新、活动商品仍会并入、失败原因归属、跨 sid 清理、 列表去重与隐藏种子行(均已做反向验证) 真机验证:刷新成功 6 个商品,价格/库存与网页一致
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
"""开通商品来源:不再依赖已下线的旧活动任务接口(2026-10-15 真机实测)。
|
||||
|
||||
真机现状(账号 hy_297832664,HTTP cdnws 通道):
|
||||
- ``webActUI.getActTaskDetail(act_id=25135)`` → 601「活动已下线」
|
||||
- ``shopMiddleUI.getGoodsInfoV5`` 对 hy-8328015 / hy-8327356 / hy-5879340 均 code=200
|
||||
|
||||
因此刷新开通商品改为「内置商品候选 + getGoodsInfoV5 详情」,活动任务详情只做可选
|
||||
补充:它失败只记 warning,不再让整次刷新失败。
|
||||
"""
|
||||
|
||||
from core.huya.activity_structs import GetActTaskDetailResp
|
||||
from core.huya.frame_decoder import decode_struct_bytes
|
||||
from core.huya.taf_protocol import TafInputStream, TafType
|
||||
from web.backend.models import HuyaAccount, HuyaRechargeGoodsSnapshot
|
||||
from web.backend.services.huya_runner import HuyaBatchRunner
|
||||
|
||||
ROOM_PID = 1199650619883
|
||||
CONFIG = {"room_pid": str(ROOM_PID)}
|
||||
|
||||
# 真机 webActUI.getActTaskDetail(act 25135) 响应字节(status=601 / msg=活动已下线)
|
||||
ACT_TASK_OFFLINE_HEX = "0a010259160fe6b4bbe58aa8e5b7b2e4b88be7babf0b"
|
||||
# 积分中心页面上的开通商品(getGoodsInfoV5 真机验证 code=200)
|
||||
BUILTIN_SPU_LITERALS = [
|
||||
"hy-8328015",
|
||||
"hy-8327356",
|
||||
"hy-5872824",
|
||||
"hy-5879346",
|
||||
"hy-5874269",
|
||||
"hy-5874270",
|
||||
]
|
||||
|
||||
|
||||
class _FakeActTaskDetail:
|
||||
def __init__(self, status: int, msg: str, tasks: list[dict] | None = None):
|
||||
self.status = status
|
||||
self.msg = msg
|
||||
self._tasks = tasks or []
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"status": self.status,
|
||||
"msg": self.msg,
|
||||
"task_count": len(self._tasks),
|
||||
"tasks": self._tasks,
|
||||
}
|
||||
|
||||
|
||||
class _FakeGoodsInfo:
|
||||
def __init__(self, spu_id: str, sku_id: str, name: str, price: int):
|
||||
self.code = 200
|
||||
self.message = ""
|
||||
self._data = {
|
||||
"spu_id": spu_id,
|
||||
"sku_id": sku_id,
|
||||
"name": name,
|
||||
"price": price,
|
||||
"stock": 100,
|
||||
"buy_limit": 1,
|
||||
"icon": "",
|
||||
"description": "",
|
||||
}
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return dict(self._data)
|
||||
|
||||
|
||||
class _StubShopClient:
|
||||
def __init__(self, task: _FakeActTaskDetail | None, goods: bool = True):
|
||||
self.task = task
|
||||
self.goods = goods
|
||||
self.goods_calls: list[str] = []
|
||||
|
||||
def get_act_task_detail(self, uid: int, cookie: str, act_id: int):
|
||||
return self.task
|
||||
|
||||
def get_goods_info(self, **kwargs):
|
||||
self.goods_calls.append(kwargs["spu_id"])
|
||||
if not self.goods:
|
||||
return None
|
||||
return _FakeGoodsInfo(
|
||||
spu_id=kwargs["spu_id"],
|
||||
sku_id=f"sku-{kwargs['spu_id']}",
|
||||
name=f"name-{kwargs['spu_id']}",
|
||||
price=3000,
|
||||
)
|
||||
|
||||
|
||||
def _runner(monkeypatch, client) -> tuple[HuyaBatchRunner, list, list]:
|
||||
runner = HuyaBatchRunner(
|
||||
db=None, batch_id="test-batch", task_type="refresh_recharge_goods"
|
||||
)
|
||||
marks: list[tuple[str, str, dict | None]] = []
|
||||
logs: list[tuple[str, str]] = []
|
||||
monkeypatch.setattr(
|
||||
runner, "_shop_client", lambda uid, cookie: client, raising=False
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
runner,
|
||||
"_mark_task",
|
||||
lambda _db, _task, status, message, result=None: marks.append(
|
||||
(status, message, result)
|
||||
),
|
||||
raising=False,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
runner,
|
||||
"_push_log",
|
||||
lambda level, message: logs.append((level, message)),
|
||||
raising=False,
|
||||
)
|
||||
return runner, marks, logs
|
||||
|
||||
|
||||
def test_real_offline_response_bytes_report_activity_offline():
|
||||
payload = decode_struct_bytes(bytes.fromhex(ACT_TASK_OFFLINE_HEX))
|
||||
|
||||
assert payload["tag0"] == 601
|
||||
assert payload["tag1"] == "活动已下线"
|
||||
|
||||
# 结构体解析路径:tRsp 以 STRUCT_BEGIN 包一层(同 WupResponse.readStruct)
|
||||
ins = TafInputStream(bytes.fromhex(ACT_TASK_OFFLINE_HEX))
|
||||
_tag, dtype = ins.peek_head()
|
||||
assert dtype == TafType.STRUCT_BEGIN
|
||||
ins.read_head()
|
||||
parsed = GetActTaskDetailResp()
|
||||
parsed.read_from(ins)
|
||||
assert (parsed.status, parsed.msg) == (601, "活动已下线")
|
||||
|
||||
|
||||
def test_refresh_recharge_goods_survives_offline_activity(db, monkeypatch):
|
||||
client = _StubShopClient(_FakeActTaskDetail(601, "活动已下线"))
|
||||
runner, marks, logs = _runner(monkeypatch, client)
|
||||
account = HuyaAccount(uid="1199665170519", cookie="yyuid=1199665170519", tag="")
|
||||
db.add(account)
|
||||
db.commit()
|
||||
|
||||
runner._execute_refresh_recharge_goods(
|
||||
db, None, account, {"uid": 1199665170519, "cookie": account.cookie}, CONFIG
|
||||
)
|
||||
|
||||
status, message, result = marks[-1]
|
||||
assert status == "success"
|
||||
assert message == "已刷新开通商品 6 个"
|
||||
assert result["goods_count"] == 6
|
||||
assert result["task_detail_error"] == "活动已下线"
|
||||
assert client.goods_calls == BUILTIN_SPU_LITERALS
|
||||
assert [item["spu_id"] for item in result["goods"]] == BUILTIN_SPU_LITERALS
|
||||
# 活动接口不可用只记 warning,不把任务标失败
|
||||
assert logs and logs[-1][0] == "warning" and "活动已下线" in logs[-1][1]
|
||||
|
||||
rows = (
|
||||
db.query(HuyaRechargeGoodsSnapshot)
|
||||
.filter(HuyaRechargeGoodsSnapshot.account_id == account.id)
|
||||
.all()
|
||||
)
|
||||
assert sorted(row.spu_id for row in rows) == sorted(BUILTIN_SPU_LITERALS)
|
||||
assert all(row.sku_id for row in rows)
|
||||
|
||||
|
||||
def test_refresh_recharge_goods_still_merges_activity_candidates(db, monkeypatch):
|
||||
task = _FakeActTaskDetail(
|
||||
200,
|
||||
"请求成功",
|
||||
[
|
||||
{"task_type": 67, "spu_id": "hy-7770001", "name": "活动商品", "task_id": 5},
|
||||
{"task_type": 66, "spu_id": "hy-ignore-me", "name": "非充值任务"},
|
||||
],
|
||||
)
|
||||
client = _StubShopClient(task)
|
||||
runner, marks, logs = _runner(monkeypatch, client)
|
||||
account = HuyaAccount(uid="1199665170519", cookie="yyuid=1199665170519", tag="")
|
||||
db.add(account)
|
||||
db.commit()
|
||||
|
||||
runner._execute_refresh_recharge_goods(
|
||||
db, None, account, {"uid": 1199665170519, "cookie": account.cookie}, CONFIG
|
||||
)
|
||||
|
||||
status, _message, result = marks[-1]
|
||||
assert status == "success"
|
||||
assert result["task_detail_error"] == ""
|
||||
assert "hy-7770001" in client.goods_calls
|
||||
assert "hy-ignore-me" not in client.goods_calls
|
||||
assert result["goods_count"] == len(BUILTIN_SPU_LITERALS) + 1
|
||||
assert logs == []
|
||||
|
||||
|
||||
def test_refresh_recharge_goods_reports_goods_failure_not_activity_status(
|
||||
db, monkeypatch
|
||||
):
|
||||
client = _StubShopClient(_FakeActTaskDetail(601, "活动已下线"), goods=False)
|
||||
runner, marks, _logs = _runner(monkeypatch, client)
|
||||
account = HuyaAccount(uid="1199665170519", cookie="yyuid=1199665170519", tag="")
|
||||
db.add(account)
|
||||
db.commit()
|
||||
|
||||
runner._execute_refresh_recharge_goods(
|
||||
db, None, account, {"uid": 1199665170519, "cookie": account.cookie}, CONFIG
|
||||
)
|
||||
|
||||
status, message, result = marks[-1]
|
||||
assert status == "failed"
|
||||
assert message == "开通商品刷新失败: 商品详情接口无响应"
|
||||
# 失败原因必须来自商品详情,而不是把旧活动接口的「活动已下线」当结论
|
||||
assert "活动已下线" not in message
|
||||
assert result["failed_count"] == len(BUILTIN_SPU_LITERALS)
|
||||
|
||||
|
||||
def test_refresh_recharge_goods_drops_stale_rows_of_other_sids(db, monkeypatch):
|
||||
"""同账号跨 sid 的旧行必须清掉,否则列表会出现同名重复商品。"""
|
||||
client = _StubShopClient(_FakeActTaskDetail(601, "活动已下线"))
|
||||
runner, marks, _logs = _runner(monkeypatch, client)
|
||||
account = HuyaAccount(uid="1199665170519", cookie="yyuid=1199665170519", tag="")
|
||||
db.add(account)
|
||||
db.commit()
|
||||
# 上次刷新用的是另一个 sid(真实库里就存在 sid=0 的旧行)
|
||||
db.add(
|
||||
HuyaRechargeGoodsSnapshot(
|
||||
account_id=account.id,
|
||||
sid=0,
|
||||
spu_id="hy-5879340",
|
||||
sku_id="old-sku",
|
||||
name="精英宝典",
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
|
||||
runner._execute_refresh_recharge_goods(
|
||||
db, None, account, {"uid": 1199665170519, "cookie": account.cookie}, CONFIG
|
||||
)
|
||||
|
||||
assert marks[-1][0] == "success"
|
||||
rows = (
|
||||
db.query(HuyaRechargeGoodsSnapshot)
|
||||
.filter(HuyaRechargeGoodsSnapshot.account_id == account.id)
|
||||
.all()
|
||||
)
|
||||
assert len(rows) == len(BUILTIN_SPU_LITERALS)
|
||||
assert {row.sid for row in rows} == {2203}
|
||||
assert len({row.spu_id for row in rows}) == len(rows)
|
||||
|
||||
|
||||
def test_list_recharge_goods_hides_global_seed_rows_and_dedupes(db):
|
||||
"""列表只返回账号级快照:隐藏全局默认行,同一 spu 只留最新一行。"""
|
||||
from web.backend.routers.huya import list_recharge_goods
|
||||
|
||||
account = HuyaAccount(uid="1199665170519", cookie="yyuid=1199665170519", tag="")
|
||||
db.add(account)
|
||||
db.commit()
|
||||
# 历史全局默认行(2026-07-04 种子,含旧版精英宝典):不属于当前在售商品
|
||||
db.add(
|
||||
HuyaRechargeGoodsSnapshot(
|
||||
account_id=None,
|
||||
sid=0,
|
||||
spu_id="hy-5879340",
|
||||
sku_id="seed-sku",
|
||||
name="精英宝典",
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
# 同一 spu 的两次账号刷新结果:只保留最新一行
|
||||
db.add(
|
||||
HuyaRechargeGoodsSnapshot(
|
||||
account_id=account.id,
|
||||
sid=2203,
|
||||
spu_id="hy-8328015",
|
||||
sku_id="stale-sku",
|
||||
name="精英宝典",
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
db.add(
|
||||
HuyaRechargeGoodsSnapshot(
|
||||
account_id=account.id,
|
||||
sid=2203,
|
||||
spu_id="hy-8328015",
|
||||
sku_id="fresh-sku",
|
||||
name="精英宝典",
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
|
||||
rows = list_recharge_goods(db=db, current=None)
|
||||
|
||||
assert [row.spu_id for row in rows] == ["hy-8328015"]
|
||||
assert rows[0].sku_id == "fresh-sku"
|
||||
@@ -132,15 +132,22 @@ def list_recharge_goods(
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(require_permission("huya:task")),
|
||||
):
|
||||
"""查看已缓存的虎牙充值商品快照。"""
|
||||
"""查看已缓存的虎牙充值商品快照。
|
||||
|
||||
只返回账号级快照(account_id 非空):库里还有一批 2026-07-04 写入的全局默认行
|
||||
(account_id 为空、sid=0,含旧版精英宝典/精英令/精英徽章),它们已不是当前在售
|
||||
商品,未刷新过的账号显示空列表、点一次「刷新开通商品」即可。同一 spu_id 只保留
|
||||
最新一行,避免下拉框出现同名重复项。
|
||||
"""
|
||||
rows = (
|
||||
db.query(HuyaRechargeGoodsSnapshot)
|
||||
.filter(HuyaRechargeGoodsSnapshot.account_id.is_not(None))
|
||||
.order_by(HuyaRechargeGoodsSnapshot.id.desc())
|
||||
.all()
|
||||
)
|
||||
latest = {}
|
||||
latest: dict[str, HuyaRechargeGoodsSnapshot] = {}
|
||||
for row in rows:
|
||||
latest.setdefault((row.sid, row.spu_id), row)
|
||||
latest.setdefault(row.spu_id, row)
|
||||
return list(latest.values())
|
||||
|
||||
|
||||
|
||||
@@ -19,14 +19,58 @@ HUYA_RECHARGE_ACT_ID = 25135
|
||||
HUYA_RECHARGE_APP_SCENE = SHOP_APP_SCENE
|
||||
HUYA_PAYMENT_POLL_SECONDS = 180
|
||||
HUYA_PAYMENT_POLL_INTERVAL = 3
|
||||
HUYA_RECHARGE_EXTRA_PRODUCTS = [
|
||||
# 内置开通商品候选 = 积分中心页面(m-shop.huya.com/points.html)「赠送指定礼物得积分」
|
||||
# 那一排商品,全部经 shopMiddleUI.getGoodsInfoV5 真机验证 code=200:
|
||||
# hy-8328015 精英宝典 ¥30(送300积分,2026-10-13 抓包下单商品)
|
||||
# hy-8327356 精英币 ¥0.1(已开通后首格「自定义数量」,送1积分)
|
||||
# hy-5872824 精英徽章 ¥1(送10积分)、hy-5879346 精英令 ¥10(送100积分)
|
||||
# hy-5874269 精英令*10 ¥100(送1000积分)、hy-5874270 精英令*100 ¥1000(送10000积分)
|
||||
# 旧版精英宝典 hy-5879340 页面上已不再使用,从候选里去掉。
|
||||
# 旧活动接口 webActUI.getActTaskDetail(act 25135) 已下线(601 活动已下线),
|
||||
# 商品来源不能再依赖它,只能作为可选补充。
|
||||
HUYA_RECHARGE_BUILTIN_PRODUCTS = [
|
||||
{
|
||||
"spu_id": "hy-5879340",
|
||||
"spu_id": "hy-8328015",
|
||||
"name": "精英宝典",
|
||||
"task_name": "开通精英宝典",
|
||||
"description": "得300积分丨解锁道具兑换权益",
|
||||
"sort": 0,
|
||||
},
|
||||
{
|
||||
"spu_id": "hy-8327356",
|
||||
"name": "精英币",
|
||||
"task_name": "购买精英币",
|
||||
"description": "得1积分丨可兑指定返场皮肤道具",
|
||||
"sort": 1,
|
||||
},
|
||||
{
|
||||
"spu_id": "hy-5872824",
|
||||
"name": "精英徽章",
|
||||
"task_name": "购买精英徽章",
|
||||
"description": "送10积分",
|
||||
"sort": 2,
|
||||
},
|
||||
{
|
||||
"spu_id": "hy-5879346",
|
||||
"name": "精英令",
|
||||
"task_name": "购买精英令",
|
||||
"description": "送100积分",
|
||||
"sort": 3,
|
||||
},
|
||||
{
|
||||
"spu_id": "hy-5874269",
|
||||
"name": "精英令*10",
|
||||
"task_name": "购买精英令*10",
|
||||
"description": "送1000积分",
|
||||
"sort": 4,
|
||||
},
|
||||
{
|
||||
"spu_id": "hy-5874270",
|
||||
"name": "精英令*100",
|
||||
"task_name": "购买精英令*100",
|
||||
"description": "送10000积分",
|
||||
"sort": 5,
|
||||
},
|
||||
]
|
||||
from .huya_runner_core import HUYA_RECHARGE_SOURCE_ID
|
||||
|
||||
@@ -226,23 +270,6 @@ class RechargeMixin:
|
||||
return
|
||||
|
||||
client: Any = self._shop_client(uid, cookie)
|
||||
task_resp = client.get_act_task_detail(
|
||||
uid=uid, cookie=cookie, act_id=HUYA_RECHARGE_ACT_ID
|
||||
)
|
||||
if task_resp is None:
|
||||
self._mark_task(worker_db, task, "error", "虎牙充值任务详情接口无响应")
|
||||
return
|
||||
|
||||
task_result = task_resp.to_dict()
|
||||
if task_resp.status != 200:
|
||||
self._mark_task(
|
||||
worker_db,
|
||||
task,
|
||||
"failed",
|
||||
task_resp.msg or f"虎牙充值任务详情获取失败: {task_resp.status}",
|
||||
task_result,
|
||||
)
|
||||
return
|
||||
|
||||
candidates: list[dict] = []
|
||||
seen: set[str] = set()
|
||||
@@ -254,29 +281,51 @@ class RechargeMixin:
|
||||
seen.add(spu_id)
|
||||
candidates.append(item)
|
||||
|
||||
for item in HUYA_RECHARGE_EXTRA_PRODUCTS:
|
||||
for item in HUYA_RECHARGE_BUILTIN_PRODUCTS:
|
||||
add_candidate(dict(item))
|
||||
|
||||
for index, item in enumerate(task_result.get("tasks", []), start=1):
|
||||
if int(item.get("task_type") or 0) != 67:
|
||||
continue
|
||||
add_candidate(
|
||||
{
|
||||
"spu_id": item.get("spu_id") or "",
|
||||
"name": item.get("name") or "",
|
||||
"task_id": str(item.get("task_id") or ""),
|
||||
"task_name": item.get("name") or "",
|
||||
"description": item.get("description") or "",
|
||||
"icon": item.get("icon") or "",
|
||||
"task_url": item.get("task_url") or "",
|
||||
"prizes": item.get("prizes") or [],
|
||||
"sort": index,
|
||||
}
|
||||
# 旧活动任务接口(webActUI.getActTaskDetail,act 25135)已下线
|
||||
# (真机 2026-10-15:601 活动已下线),只作为可选补充:
|
||||
# 能返回就并入活动里的商品,失败不再阻断整次刷新。
|
||||
task_result: dict | None = None
|
||||
task_error = ""
|
||||
task_resp = client.get_act_task_detail(
|
||||
uid=uid, cookie=cookie, act_id=HUYA_RECHARGE_ACT_ID
|
||||
)
|
||||
if task_resp is None:
|
||||
task_error = "活动任务详情接口无响应"
|
||||
else:
|
||||
task_result = task_resp.to_dict()
|
||||
if task_resp.status == 200:
|
||||
for index, item in enumerate(task_result.get("tasks", []), start=1):
|
||||
if int(item.get("task_type") or 0) != 67:
|
||||
continue
|
||||
add_candidate(
|
||||
{
|
||||
"spu_id": item.get("spu_id") or "",
|
||||
"name": item.get("name") or "",
|
||||
"task_id": str(item.get("task_id") or ""),
|
||||
"task_name": item.get("name") or "",
|
||||
"description": item.get("description") or "",
|
||||
"icon": item.get("icon") or "",
|
||||
"task_url": item.get("task_url") or "",
|
||||
"prizes": item.get("prizes") or [],
|
||||
"sort": 1000 + index,
|
||||
}
|
||||
)
|
||||
else:
|
||||
task_error = (
|
||||
task_resp.msg or f"活动任务详情获取失败: {task_resp.status}"
|
||||
)
|
||||
if task_error:
|
||||
self._push_log(
|
||||
"warning",
|
||||
f"[{uid}] 活动任务接口不可用({task_error}),按内置开通商品刷新",
|
||||
)
|
||||
|
||||
if not candidates:
|
||||
self._mark_task(
|
||||
worker_db, task, "failed", "未从活动任务中发现充值商品", task_result
|
||||
worker_db, task, "failed", "未发现可用的开通商品", task_result or {}
|
||||
)
|
||||
return
|
||||
|
||||
@@ -328,10 +377,10 @@ class RechargeMixin:
|
||||
}
|
||||
goods.append(item)
|
||||
|
||||
# 清掉该账号的全部旧商品行(不限 sid):否则跨 sid 的旧行会和本次结果
|
||||
# 在列表接口里叠成同一商品的重复项(如 精英宝典 x2)。
|
||||
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(
|
||||
@@ -355,9 +404,6 @@ class RechargeMixin:
|
||||
|
||||
account.status = "recharge_goods_refreshed"
|
||||
account.updated_at = now
|
||||
message = f"已刷新充值商品 {len(goods)} 个"
|
||||
if failed:
|
||||
message += f",失败 {len(failed)} 个"
|
||||
result = {
|
||||
"act_id": HUYA_RECHARGE_ACT_ID,
|
||||
"goods_count": len(goods),
|
||||
@@ -365,10 +411,19 @@ class RechargeMixin:
|
||||
"goods": goods,
|
||||
"failed": failed,
|
||||
"task_detail": task_result,
|
||||
"task_detail_error": task_error,
|
||||
}
|
||||
self._mark_task(
|
||||
worker_db, task, "success" if goods else "failed", message, result
|
||||
)
|
||||
if not goods:
|
||||
reason = failed[0].get("message") if failed else "无候选商品"
|
||||
self._mark_task(
|
||||
worker_db, task, "failed", f"开通商品刷新失败: {reason}", result
|
||||
)
|
||||
return
|
||||
|
||||
message = f"已刷新开通商品 {len(goods)} 个"
|
||||
if failed:
|
||||
message += f",失败 {len(failed)} 个"
|
||||
self._mark_task(worker_db, task, "success", message, result)
|
||||
|
||||
def _execute_create_recharge_order(
|
||||
self,
|
||||
|
||||
@@ -78,7 +78,7 @@ export default function HuyaElitePage() {
|
||||
const [goods, setGoods] = useState<HuyaGoodsItem[]>([]);
|
||||
const [selectedGoodsId, setSelectedGoodsId] = useState('');
|
||||
const [rechargeGoods, setRechargeGoods] = useState<HuyaRechargeGoodsItem[]>([]);
|
||||
const [selectedRechargeSpu, setSelectedRechargeSpu] = useState('hy-5879340');
|
||||
const [selectedRechargeSpu, setSelectedRechargeSpu] = useState('');
|
||||
const [search, setSearch] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [starting, setStarting] = useState(false);
|
||||
@@ -240,7 +240,14 @@ export default function HuyaElitePage() {
|
||||
return accounts.filter((item) => !value || [item.uid, item.nickname, item.username, item.tag, item.game_name].some((v) => String(v || '').toLowerCase().includes(value)));
|
||||
}, [accounts, search]);
|
||||
const filteredGoods = useMemo(() => goods.map((item) => ({ ...item, label: `${item.name} / ${item.price ?? '-'}分 / ${item.remain_text || '动态库存'}` })), [goods]);
|
||||
const filteredRechargeGoods = useMemo(() => rechargeGoods.map((item) => ({ ...item, label: `${item.name || item.spu_id} / ${item.price ? `${(item.price / 100).toFixed(2)}元` : '价格动态'}` })), [rechargeGoods]);
|
||||
const filteredRechargeGoods = useMemo(() => rechargeGoods.map((item) => ({ ...item, label: `${item.name || item.spu_id} / ${item.price ? `${(item.price / 100).toFixed(2)}元` : '价格动态'} / ${item.spu_id}` })).sort((a, b) => Number(a.raw?.raw_order ?? 0) - Number(b.raw?.raw_order ?? 0) || a.id - b.id), [rechargeGoods]);
|
||||
// 默认选中列表里的第一个商品;已选商品从列表消失时同步回落到第一个
|
||||
useEffect(() => {
|
||||
if (!filteredRechargeGoods.length) return;
|
||||
if (!filteredRechargeGoods.some((item) => item.spu_id === selectedRechargeSpu)) {
|
||||
setSelectedRechargeSpu(filteredRechargeGoods[0].spu_id);
|
||||
}
|
||||
}, [filteredRechargeGoods, selectedRechargeSpu]);
|
||||
const exchangeCount = tasks.filter((task) => task.task_type === 'exchange_goods').length;
|
||||
|
||||
const accountColumns: TableProps<HuyaAccountItem>['columns'] = [
|
||||
|
||||
@@ -676,7 +676,7 @@ export default function HuyaTasksPage() {
|
||||
const rechargeGoodsOptions = useMemo(() => {
|
||||
return sortedRechargeGoods.map((item) => ({
|
||||
value: item.spu_id,
|
||||
label: `${item.name || item.spu_id}${item.price ? ` / ${formatPriceText(item.price)}` : ''}`,
|
||||
label: `${item.name || item.spu_id}${item.price ? ` / ${formatPriceText(item.price)}` : ''} / ${item.spu_id}`,
|
||||
}));
|
||||
}, [sortedRechargeGoods]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user