feat(upserver): 保存M2客户端KV迁移至本人统计写检查点

保存独立KV用户服务、固定会话存档与原业务接线,以及C3d资料缓存和C3e本人统计写。
保留原PF/AGG路线、业务公式和时序;榜单读取、好友资料、公告、匹配及首场景闸仍待完成。

验证:ServiceHost两条件各33组646断言;Unity CLI Loop编译0错误269警告。
包含相关方案、评审裁决、测试及状态文档。未操作服务器、正式账号或打包。
This commit is contained in:
Liubing\LB
2026-09-30 19:19:05 +08:00
parent 1c0cdf7a99
commit 6813da149a
511 changed files with 151858 additions and 2376 deletions

View File

@@ -0,0 +1,38 @@
# M2 CP2 正式接线设计输入与新增阻断(待完整方案审查)
日期:2026-09-29。继承修订2及 CP1 限定关闭裁决。本文件记录只读源码核查和拟议边界,**不是 CP2 编码批准**;不得据此改生产代码。正式地址固定 `http://s101.local:18080/`,Unity 仅 CLI Loop,不用 UnitySkills,暂不打包。
## 首批账号裁决
用户本轮明确选定“仅已核准且已有 KV 档的账号”,并确认首批名单**仅 101、102**。因此正式 KV 入口须在认证前核准规范化十进制 ID 为 101/102,认证后再核同一 ID、`newCreate == false`、现存 KV 档及完整 Schema;任一失败停止并排水关闭,不创建空档、不迁移其他账号历史档、不覆盖未知数据。101/102 的既有受控许可不是正式入口的授权机制;正式清单的来源、变更方式和校验文件须在完整方案中单列。其他账号遇 `SaveBackend=kv` 应报错停止,不能静默回退到 PF 或为其自动建档。实际正式账号请求/写入仍不在本检查点。
## 方案选择
| 方案 | 结果 | 裁决方向 |
|---|---|---|
| A:启动前捕获 backend,KV 分支不创建 PF 组件;同一 Context 的 S101 认证、KV Owner 和 API 租约;加载后发布身份与原 handle | 沿用 CP1 原对象/关闭屏障;需完成正式实例准入、网络路由和旧调用审计 | 推荐作为完整 CP2 方案主体,待审查 |
| B:仍创建 PF 组件但在 Awake 内按 backend 跳过 | 保留组件引用可掩盖旧调用,生命周期和旧定时器容易复活;不能证明运行引用归零 | 不采用 |
| C:KV 配置直接调用 Editor 许可循环/验证工厂并开放全部 API | 测试专用权限与正式账号混用,路由无边界 | 不采用 |
用户本轮选定 CP2 首段验收仅含非 AGG 的正式 Bootstrap→登录→31 模块 Apply→原 handle 交接→退出排水,**在进入 `FishingStage` 前停住**;Mail/IAP、业务 API 和公开授权事件先不触发。首场景可运行是下一独立验收门。`FishingStage` 内还有即时 PF 与 API 调用,见下表,不能让登录成功后自然切场景再靠空引用暴露漏项。AGG 渠道路径仍需独立完整设计;在该设计获批前,AGG 构建的 `kv` 必须显式拒绝、PF 默认路径维持。此首段仍须完整独立审查,不能仅凭本文件视为获批。
## 源码证据与必须写入完整方案的合同
1. **启动捕获**:`Boostrap.AfterAssetUpdateV2` 在已取得 `config` 后先调用 `SubBoostrap.RegisterServices(ctx.gameObject)`,后调用 `AfterAssetUpdate(config,...)`。后者当前无条件 `AddComponent<PlayFabMgr>()`。正式方案应把捕获的 `SaveBackend` 传给注册入口,在创建组件前分流;只接受精确 `playfab`/`kv`,缺省 PF,未知值停止。运行期间配置变更不切换当前组件或身份。Editor 的 `PlayerKVGameplayLoop.BeforeBootstrap` 属许可流程,不替代正式捕获。
2. **正式实例与加载**:`PlayFabUserService()` 的 `kvControlledInstance=false`,而 `ResetForKVLoad` / `ReplaceFromKVDecoded` 只接受受控实例或 Editor loop;`RejectSaveBackend("kv")` 直接拒绝。完整方案必须定义互斥的正式 KV 实例准入,不能把 `kvControlledInstance` 当成正式标志随意改义;第 31 模块的初始化通知/归一化意图、失败清理和 `OnInitData` 接收者都要有正式合同。正式登录仅调用 `LoadForGameAsync` 一次,读取 `TryGetCurrentGameHandle(epoch)` 得到 CP1 同一原对象;`TryAcquireGameHandle` 仅由 Coordinator 内部消费。不可再调 `LoginApiServerCore` 建第二个 S101 会话。
3. **身份发布**:当前 `IsLogin` 必须有 PF SDK 登录,`IsAuthorized` 必须 `apiSession` 认证,`ApiPlayerId` 取 `apiSession.PlayerId`,而正式 KV Context 持有的是另一会话。CP2 首段仅在 GameLoaded 后形成内部“已认证且原 handle 已交接”的状态,阶段切换、Mail/IAP 初始化、`EvtAPISvrLoginSuccess` 和对玩法公开 `IsAuthorized` 均保持关闭;后续首场景门才一次性发布 `InternalId`、API PlayerId、`CreateTime` 和事件。失败/Logout 同步撤销内部状态和 handle,再异步排水;旧 epoch 响应不得恢复身份。`UserId` 当前代表 PF ID,Mail 缓存和若干埋点会消费它;不得把 S101 十进制 ID 偷换成“PF ID”而不逐调用审计。`SetMMTestValue` 把 UserId 按十六进制解析并写 PF,KV 分支必须使用已解码 MMTest 且不调用该旧函数。`OnYinDunLogin`/TapJoy、资料更新及 AGG 头像/关联账号写入须列逐项行为边界。
4. **工厂与文件**:`PlayerKVGameLoadCapability` 目前只有 `UNITY_EDITOR` 的 `CreateForValidation`,其验证 handler 明确拒绝真实传输。需另增正式工厂,从启动捕获的 API 校验恰为 `http://s101.local:18080/`,使用同一 CookieContainer、三个互不共享且可独立 Dispose 的 auth/KV/lease handler;禁止重用 `S101AccountSession` 的旧 Cookie 替换流。CacheRoot 必须按账号隔离且路径固定,表快照取已加载的真实 `cfg.Tables`,不能沿用 Bindings 的硬编码 `"kv-loop"` 当正式版本。观察超时保持既有范围,Close 失败不得启动新 Context。
5. **31 模块**:`PlayerKVGameplayBindings.cs` 整文件仅 Editor 编译,`Snapshot`/`Bind` 要在 Player 目标可用;`LoginStage` 的 `EventShootingRangeData` 注册仅 Editor loop 下存在。需逐模块核 1–31 的容器注册、加载前静止点、Apply 主线程和版本快照;任一缺失失败关闭,不造空对象替代。原 `PFLoginAct` 登录成功后马上 `MailService.Init()` 与 `iapService.Init()`;`InGameMailService.Init()` 在已授权时马上异步 `mail/fetch`。`DevIAPService.Init()` 和 `AggRTIAPService.Init()` 目前只注册实时事件,首批不能以此推断全部购买流程已迁移。
6. **API 借用**:`CustomServerMgr` 的 `kvOnly` 仅放行两条 `__kv_validation`,普通 `CustomServerPost` 直接抛错;`PlayerKVAccountLease.SendAsync` 也只有这两条验证路径。正式租约应独立于验证路由,先定义确切 `(Method, canonical route)` 白名单。已核自动请求是 `POST mail/fetch`;若允许进入 `FishingStage`,还会由 `InitEvent` 触发 `POST friend/GetInvitedListCount`、由 `AntiCheatManager.CheckBan` 触发 `POST anti-cheat/CheckBan`;HomePanel 条件触发 `POST club/GetSelfClub`。这些是**候选清单而非完整首场景白名单**,其余 Club 路由仅按实际验收场景逐项批准,不能开 `club/*`。路由不得接受绝对 URL、编码绕过、查询、片段、路径穿越或重定向。绑定期间至 GameLoaded 前不发送普通业务请求;响应返回前后核 SessionVersion、同一租约、epoch、授权与关闭状态;401/403 只撤销当前身份,不重登 PF;Close 排水后才允许新租约。`Cookie` 旧属性不可把正式 KV Cookie 暴露为可替换流。
7. **退出和故障**:CP1 独立复核未重跑三类 cleanup-failure,也没有 GameLoaded 后 Close 失败的专项用例。CP2 必须注入/验证成功发布后的 Close 故障、旧 handler 无法排水、401/403、迟到响应、同账号新 epoch、切账号、Unity Quit;公开状态先撤销、屏障故障保持阻断,不声称底层资源已释放。卸载/重试/退出均观察同一 Registry;不可用 `async void` 异常吞掉失败。
## 审查前仍未解决的硬点
- 正式核准账号清单如何发放与防止“只改配置就扩大范围”,以及初次 KV 档存在性的无写判定(`newCreate=false` 本身不充分)。
- `UserId` 的全调用点分类:哪些要求 PF 十六进制 ID,哪些只需稳定玩家标识;Mail 缓存键、统计与好友/社团请求不能盲目替换。
- 登录后初始场景还会发起的所有 API 和 PF 管理器调用;目前只核到下述若干入口,不能推断首个界面已可无错运行。
- `FishingStage.StartAsync` 必走 `SurveyService` 构造(直接 `PlayFabMgr.Instance.GetLocalData`)、`LeadboardData.SetLvData`(PlayFab 排行榜调用);还触发上述 Friend/AntiCheat API。`SurveyService.GetSurveyMatchlist` 又把 `UserId` 按十六进制解析。进入场景前必须完成这几条业务转换或明确阻断阶段跳转,不能仅空值兜底。非 AGG 的 `DevIAPService.GetOrderUnreceived` 目前返回空字典,不能据此推断 AGG 支付路径可用。
- AGG SDK 登录与 S101 账号映射、关联账号归一化、头像写入及埋点键名;在独立方案前保持 KV 显式拒绝。
- Player 目标原有 `_cheatingScore` 编译错误的来源和独立修复白名单;正式验收必须 0 错误,不把 Editor 编译等同 Player。
以上硬点任一未在 CP2 完整方案给出可执行合同与测试矩阵,则 CP2 保持待评审,**不修改生产代码**。最终 CP4/CP5 要核旧 `PlayFabMgr` 自动执行及运行引用归零、删掉没有正式消费者的迁移接口和闲置实现,给最终流程图;本检查点不提前清理。