重构虎牙精英宝典协议与支付状态链路

This commit is contained in:
yml2213
2026-09-01 11:31:15 +08:00
parent 955ba45488
commit 684e16a07a
12 changed files with 937 additions and 51 deletions
@@ -0,0 +1,123 @@
# 虎牙精英宝典后端与 9.1 全流程抓包对照
## 结论
当前后端不是“完全不能用”,而是保留了旧版 HTTP/WUP 兜底链路;最新浏览器链路已经切换为两个长连接 WSS:活动/绑定通道和商城/支付通道。查询和下单的多数字段与抓包兼容,真正需要优先更新的是兑换闭环、支付后订单状态和权益回查。
## 对照矩阵
| 优先级 | 领域 | 9.1 实际链路 | 当前后端 | 判断 | 更新动作 |
|---|---|---|---|---|---|
| P0 | 支付监听 | `orderDetailV5(orderId)` 重复查询,状态最终为 50 | `queryUserOrderList`,同时尝试 `shopMiddleUI/revenueWebUI` | 不一致 | 增加 `orderDetailV5` 结构和方法,按订单号轮询;以订单状态 50/已支付时间作为完成依据 |
| P0 | 支付超时 | 未支付仍是待支付/取消状态 | `huya_runner_recharge.py:535-537` 将 timeout 标为 `success` | 业务错误 | 超时改为 `pending``timeout`,不能写成功;前端显示“等待支付超时” |
| P0 | WSS 会话 | 活动通道 `d35bf373-ws.va.huya.com`,商城通道 `wsapi.huya.com`;每个会话有 launch、register、confirm、递增 requestId | `HuyaBatchRunner` 全部调用 `HuyaHttpClient``cdnws.api.huya.com` 单次 HTTP | 架构不一致 | 优先实现可复用的 WSS sessionHTTP 保留为显式 fallback,并记录 fallback 原因和重试次数 |
| P1 | 商城身份字段 | 最新商城业务 `UserId.sHuYaUA=web&1.0.0&huya``sGuid` 为空 | HTTP `_build_user` 使用 `webh5&0.0.1&websocket...`,并从 Cookie 派生 `sGuid` | 字段不一致 | 商城 WSS 优先;HTTP fallback 也应拆分商城 UserId,按最新 UA/空 guid 构造 |
| P1 | 兑换前置 | `getActPrizeDetail(sid=2203,pid)`,随后才 `scoreExchangePrize` | `huya_runner_goods.py:327-329` 直接提交兑换 | 缺步骤 | 增加 `GetActPrizeDetailReq/Resp`;提交前校验 `isCanExchange`、活动时间、库存、今日/用户限制和积分余额 |
| P1 | 兑换回查 | 成功后依次回查 `getUserScore``getUserPrizeRecords``getEntityPrizeFieldMap``getModuleAddress` | 成功后立即结束任务 | 缺步骤 | 将扣分、记录和地址状态写入任务结果,并更新 `HuyaAccount.points` |
| P1 | 活动初始化 | `getActInfo``getActUserTaskDetail``getUserScore``getUserPrizeRecords` 等并行查询 | `query_act_tasks` 只调用 `getActTaskDetail` | 信息不完整 | 增加 snapshot 任务或让页面初始化批量创建这些查询任务 |
| P1 | 权益到账 | 支付完成后仍需回查宝典积分/用户任务 | 只回查商城订单列表 | 信息不完整 | 订单完成后调用积分和用户任务接口,明确“已支付”和“宝典权益到账”两个状态 |
| P2 | 商品详情 | `getGoodsInfoV5` 直接返回 `spu=hy-5879340``sku=5370360`、场景 4 | 当前会先从任务详情发现 SPU,再以 sku=0 查询详情 | 兼容但脆弱 | 固定业务默认值仅作候选;优先使用实时商品详情返回的 SKU,不依赖任务类型 67 |
| P2 | 商品详情 gameId | 最新 `getGoodsInfoV5` 请求的 `gameId` 为空字符串 | `huya_runner_recharge.py:385-395` 传入 `game_id="0"` | 细节不一致 | 查询商品详情改为空字符串;`createOrderV5``gameId="0"` 仍保持不变 |
| P2 | 下单字段 | `orderType=6``src=4``orderScene=4``bizType=5``gameCategoryId=507``gameId="0"` | `create_order` 已按相同字段写入 | 基本一致 | 保留,增加请求快照脱敏日志以便字段回归 |
| P2 | 支付提交 | `payOrderSubmitV5(order,Zfb,QrCode,callback,env)` 返回支付宝网关 URL | 当前 HTTP 手工构造 WUP,字段基本一致 | 兼容但非同链路 | 迁移到商城 WSS;未迁移前保留 HTTP fallback |
| P2 | 支付渠道 | 先 `listPayChannelV5`,返回 `Zfb/Weixin` | 直接使用配置渠道 | 缺运行时校验 | 下单前读取可用渠道,不支持配置值时回退或报错 |
| P2 | 配置 | 本次活动 `sid=2203`、绑定活动 `9271`、外部活动响应为 `17096` | `outer_act_id` 默认 `9504` 且执行器未实际使用 | 配置过期/死字段 | 默认值改为当前活动值或删除该字段;将外部活动 ID 从 `getLiveLinkParam` 响应传递,不手填签名 |
| P2 | 数据隔离 | 业务响应随账号、绑定状态和库存变化 | `HuyaGoodsSnapshot``HuyaRechargeGoodsSnapshot` 为全局快照 | 并发风险 | 至少按 `sid/spu` 隔离;兑换可用性必须以执行账号的实时查询为准 |
## 已对上的部分
- `ActivityUserId``lUid``sHuYaUA`、Cookie 前缀和空 Token 形态与 9.1 活动帧兼容。
- `scoreExchangePrize` 的主体字段 `userId/sid/pid/ip/clientEnv/source/isRole` 已有结构,样本中后三项为空、`isRole=1` 与默认值一致。
- 商城 `CreateOrderReqV5` 已覆盖抓包中的完整 tag 0-32;`orderType=6` 和场景 4 正确。
- `payOrderSubmitV5` 的回调 URL、`Zfb``QrCode` 和环境 map 已覆盖抓包字段。
- `CreateOrderReqV5` 的下单 `gameId="0"` 与抓包一致;只有商品详情查询的 `gameId` 应为空字符串。
- Cookie、baseinfo、WUP requestId、支付宝订单和签名没有被当作固定常量,方向正确。
## 推荐更新顺序(按依赖关系)
之前的顺序有两个问题:把支付状态修复放在协议补齐之前,且把 WSS 迁移放在写操作之后。这样会先扩大旧链路的行为面,无法判断是业务字段还是传输方式导致失败。建议按下面的依赖顺序推进,每一步都能独立验证和回滚。
### 0. 固化 9.1 契约和回归样本
整理脱敏的请求/响应 fixture,明确活动通道、商城通道、WUP tag、状态码和动态字段边界。先不改线上任务行为,只增加解码测试。验收条件:9.1 中的关键 RPC 能稳定解码,动态 Cookie、签名、订单号不会进入 fixture。
### 1. 先修配置和身份模型
`sid=2203``bindActId=9271``outerActId=17096``moduleId=20051``scene=4``sourceId=yellowcarlist` 统一为活动配置;商城 UserId 与活动 UserId 分开,商城使用最新 `sHuYaUA`/空 `sGuid` 规则。此步只影响参数构造,不启用新写操作。
### 2. 补齐只读协议方法和响应结构
优先实现并测试:
```text
getActInfo
getActUserTaskDetail
getActPrizeDetail
getEntityPrizeFieldMap
getModuleAddress
listPayChannelV5
orderDetailV5
```
只读方法齐全后,才能用服务端实时数据做兑换和支付前置判断;不要先改 `scoreExchangePrize` 或支付提交。
### 3. 建立统一传输层,再做只读灰度
抽象活动 WSS session 和商城 WSS session,复用 launch/register/confirm、requestId FIFO、断线和超时处理。先让查询接口走 WSS,失败时明确记录原因并 fallback 到 HTTP;连续验证成功后再切换写接口。不要在每个 runner 中分别临时建 WebSocket。
### 4. 先做快照和数据隔离
页面初始化改为一次读取活动信息、用户任务、积分、奖品和记录;商品/充值商品快照至少按 `sid + spu/pid` 隔离,执行账号始终以实时详情覆盖快照。此步完成后 UI 可以展示真实状态,但仍不触发兑换或支付。
### 5. 重写兑换状态机
严格按以下顺序:
```text
实时 getActPrizeDetail
-> 校验 isCanExchange/活动时间/库存/今日限制/用户限制/可用积分
-> scoreExchangePrize
-> getUserScore + getUserPrizeRecords
-> getEntityPrizeFieldMap + getModuleAddress
```
成功、已兑换、余额不足、库存不足、需要地址和未知错误分别落库;收到成功响应后使用幂等键阻止重复提交。只有四项回查完成,任务才标记为兑换完成。
### 6. 重写开通和支付状态机
严格按以下顺序:
```text
getGoodsInfoV5
-> listPayChannelV5
-> checkHyProtocolV5 / checkUserBuyAuth
-> createOrderV5(orderType=6)
-> payOrderSubmitV5(Zfb|Weixin, QrCode)
-> 立即 orderDetailV5 轮询
-> 支付完成后回查宝典积分和用户任务
```
`orderDetailV5` 必须先于 runner 改造完成;`queryUserOrderList` 只能作为兼容 fallback。支付状态拆成 `created/pending/paid/timeout/cancelled/error`,超时绝不能标记 `success`。二维码生成后任务保持 `running/pending`,不能因为 runner 线程尚未结束而隐藏二维码。
### 7. 最后接入 UI 和批量执行
UI 只调用统一任务 API:查询使用快照任务,兑换和开通使用状态机任务;通过任务 WebSocket 推送二维码、订单状态和最终权益状态。先单账号灰度,再开启并发批量,避免旧的全局商品快照污染多个账号。
### 8. 清理旧链路
连续灰度验证通过后,删除 `create_order` 的 orderType 猜测和支付列表轮询主路径;保留 HTTP fallback、失败原因、超时和回滚开关。最后再迁移旧版虎牙任务入口,避免影响非精英宝典功能。
## 不应照搬抓包的字段
UID、Cookie、guid、baseinfo、WUP requestId、支付宝 payOrderId、ctoken、二维码 URL、`t/code/sig`、用户点击时间和 RSA2 签名均为运行时动态值。它们只能来自当前登录态、前序响应或运行时生成,不能写入默认配置或测试 fixture。
## 本轮已实施
- 增加 `getActInfo``getActUserTaskDetail``getActPrizeDetail` 和详情响应结构。
- 兑换提交前读取实时详情和积分,校验可兑换状态、库存、时间窗口和余额;成功后回查积分和兑换记录。
- 增加 `orderDetailV5` 请求/响应结构,支付监听优先按订单号查询,旧订单列表作为 fallback。
- 支付超时改为终态 `timeout`,不再误报成功;支付完成后回查活动积分。
- 商城 HTTP fallback 的 `UserId` 改为 9.1 商城 UA/空 guid 形态;商品详情 `gameId` 改为空字符串,下单仍使用 `gameId="0"`
- 外部活动默认值由旧值更新为 `17096`,已有旧配置会在读取时迁移。
仍待下一轮实施:将批处理执行器从 HTTP fallback 迁移到统一活动/商城 WSS session;补齐 `listPayChannelV5``checkHyProtocolV5``getMyPromotion``calcOrderPromotion``checkUserBuyAuth` 的显式前置调用;按账号隔离商品快照。