docs: WaHappy 开发文档
孤儿分支,只含 docs/。内容包括: - 项目总览 / 主脚本编写指南 / 日志指南 - 上传成绩开发文档 / UpsertUserAll 踩坑清单(28 条) - 真机 Hook 对照报告 / 实跑验证记录 - 购买功能票流程(含被服务端拒绝的排查过程) · 扫码登录协议技术规范
This commit is contained in:
+133
@@ -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_*` 一致。
|
||||
Reference in New Issue
Block a user