Files
Astral/docs/UpsertUserAll落库排查报告.md
T
Txiaochen 41909b9169 docs: WaHappy 开发文档
孤儿分支,只含 docs/。内容包括:
- 项目总览 / 主脚本编写指南 / 日志指南
- 上传成绩开发文档 / UpsertUserAll 踩坑清单(28 条)
- 真机 Hook 对照报告 / 实跑验证记录
- 购买功能票流程(含被服务端拒绝的排查过程)
  · 扫码登录协议技术规范
2026-10-06 14:42:32 +08:00

175 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# UpsertUserAll 落库排查报告
> **背景**:`python upsert.py "<二维码>" --send` 修复闸门后能真实发包,
> 服务端返回 `returnCode=1`,但**数据未写入服务器**。
> 本文档记录已完成的全部排查与结论。
---
## 一、结论速览
| 项 | 状态 |
|---|---|
| 闸门放行 | 已修复,`returnCode=1` |
| 报文与真机对齐 | **非预期差异归零** |
| 数据落库 | **未落库**(`point`/`playCount` 回查不变) |
| 根因 | **未确定**,已排除 6 类可能 |
---
## 二、已修复的 5 个 Bug
### Bug 1:`upsert.py` 漏传 `confirm`(你遇到的直接原因)
`upsert.py` 原来只传 `dry_run=not opts["send"]`,但 `UpsertUserAll` 的规则是
`confirm=None` 时取 `config.UPSERT_CONFIRM`(默认 `False`),所以照样被拦。
```python
# 修复前
dry_run=not opts["send"],
# 修复后
dry_run=not opts["send"],
confirm=bool(opts["send"]), # 关键
```
### Bug 2:`userExtend` 缺 2 个字段
| | Get 侧 | Upsert 侧 |
|---|---|---|
| 键数 | 14 | **16** |
| 差 | — | `isPhotoAgree`、`isGotoCodeRead` |
这两个字段**只在 Upsert 侧出现**,必须补上。
### Bug 3:`userRating.udemae` 有 7 个冗余大写字段
服务器 Get 响应里 `npcWinNum` 与 `NpcWinNum` **并存**(23 键):
```json
// Get 侧(source/2026-10-05.log:87)
{ "npcWinNum": 0, ..., "NpcWinNum": 0, "NpcLoseNum": 0, ... } // 23 键
// Upsert 侧(source/2026-10-05.log:123)
{ "npcWinNum": 0, ... } // 16 键
```
源码 `Net.VO.Mai2/UserUdemae.cs` 只声明了**小写的 16 个**,所以大写那批是
服务端冗余输出,回传时必须裁掉。修复前请求体比真机大 119 字节。
### Bug 4:`userCharacterList` 排序错
| | 顺序 |
|---|---|
| `charaSlot` | [395, 101, 400402, 400401, 200401] |
| 真机 `userCharacterList` | **[101, 395, 200401, 400401, 400402]**(按 characterId 升序) |
回传时按 `characterId` 升序,不是按 `charaSlot` 顺序。
### Bug 5:扫码用的 chipID 形式错
| 用途 | 正确值 | 源码依据 |
|---|---|---|
| aimedb 扫码 | `XXXX-XXXXXXXXXXX`(**完整**) | `ChimeReaderManager.cs:250` `KeychipId.Value` |
| 游戏服务器 | `TESTCLIENT1`(短值) | `PacketGetGameSetting.cs:23` `KeychipId.ShortValue` |
我原来扫码也用了短值。已按源码修正为完整形式。
---
## 三、报文对齐最终状态
```
顶层键 : 7/7
36 节点形态 : 36/36
userData : 82/82 键
userPlaylog : 110/110 键
userExtend : 16/16 键
udemae : 16/16 键
非预期差异 : 0
请求体大小 : 17158 vs 17161 字节(差 3 字节,来自数值位数)
```
**深度递归对比(逐字段、逐元素)后,除「不同次登录必然不同」的值外,零差异。**
---
## 四、已排除的可能(6 类)
| # | 假设 | 验证方式 | 结果 |
|---|---|---|---|
| 1 | 时序问题(写入有延迟) | 发送后 T+0 / T+20s / T+45s 三次回查 | 都不变 |
| 2 | 差分节点为空导致「看不出变化」 | 直接改绝对值(`point` 4110→4111) | 仍不写 |
| 3 | 字段缺失/多余 | 深度递归键集合 + 值对比 | 已归零 |
| 4 | `number` 请求头缺失 | 补上 `Packet.cs:170` 要求的头 | 仍不写 |
| 5 | `playSpecial` 值不对 | 查源码 `CalcSpecialNum` = 硬件标志位 | 非根因 |
| 6 | 缺少 `GetUserNewItemList` 前置 | 完整复刻真机 121→123 流程 | 仍不写 |
### 关于第 2 项的探测细节
这是最关键的一次探测 —— 直接 `patch` 把 `userData.point` 从 4110 改成 4111 发上去:
```
baseline point = 4110
上传成绩成功:returnCode=1
T+ 0s point = 4110
T+20s point = 4110
T+45s point = 4110
```
**连最简单的整数字段都不写**,说明不是「数据不对」而是「整包没被接受」。
---
## 五、当前判断
服务端**收下了**请求(返回正确的 `apiName`),但**没有应用**。
可能的方向(未验证):
1. **账号/机台绑定校验** —— token 可能绑定了特定 keychip,而 `XXXX-XXXXXXXXXXX`
只是文档里的示例值,不是**你实际机台**的 keychip。
若服务端校验「token 对应的机台 ≠ 请求里的 chipID」,就会静默拒收。
2. **需要真实游玩产生的会话状态** —— 真机的 Upsert 发生在**真实游玩之后**,
服务端可能记录了「本次 loginId 下有一局真实游玩」才允许结算。
纯网络脚本没有这个前置状态。
3. **服务端侧的风控/审计** —— 真机与脚本的 TLS 指纹、请求节奏等特征不同。
---
## 六、需要你提供的信息
要继续定位,最有价值的是:
| # | 信息 | 为什么关键 |
|---|---|---|
| 1 | **你机台的真实 Keychip ID** | 若 `XXXX-XXXXXXXXXXX` 不是你的机台,方向 1 就成立 |
| 2 | 该机台在 `keychip.csv` 里的对应行 | 可确认 keychip 与店铺/区域的绑定关系 |
| 3 | 真机跑一局后立刻导出 Upsert 报文 | 可拿同账号同机台的最新报文做 1:1 对比 |
> `source/keychip.csv` 有 229KB 全量机台对照表,如果你能告诉我机台所在的
> 店铺 ID,我可以从中查出正确的 keychip。
---
## 七、本次排查的正面成果
即使尚未落库,这次排查也修掉了 **5 个真实 Bug**,其中 3 个(`userExtend` 缺字段、
`udemae` 冗余字段、`userCharacterList` 排序)是**只有做深度值对比才能发现**的:
- 修复前:报文与真机有 11 处非预期差异
- 修复后:**0 处**
自检 129/129 仍全绿。
---
## 八、复现方式
```powershell
# 1) 真实发送 + 落库验证(会写服务器,不可撤销)
python upsert.py "<二维码>" --send
# 2) 零网络自检
python selftest.py
```