docs: WaHappy 开发文档

孤儿分支,只含 docs/。内容包括:
- 项目总览 / 主脚本编写指南 / 日志指南
- 上传成绩开发文档 / UpsertUserAll 踩坑清单(28 条)
- 真机 Hook 对照报告 / 实跑验证记录
- 购买功能票流程(含被服务端拒绝的排查过程)
  · 扫码登录协议技术规范
This commit is contained in:
Txiaochen
2026-10-06 14:42:32 +08:00
commit 41909b9169
15 changed files with 34059 additions and 0 deletions
+133
View File
@@ -0,0 +1,133 @@
# 实跑验证记录(真实发包 + 落库确认)
> 本文记录本工程**第一次真实跑通**「扫码登录 → 取数 → 上传成绩 → 落库回查 → 登出」的完整证据。
> 执行方式:`python upsert.py <二维码> --send`(等价于下面的驱动方式)。
---
## 一、结论
| 环节 | 结果 |
|---|---|
| 扫码登录 | ✅ user_id=12345678,token 获取成功 |
| 用户预览 | ✅ errorId=0、banState=0、isLogin=False |
| 正式登录 | ✅ returnCode=1,loginId=7693236821794227665 |
| 抓基线快照 | ✅ 19 个节点全部成功,失败 0 个 |
| 组包 + 发包前自检 | ✅ 0 问题 |
| 冷却 | ✅ 等待 60.0 秒(模拟真机节奏) |
| **上传成绩** | ✅ **returnCode=1** |
| **落库回查** | ✅ **userData.playCount 1260 → 1261** |
| 登出 | ✅ returnCode=1 |
**这是关键突破**:之前的失败模式是「returnCode=1 但没落库」。
本次是**回码成功 + 重新拉服务器数据确认数值真的变了**,两者同时成立。
---
## 二、时间线(2026-10-06 01:28)
```
01:28:01 扫码登录成功 user_id=12345678
01:28:01 用户登录成功 loginId=7693236821794227665
loginDateTime=1791250081 dateTime=1791221281
01:28:03 结算快照抓取完成 成功 19 个节点,失败 0 个
01:28:04 角色快照取自服务器 395:3803 / 101:4458 / 400402:137 / 400401:137 / 200401:137
01:28:04 发包前自检通过
01:28:04 上传成绩冷却 等待 60.0 秒后再发送(模拟真机节奏)
01:29:04 冷却结束 共等待 60.0 秒
01:29:04 上传成功 returnCode=1
01:29:04 落库回查:
playCount 1260 -> 1261
currentPlayCount 188 -> 189
lastPlayDate 2026-10-05 22:32:21 -> 2026-10-06 01:28:04
musicId=143 level=4 playCount -> 11
01:29:05 【落库成功】userData.playCount 已 +1
01:29:05 用户登出成功 returnCode=1
```
---
## 三、落库证据(上传后重新拉服务器数据)
上传成功后**不登出、立刻**调 `GetUserDataApi` + `GetUserMusicApi` 回读,服务器返回:
```json
{
"userData": {
"playCount": 1261,
"currentPlayCount": 189,
"lastPlayDate": "2026-10-06 01:28:04",
"lastLoginDate": "2026-10-06 01:28:01",
"eventWatchedDate": "2026-10-06 01:28:01",
"lastPlaceId": 0,
"point": 4460,
"charaSlot": [395, 101, 400402, 400401, 200401]
}
}
```
```
musicId=143 level=4 -> playCount = 11
```
对照**上传前**的基线:`playCount=1260`、`currentPlayCount=188`、`(143,4).playCount=10`。
三项全部 +1,**证明服务端真的把这份报文写进去了**。
同时确认:
- `point` 保持 4460 不变 —— 与「`userGetPointList` / `userLoginBonusList` 留空」的预期一致
- `lastLoginDate` = 登录时刻(01:28:01),`lastPlayDate` = 上报时刻(01:28:04),两者分离正确
- `charaSlot` 未被破坏
---
## 四、这次跑通依赖的关键实现点
1. **`number` 请求头**:先跑 `Ping()` 取得往返毫秒,之后所有请求带上(本次 47 ms)
2. **Cookie 复用**:全程同一个 `MaimaiNetClient`,`UserLoginApi` 的 `Set-Cookie: JSESSIONID` 自动带入后续请求
3. **userData 整表覆盖**:先抓 19 节点快照作基线,再按本局演算 +1 后整表回传
4. **差分节点只回传变化项**:`userMusicDetailList` 只回传 (143,4) 那条、`userMapList` 只回传 550003、
`userCharacterList` 只回传 5 只组队角色
5. **11 条位串与数组长度严格对齐**:`isNewFavoriteList="22212"` 等全部复刻真机
6. **`playSpecial` 用 `CalcSpecialNum=1024`**:与真机位模式一致
7. **`characterLevelN` 用游玩前等级**:playlog 3803/4458/137,userCharacterList 才是 3804/4459/138
---
## 五、复现方式
```bash
# 1) 预检(不发包,零副作用)
python upsert.py "<二维码>" --debug
# 2) 真实上传(★ 会把成绩写进服务器,不可撤销)
python upsert.py "<二维码>" --send
# 想跳过 60 秒冷却
python upsert.py "<二维码>" --send --cooldown 0
# 想跳过上传后的落库回查
python upsert.py "<二维码>" --send --no-verify
```
`--send` 会自动做落库回查:重新拉 `GetUserDataApi` + `GetUserMusicApi`,
对比 `playCount` 有没有 +1,并打印 `【落库成功】` 或 `【疑似未落库】`。
> ★ 建议先用 `--debug` 跑一次预检:DEBUG 日志会完整打印请求头、请求体、响应体,
> 便于逐字段核对。代价是日志文件会很大(本次 DEBUG 日志约 200 万行)。
---
## 六、账号状态变化(本次真实写入的内容)
| 项 | 上传前 | 上传后 |
|---|---|---|
| `userData.playCount` | 1260 | **1261** |
| `userData.currentPlayCount` | 188 | **189** |
| `userData.lastPlayDate` | 2026-10-05 22:32:21 | **2026-10-06 01:28:04** |
| `userMusicDetailList` (143, Re:Master) playCount | 10 | **11** |
| `userMapList` 550003 distance | 1457000 | **1468000**(+11000) |
| `userCharacterList` 5 只组队角色 | 3803/4458/137/137/137 | **3804/4459/138/138/138**(+1) |
| `userData.point` | 4460 | 4460(不变) |
写入的 `userPlaylogList[0]` 是 `musicId=143`(Re:Master)全 Miss 的失败局,
判定数 441/19/192/0/3,与 `config.PLAY_*` 一致。