From 64dc9a9116e78874a7f8f66cef564d224daeeb04 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sun, 4 Oct 2026 18:56:01 +0800 Subject: [PATCH] =?UTF-8?q?docs(query):=20=E8=B4=A6=E5=8F=B7=E3=80=8C?= =?UTF-8?q?=E4=B8=80=E9=94=AE=E6=9F=A5=E8=AF=A2=E3=80=8D=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=20=E2=80=94=E2=80=94=20=E6=95=B0=E6=8D=AE=E6=BA=90=E5=B7=B2?= =?UTF-8?q?=E7=94=A8=E7=94=9F=E4=BA=A7=E9=95=9C=E5=83=8F=E5=AE=9E=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 决策已定:两个套装属精英宝典(manual_id/rid 用后台配置)、异步任务。 ## 关键实证(镜像库真实数据) 「是否已拥有某套装」**能直接从斗鱼官方查**: query_limited_goods 的商品项里 count / foreverCount / storeStatus 是**账号维度**的。 实测「套装-兔兔白日梦」—— 已兑换的账号 558 是 count=0/storeStatus=0, 未兑换的 15407 是 count=96982/storeStatus=1。配合 countLimited=1(每账号限兑 1 次), 判定规则就是 storeStatus==0 → 已拥有。 ⚠️ 同时记下风险:storeStatus=0 也可能是全局售罄,UI 要写「不可再兑」并显示 count。 ## 另一个坑 任务类型 query_exchange_records **在生产从未运行过**(0 行); 真正的官方兑换记录接口是客户端的 exchange_records()(activity_client.py:950), **没有被任何任务接线**。一键查询要用它。 ## 文档内容 数据源矩阵(8 个客户端方法 + 参数来源)、任务设计(新类型 query_account_overview,只读、单项失败不整体失败、errors 如实展示)、 结果 JSON 结构、后端/前端/测试实现清单、判定规则、镜像库复现样本、风险。 注:本次只落设计不写实现 —— 上下文不足以一次干净做完, 半成品比未开始更糟。实现按本文档一次完成。 --- docs/设计-账号一键查询.md | 181 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 docs/设计-账号一键查询.md diff --git a/docs/设计-账号一键查询.md b/docs/设计-账号一键查询.md new file mode 100644 index 0000000..9991c47 --- /dev/null +++ b/docs/设计-账号一键查询.md @@ -0,0 +1,181 @@ +# 设计:账号「一键查询」(斗鱼官方实时) + +> 状态:**方案已定,待实现**。所有数据源已用**生产镜像库的真实数据**核实过。 +> 决策:① 两个套装都属于**精英宝典**,`manual_id`/`rid` **用后台配置**; +> ② 交互方式 = **异步任务**(复用现有 runner + WebSocket 推送)。 + +--- + +## 1. 目标 + +账号管理/详情页一个「一键查询」按钮 → 实时向斗鱼拉取该账号的**全部关键信息** → +汇总展示,并**高亮两个高价值套装是否已拥有**。 + +--- + +## 2. 关键实证发现(已核实,别再猜) + +### 2.1 「是否已拥有某套装」能直接从官方查 + +`query_limited_goods(manual_id, rid)` 返回的商品项里,**`count` / `foreverCount` / +`storeStatus` 是账号维度的**。实测同一个商品「套装-兔兔白日梦」: + +| 字段 | 账号 558(**已兑换**) | 账号 15407(未兑换) | +| --- | --- | --- | +| `count` | **0** | 96982 | +| `foreverCount` | **0** | 96982 | +| `storeStatus` | **0** | 1 | + +**判定规则**:`storeStatus == 0`(或 `count == 0`)→ **已拥有**;否则可兑换。 + +⚠️ 注意 `storeStatus` 也可能是「全局售罄/下架」导致 0,所以展示时**不要只写"已拥有"**, +建议写「**不可再兑**(已拥有或已售罄)」,并把 `count` 一起显示,避免误判。 + +### 2.2 两个套装的确切标识 + +| 商品名 | `commodityId` | `score`(积分价) | `countLimited` | +| --- | --- | --- | --- | +| 套装-兔兔白日梦 | `20260120QYOOB_bag10` | 1280 | **1**(每账号限兑 1 次) | +| 套装-狂战萌兔 | 待取(同法从官方拉) | 待取 | 待取 | + +两者均带 `eliteLimited = 1` → **属于精英宝典**(与决策① 一致)。 + +> 实现时**按 `commodityName` 匹配**(含「兔兔白日梦」「狂战萌兔」), +> 并把 `commodityName` 做成配置项,不要硬编码到代码里(以后可能加更多高价值商品)。 + +### 2.3 一个易踩的坑 + +`query_exchange_records` 这个任务类型**在生产从未运行过**(0 行)。真正的官方兑换记录 +接口是客户端的 **`exchange_records()`**(`activity_client.py:950`),它**没有被任何任务接线**。 +一键查询要用它。 + +--- + +## 3. 数据源矩阵(客户端方法都已存在) + +| 展示项 | 客户端方法 | 参数来源 | +| --- | --- | --- | +| 鱼翅余额 | `gold_account()` | 无 | +| 钱包兑换余额 | `exchange_balance()` | 无 | +| **积分 + 到期时间** | `query_points(uid=, ctn=)` | `account.uid`;`ctn` 来自 `acf_ccn()` | +| 精英宝典开通状态 | 现有 `query_elite_status` 任务的实现(见 runner) | 后台配置 | +| 电竞手册状态 + 积分 | `esports_user_info(manual_id=)` | 后台配置 | +| **兑换记录(官方)** | **`exchange_records(manual_id=, limit=, offset=)`** | 后台配置 | +| **限兑商品(含两套装是否已拥有)** | **`query_limited_goods(manual_id=, rid=)`** | 后台配置 | +| 小店余额 / 碎片 | `xpd_embed_query(act_alias=, rid=)` | 后台配置 | + +> 「现有 `query_elite_status` 的实现」需要实现时去 runner 里确认它调的是哪个方法 +> (我的检索没直接命中该方法名,可能在 runner 内联了请求)。 + +--- + +## 4. 任务设计 + +**新增任务类型**:`query_account_overview` + +- 走现有 `douyu_tasks` 体系(异步 + WebSocket 进度推送 + 离线可查) +- **只读**:不改任何账号状态、不发业务事件 + (纯查询不是"事实",按既有设计**例行查询不进时间线**,避免刷屏) +- 结果写入 `task.result`,结构见下 + +### 结果结构(建议) + +```jsonc +{ + "queried_at": "2026-10-04T18:00:00Z", + "assets": { + "gold_balance": 38, // 鱼翅 + "exchange_balance": 120, // 钱包兑换余额 + "points": 990, + "points_expire_at": "2026-12-01 00:00:00" // 来自 willExpireTime + }, + "elite": { "opened": true, "expire_time": "..." }, + "esports": { "opened": false, "points": null }, + "xpd": { "balance": null, "fragments": null }, + "high_value_goods": [ // 🎯 重点 + { + "name": "套装-兔兔白日梦", + "commodity_id": "20260120QYOOB_bag10", + "score": 1280, + "owned": true, // storeStatus == 0 || count == 0 + "store_status": 0, + "count": 0, + "start_time": "1769153181", + "end_time": "1795967999" + }, + { "name": "套装-狂战萌兔", "...": "同上" } + ], + "limited_goods_total": 87, // 商品总数(便于判断是否拿到了完整列表) + "exchange_records": [ /* 官方兑换记录前 N 条 */ ], + "errors": { "esports": "该账号未绑定电竞手册" } // 单项失败不整体失败 +} +``` + +**关键设计点**: + +1. **单项失败不整体失败** —— 每个查询独立 try/except,失败写进 `errors[项]`, + 其余照常返回。账号没绑定电竞手册是常态,不该让整次查询失败。 +2. **`errors` 必须如实展示** —— 避免"没查到"被误读成"没有"。 +3. `exchange_records` 默认取前 20 条,完整记录量大。 + +--- + +## 5. 实现清单 + +### 后端 + +| 文件 | 改动 | +| --- | --- | +| `web/backend/services/douyu_runner_*.py` | 新增 `query_account_overview` 的 handler(聚合各查询) | +| 任务类型注册处 | 加入 `query_account_overview`(**放在只读/查询类**,不要放进写操作类) | +| `web/backend/routers/douyu.py` | 建任务时允许该类型;payload 只需 `account_id` | +| 后台配置 | 取精英宝典的 `manual_id` / `rid`(复用现有配置项,不新增硬编码) | +| 高价值商品名单 | 加配置项,默认 `["兔兔白日梦", "狂战萌兔"]` | + +### 前端 + +| 文件 | 改动 | +| --- | --- | +| `web/frontend/src/pages/AccountDetailPage.tsx` | 顶部加「一键查询」按钮;结果面板展示资产/开通/绑定/兑换记录;**两个套装单独高亮卡片**(已拥有 / 可兑换 + 积分价 + 活动时间) | +| `web/frontend/src/pages/douyu-tasks/DouyuTasksPage.tsx` | 新任务类型的展示(结果渲染成上面的面板,而不是原始 JSON) | +| (可选,强烈建议)`AccountsPage.tsx` | 列表加「高价值套装」列/标记,**支持筛选"拥有兔兔白日梦的账号"** —— 这才是"找高价值账号"的真正用法 | + +### 测试 + +| 用例 | 要点 | +| --- | --- | +| 高价值商品判定 | `storeStatus=0` → `owned=true`;`=1` → `false`;**缺该项时不能报 owned** | +| 单项失败隔离 | 电竞查询抛异常 → 其余字段仍返回,且 `errors.esports` 有值 | +| 只读性 | 该任务**不改**任何 `accounts.*` 字段、**不发**任何 `account_events`(用既有的事件计数断言) | +| 结果结构 | `high_value_goods` 一定包含配置里的商品名(即使官方列表里没有 → 标记 `"missing": true`,不能默默消失) | +| 契约 | 用真实返回样本(已存于镜像库)做解析回归,避免字段改名静默失效 | + +--- + +## 6. 实测样本可复现(镜像库) + +```sql +-- 已兑换的账号(用于验证判定规则) +SELECT DISTINCT account_id FROM douyu_tasks +WHERE task_type='exchange_goods' AND status='success' + AND result LIKE CONCAT('%', 0xE58594E58594E799BDE697A5E6A2A6, '%') LIMIT 5; +-- → 558, 607, 2682, 3094, 3090 + +-- 未兑换但跑过商品查询的账号 +-- → 15407, 15406, 15401 +``` + +> 注意:SQL 里中文用 `LIKE '%兔兔白日梦%'` 在多层引号里会被 shell 搞坏, +> **要写成 `0x...` 十六进制**(上面的写法)。 + +--- + +## 7. 待确认 / 风险 + +| 项 | 说明 | +| --- | --- | +| `rid`(房间号) | 决策是用后台配置。要确认**同一套配置对所有账号都适用**(房间号通常绑定活动,不随账号变) | +| 狂战萌兔的 `commodityId` | 实现时从官方列表取,不要写死 | +| 「已拥有」的准确措辞 | `storeStatus=0` 也可能是售罄/下架,UI 上应写「不可再兑」并显示 `count` | +| 是否发事件 | 本方案**不发**(纯查询)。若希望"查过"留痕,可单独加一个轻量事件,但要克制 | +| 批量查询 | 用户尚未确认。若要做,形态应是「勾多个账号 → 批量建任务 → 汇总对比表」 |