fix(huya): 一键查询统一最新 CK 探针与 HTTP 快速通道

This commit is contained in:
yml2213
2026-10-17 16:45:32 +08:00
parent ef6663beae
commit 8fac4a1d04
2 changed files with 114 additions and 86 deletions
+29 -20
View File
@@ -230,9 +230,7 @@ def _runner(monkeypatch, shop_client, activity_client=None):
runner = HuyaBatchRunner(db=None, batch_id="test-batch", task_type="query_all")
marks: list[tuple[str, str, dict | None]] = []
logs: list[tuple[str, str]] = []
monkeypatch.setattr(
runner, "_shop_client", lambda uid, cookie: shop_client, raising=False
)
monkeypatch.setattr(runner, "_http_client", lambda: shop_client, raising=False)
if activity_client is not None:
monkeypatch.setattr(
runner,
@@ -324,7 +322,10 @@ def test_query_all_fills_expire_time_from_season(monkeypatch):
)
runner, marks, _logs = _runner(monkeypatch, shop_client, activity_client=object())
monkeypatch.setattr(
runner, "_resolve_bind_status", lambda **kwargs: (None, {}), raising=False
runner,
"_probe_bind_status",
lambda **kwargs: (_FakeBindStatus(status=601, msg="活动已下线"), {}),
raising=False,
)
account = HuyaAccount(uid=str(UID), cookie=COOKIE)
@@ -606,7 +607,7 @@ def test_query_all_combines_points_elite_and_role(monkeypatch):
runner, marks, _logs = _runner(monkeypatch, shop_client, activity_client=object())
monkeypatch.setattr(
runner,
"_resolve_bind_status",
"_probe_bind_status",
lambda **kwargs: (_FakeBindStatus(), {"bind_status_source": "outer"}),
raising=False,
)
@@ -660,7 +661,10 @@ def test_query_all_reports_not_opened_without_order_lookup(monkeypatch):
shop_client = _StubClient(redeem=_redeem(REDEEM_NOT_OPENED_HEX))
runner, marks, _logs = _runner(monkeypatch, shop_client, activity_client=object())
monkeypatch.setattr(
runner, "_resolve_bind_status", lambda **kwargs: (None, {}), raising=False
runner,
"_probe_bind_status",
lambda **kwargs: (_FakeBindStatus(status=601, msg="活动已下线"), {}),
raising=False,
)
account = HuyaAccount(uid=str(UID), cookie=COOKIE)
@@ -669,13 +673,13 @@ def test_query_all_reports_not_opened_without_order_lookup(monkeypatch):
)
status, message, result = marks[-1]
# 角色接口挂了不影响宝典/积分结论
# 角色接口业务失败不影响宝典/积分结论
assert status == "success"
assert result["elite_opened"] is False
assert account.elite_opened is False
assert account.elite_open_time is None
assert "未开通宝典" in message
assert "角色信息接口无响应" in message
assert "活动已下线" in message
# 未开通不查订单(省一次请求)
assert [name for name, _kwargs in shop_client.calls] == ["get_redeem_need_goods"]
@@ -684,9 +688,12 @@ def test_query_all_fails_when_points_center_unreachable(monkeypatch):
shop_client = _StubClient(redeem=None)
runner, marks, _logs = _runner(monkeypatch, shop_client, activity_client=object())
monkeypatch.setattr(
runner, "_resolve_bind_status", lambda **kwargs: (None, {}), raising=False
runner,
"_probe_bind_status",
lambda **kwargs: (_FakeBindStatus(status=601, msg="活动已下线"), {}),
raising=False,
)
account = HuyaAccount(uid=str(UID), cookie=COOKIE)
account = HuyaAccount(uid=str(UID), cookie=COOKIE, status="imported")
runner._execute_query_all(
None, None, account, {"uid": UID, "cookie": COOKIE}, {"bind_act_id": "9271"}
@@ -695,9 +702,9 @@ def test_query_all_fails_when_points_center_unreachable(monkeypatch):
status, message, result = marks[-1]
assert status == "failed"
assert "积分中心接口无响应" in message
# 两条通道都没回包:CK 失效标记跟着失败结果一起落库(前端整行标红)
assert result["ck_expired"] is True
assert account.status == "ck_expired"
assert result.get("ck_expired") is None
# 没有查到任何东西时不能把账号标成 all_queried
assert account.status == "imported"
def test_query_all_flags_ck_expired_when_role_lookup_has_no_response(monkeypatch):
@@ -705,13 +712,16 @@ def test_query_all_flags_ck_expired_when_role_lookup_has_no_response(monkeypatch
真机日志(logs/app-2026-10-15.log,hy_297838517)里
``webActUI.checkUserBindGameAccount`` 的响应只有 ``newdata keys: ['']``(没有 tRsp),
``_resolve_bind_status`` 因此返回 None;积分中心走另一条通道却还能返回 200,
``_probe_bind_status`` 因此返回 None;积分中心走另一条通道却还能返回 200,
所以不能凭「积分查到了」就当成功,必须打 ck_expired 让页面标红。
2026-10-17 起一键查询也改成先验 CK:判定失效后立即停止,
不再白跑积分中心和宝典接口。
"""
shop_client = _StubClient(redeem=_redeem(REDEEM_NOT_OPENED_HEX))
runner, marks, _logs = _runner(monkeypatch, shop_client, activity_client=object())
monkeypatch.setattr(
runner, "_resolve_bind_status", lambda **kwargs: (None, {}), raising=False
runner, "_probe_bind_status", lambda **kwargs: (None, {}), raising=False
)
account = HuyaAccount(uid=str(UID), cookie=COOKIE)
@@ -720,13 +730,12 @@ def test_query_all_flags_ck_expired_when_role_lookup_has_no_response(monkeypatch
)
status, message, result = marks[-1]
# 宝典/积分查到了,任务本身不算失败(结果照样展示),但 CK 失效标记必须带上
assert status == "success"
assert status == "error"
assert result["ck_expired"] is True
assert result["ck_expired_reason"] == "角色信息接口无响应"
assert account.status == "ck_expired"
assert "角色信息接口无响应" in message
assert "疑似 CK 已失效" in message
assert "CK 已失效" in message
assert shop_client.calls == []
def test_query_all_keeps_business_error_out_of_ck_expired(monkeypatch):
@@ -735,7 +744,7 @@ def test_query_all_keeps_business_error_out_of_ck_expired(monkeypatch):
runner, marks, _logs = _runner(monkeypatch, shop_client, activity_client=object())
monkeypatch.setattr(
runner,
"_resolve_bind_status",
"_probe_bind_status",
lambda **kwargs: (_FakeBindStatus(status=601, msg="活动已下线"), {}),
raising=False,
)
+85 -66
View File
@@ -263,7 +263,12 @@ class EliteMixin:
account_info: dict,
config_info: dict,
):
"""一键查询:角色绑定 + 精英宝典开通 + 积分(勾选账号批量跑)。"""
"""一键查询:先验 CK,再查角色绑定 + 精英宝典开通 + 积分。
与「查询角色 / 查询积分」保持同一套最新链路:
- CK 走 HTTP 探针 + outer_act_id + 15 秒;无回包立即判失效并返回;
- 积分/宝典直接走 HTTP,不再为一次查询建 WSS 商城会话。
"""
uid = self._resolve_uid(account_info)
if not uid:
self._mark_task(worker_db, task, "failed", "无法从账号或 Cookie 解析 yyuid")
@@ -277,14 +282,86 @@ class EliteMixin:
result: dict[str, Any] = {"uid": uid}
failures: list[str] = []
message_parts: list[str] = []
role_name = ""
# ---- 积分 + 宝典开通(积分中心同一个接口) ----
shop_client: Any = self._shop_client(uid, cookie)
# ---- 角色绑定 / CK 有效性(先做,失效就不空跑后面两个接口) ----
b_act_id_int = self._to_int(
self.payload.get("bind_act_id") or config_info.get("bind_act_id")
)
if not b_act_id_int:
failures.append("未配置绑定 bActId")
result["ck_valid"] = False
else:
outer_act_id = str(
self.payload.get("outer_act_id")
or config_info.get("outer_act_id")
or ""
).strip()
bind_status, bind_query_result = self._probe_bind_status(
uid=uid,
cookie=cookie,
b_act_id_int=b_act_id_int,
outer_act_id=outer_act_id,
)
if bind_status is None:
account.status = "ck_expired"
account.updated_at = datetime.now(UTC)
result.update(
{
"bind_act_id": b_act_id_int,
"failures": failures + ["角色信息接口无响应"],
"ck_expired": True,
"ck_expired_reason": "角色信息接口无响应",
}
)
self._mark_task(
worker_db,
task,
"error",
"虎牙角色信息接口无响应(CK 已失效,请重新登录或重新导入 Cookie)",
result,
)
return
result.update(
{
"bind_act_id": b_act_id_int,
"ck_valid": int(getattr(bind_status, "status", 0) or 0) == 200,
}
)
if bind_status.status != 200:
failures.append(
bind_status.msg or f"角色信息查询失败: {bind_status.status}"
)
else:
role_info = self._bind_role_result(bind_status)
role_name = role_info.get("role_name") or ""
result.update(
{
**role_info,
**self._bind_change_state(bind_status),
**bind_query_result,
}
)
if role_name:
account.game_name = role_name
account.game_channel = (
self._role_channel(bind_status) or account.game_channel
)
message_parts.append(f"角色 {role_name}")
else:
message_parts.append("未绑定角色")
# ---- 积分 + 宝典开通(积分中心同一个接口,直接 HTTP) ----
shop_client: Any = self._http_client()
goods_num = (
self._to_int(self.payload.get("goods_num")) or POINTS_REDEEM_GOODS_NUM
)
redeem = shop_client.get_redeem_need_goods(
uid=uid, guid="", cookie=cookie, goods_num=goods_num
uid=uid,
guid="",
cookie=cookie,
goods_num=goods_num,
timeout=10.0,
)
points: int | None = None
opened: bool | None = None
@@ -338,68 +415,8 @@ class EliteMixin:
opened_text = "未开通宝典"
message_parts.append(opened_text)
# ---- 角色绑定(活动通道) ----
b_act_id_int = self._to_int(
self.payload.get("bind_act_id") or config_info.get("bind_act_id")
)
role_name = ""
# 角色查不到 = 这个 CK 已经不认了(模块头里有真机依据),整行要标红提醒
ck_expired = False
ck_expired_reason = ""
if not b_act_id_int:
failures.append("未配置绑定 bActId")
else:
outer_act_id = str(
self.payload.get("outer_act_id")
or config_info.get("outer_act_id")
or ""
).strip()
bind_client: Any = self._http_client()
bind_status, bind_query_result = self._resolve_bind_status(
client=bind_client,
uid=uid,
cookie=cookie,
b_act_id_int=b_act_id_int,
outer_act_id=outer_act_id,
timeout=15.0,
)
if bind_status is None:
ck_expired = True
ck_expired_reason = "角色信息接口无响应"
failures.append(ck_expired_reason)
elif bind_status.status != 200:
failures.append(
bind_status.msg or f"角色信息查询失败: {bind_status.status}"
)
else:
role_info = self._bind_role_result(bind_status)
role_name = role_info.get("role_name") or ""
result.update(
{
"bind_act_id": b_act_id_int,
**role_info,
**self._bind_change_state(bind_status),
**bind_query_result,
}
)
if role_name:
account.game_name = role_name
account.game_channel = (
self._role_channel(bind_status) or account.game_channel
)
message_parts.append(f"角色 {role_name}")
else:
message_parts.append("未绑定角色")
account.points = points if points is not None else account.points
# CK 失效时账号状态单独标出来(前端「状态」列据此显示红色「CK 失效」)
account.status = "ck_expired" if ck_expired else "all_queried"
account.updated_at = datetime.now(UTC)
result["failures"] = failures
if ck_expired:
result["ck_expired"] = True
result["ck_expired_reason"] = ck_expired_reason
ck_hint = ";疑似 CK 已失效,请重新登录或重新导入 Cookie" if ck_expired else ""
fetched_anything = points is not None or role_name or opened is not None
if not fetched_anything:
@@ -407,12 +424,14 @@ class EliteMixin:
worker_db,
task,
"failed",
(";".join(failures) or "一键查询全部失败") + ck_hint,
";".join(failures) or "一键查询全部失败",
result,
)
return
account.status = "all_queried"
account.updated_at = datetime.now(UTC)
message = "|".join(message_parts) or "一键查询完成"
if failures:
message += f";部分失败: {';'.join(failures)}"
self._mark_task(worker_db, task, "success", message + ck_hint, result)
self._mark_task(worker_db, task, "success", message, result)