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,72 @@
# M2:KVMgr与PlayFabMgr同生态位替代策略(主会话意见,待完整方案审查)
日期:2026-09-29。用户要求KVMgr与PlayFabMgr处于同一生态位,考虑先把PlayFabMgr所有函数替代并切换调用,最后处理保护。本意见基于当前源码和[架构复审裁决](M2-架构复审裁决与修复顺序-2026-09-29.md),用于修订R1完整方案;**没有批准代码修改**。
> **2026-09-29 用户纠偏,以下为本文件最新有效顺序**:我先前把管理器迁移与Guard归属、目录移动、生命周期重构放进同一实施批,增加了难以核对的变化;撤回这种混做安排。本阶段以现有行为为基线,逐成员列“旧入口→新入口→调用方→等价/必要差异”,完成存档能力承接和调用迁移。`PlayerKVGameLoadGuard`、现有KV Handle/Owner和旧PF路径在迁移期间保持原行为;不新造Guard、不移动Guard、不搬KVMgr文件、不顺手重写定时器或`Awake`。只有被具体旧调用阻断、且有用例证明的最小兼容改动才能列入迁移白名单。Guard归属、物理分层和代码清理留到迁移完成后的独立优化方案;此前正文中相反的“先归位”“同批生命周期适配”建议不再作为当前实施要求。生产代码仍须等修订后完整方案评审、逐项裁决。
### Guard事实与处理
| 问题 | 已核实事实与本阶段决定 |
|---|---|
| 新KVMgr为何有Guard | **是本次upserver迁移引入的保护**。9月15日前的仓库版本无Guard。9月28日提交`0eb43b2b4`同时新增`PlayerKVGameLoadGuard.cs`、PlayFabMgr旧方法中的Guard调用及四个KV入口;之后B02只是把四个带Guard的入口原样移到KVMgr。此前我只说“搬迁前已有”,遗漏了真正来源,现予纠正。 |
| 旧PlayFabMgr是否有Guard | 原始旧类没有。9月15日的仓库树`f40a816cc`和该文件上一次提交`7a7c1e9f5`均未出现`PlayerKVGameLoadGuard`;当前旧类有11处,全部来自9月28日迁移提交。 |
| 它是否必须存在 | 对原始PlayFabMgr不是必须的,也不是KVMgr天然职责。**当前受控KV加载实现**在31模块`Apply`期间持有它,并用`Violations==0`判副作用;四KV入口的拒绝结果和计数已有验证用例。因此现在直接删除会改变已审加载合同;先保持到迁移完成,再以实际运行/测试证据判断能否移除或换成更小的边界保护,不预设永久保留。 |
| KVMgr调用Guard是否等于反向调用业务Service | 否。Guard实现仅依赖`System`/`Threading`,由加载协调器进入与释放;KVMgr没有调用`UserService`或加载协调器。文件放在`Services/PlayerData`目录造成的层级疑虑是真实设计问题,但目录调整不是迁移前置。 |
“先移植”的验收口径:先实现旧**对外存档能力**在KV路径的对应入口,按业务域替换调用,逐项核对保存结果和旧行为;旧PF专属协议逻辑在切换完成前维持原路径。`CompareDatas`、`Awake`、40秒PF定时器不得未经对照就复制到KVMgr,也不得在同一迁移补丁里悄悄删除;需分别列原触发时机、作用与KV路径的必要替代,评审后作为迁移检查点处理。把PF SDK代码直接改名塞进KVMgr,只会把第二次后端迁移推迟,不算已完成KV替代。读失败/缺键、写拒绝/不确定、原票据确认及固定handle属于新旧后端合同的必要差异,必须在替换相应调用时显式适配;这不是可后置的“优化”。
## 结论与边界
**同一生态位成立,含义是“业务存档入口由KVMgr承接,最终PlayFabMgr运行调用归零”**,不是把旧类逐字复制、保留`Instance`全局当前账号或维持旧返回类型。当前KVMgr只搬了四个显式KV入口,尚未替换旧PF存档调用,之前按B01/B02小段推进没有达到用户期待的管理器完整接管。
`PlayFabMgr.cs`实际上混了存档门面、PF本地与远端合并、定时同步、Unity生命周期、PF SDK发送和Editor测试循环。KVMgr只拥有存档门面;其余能力必须分别归到会话加载/迁移政策、现有KV Owner同步、上层生命周期或测试宿主。目标是**完全替代运行能力与调用,保留旧文件但停用自动执行**,不是要求每个私有函数都有一个KVMgr同名函数。
```mermaid
flowchart LR
B[业务/玩法] -->|旧存档调用:待清零| P[PlayFabMgr]
B -->|显式原handle与结果| K[KVMgr:存档门面]
L[登录/加载/退出Service] -->|发布、撤销原handle| B
L -->|认证/加载阶段| H[KV Handle/Owner]
K -->|读/写/Flush/票据| H
P -->|旧PF分支:切换前保留| PF[PlayFab SDK]
```
## PlayFabMgr方法逐类去向
| 现有成员 | 真正职责 | 替代去向与能否同名 |
|---|---|---|
| `GetLocalData(key): string/null` | 存档键读取 | 已有`KVMgr.ReadKV(handle,key)`,必须保留成功/缺键/失败三态;不能同签名返回null掩盖读错。 |
| `UpdateUserDataValue(key,data,remoteStore): void` | 本地编辑+远端待同步 | 已有`KVMgr.WriteKV(handle,key,data,remoteStore)`,调用方要处理Accepted、拒绝、本地提交不确定及票据;旧void合同不可直接复制。 |
| `SyncDataImmidiate(key,data): Task<bool>` | 单键立即远端提交 | 用同一原handle的`WriteKV`+`FlushKVAsync`+`WaitForKVTicketAsync`观察**该次原票据**;bool不足以表达Unknown/部分确认。消费/领奖须按业务动作收口。 |
| `CompareDatas(remoteDatas,userId)` | PF远端与PlayerPrefs时间戳合并 | 归历史档迁移/正式加载来源政策,不进入KVMgr常规读写API;不能对KV档再做PF式双源合并。 |
| `SyncData`、私有定时循环、`InitBuffer`、`UpdateFaild` | 旧PF缓冲和SDK发送 | 现有KV Owner/Coordinator负责缓存、同步和状态;不复制第二套队列、计时器或PF失败回调。 |
| `Awake`、`OnApplicationPause/Quit`、`OnDestroy` | Unity自动生命周期 | 上层会话Service在登录、暂停、退出时协调Flush/Close;KVMgr保持非MonoBehaviour。全部旧调用迁出后停止创建旧组件。 |
| `Instance`与Editor `Start/StopKVGameplayLoopSync` | 隐式全局入口/受控测试循环 | 正式业务显式传原handle;测试循环继续受Editor许可限制,收口时改测试宿主,不变成KVMgr生产单例。 |
### `CompareDatas`、`Awake`与40秒PF定时器的具体落点
**现状:三者都没有迁走。** `CompareDatas`仍在`Core/PlayFabMgr.cs`,由`Services/UserService.cs::InitData`在PF登录取得`UserData`后调用;它读写按`userId`命名的PlayerPrefs时间戳/本地字典,可能回写PF。`Awake`仍在PlayFabMgr,注册`_instance`、建立5个PF缓冲区并启动40秒`UpdateUserDataValue(CancellationToken)`循环。`SubBoostrap.RegisterServices`仍`AddComponent<PlayFabMgr>()`。KV `PlayerKVSessionOwner.FlushAsync`已能执行一次固定Owner同步,但没有自动40秒调度;`PlayerKVGameLoadCoordinator.Revoke`负责关闭,并不等同“退出前远端Flush确认”。Editor限定玩法还借`PlayFabMgr.StartKVGameplayLoopSync`启动旧计时器。
**逐项审查问题(非预先选定的R1实现):**
1. `CompareDatas`不复制到KVMgr。正常KV登录改走现有`PlayerKVLoginContext.PrepareAsync`读取KV快照→`PlayerKVSaveSchema.Decode`→31模块`PlayerKVGameLoadTransaction.Apply`,由上层`UserService`/加载协调器决定何时发布成功。PF历史档与PlayerPrefs双源合并只属于一次性历史迁移政策:若其他账号确实需要,另列受控`LegacyPlayFabSaveImport`工具/流程,读取原数据、校验目标和备份后导入;未确定政策前保留旧PF分支,不在正常KV登录自动调用CompareDatas。
2. `Awake`目前创建旧单例、缓冲并启动PF循环;迁移方案先列出其实际触发链、KV现存能力和停用旧组件的条件,不在本轮先建新组件。正式切换时必须证明登录、暂停、退出仍有对应行为,再决定是否需要最小接线。
3. 40秒PF定时器目前仍在旧Mgr;先核对KV Owner现有同步触发与实际需求,列定时/暂停/退出的可观察行为和验证条件。不在移植时预设新的定时器或生命周期组件;如果现有能力不能满足切换条件,再单列必要替代并评审。
KVMgr仍只占**存档API**生态位,不反向调用UserService。正式默认启用前,Unity生命周期和Editor测试循环是否仍依赖旧Mgr必须逐项核实并完成替代验收,不能把停用旧组件等同于行为已替代。
现有Roslyn矩阵为224语法候选/81文件,其中`UpdateUserDataValue`164、`GetLocalData`50、`SyncDataImmidiate`2、`CompareDatas`1,其余是旧Mgr内部及SDK调用。数字是语法候选,不是224项独立审批,也不含所有Unity序列化、反射或别名引用。最终须另查组件创建、Prefab/UnityEvent、动态调用与直接PlayFab SDK。
两处`SyncDataImmidiate`分别在钓鱼鱼饵消费和转盘券消费;它们必须跟所属扣费/奖励动作一起验证,不能仅以“新管理器同名方法返回true”证明替换完成。
## 可执行的顺序:先能力,后调用,保护分层处理
1. **一次完整方案评审后,先定KVMgr的存档合同**:保持现有文件位置与单一Guard,明确固定handle来源和`Read/Write/Flush/Wait`如何覆盖旧三个对外存档方法。优先复用四个现成入口;不为了名字相似新增薄转发函数。
2. **在同一实施批按业务域成组替换调用方**:先已关闭B01代表链,然后核心进度及奖励、活动/商业、身份/加载边界;每组列key、首次模型修改、结果处理和回归。可以先把全部管理器能力实现好,再连续替换调用方;不能只做`PlayFabMgr.Instance`→`KVMgr`的文本替换后统一补错。每检查点更新日志,合同内错误直接修复,不按单函数反复审批。
3. **不可后置的最低保护与替换同时生效**:原handle归属/撤销、加载期拒绝、读三态、写Rejected/uncertain、原票据确认、迟到回调旧epoch隔离。否则新调用会把读失败当空档、把未知提交当成功或重复扣费。现有保护优先复用,不新建一套。
4. **可在调用替换后、正式默认切换前收尾的保护**:T08多键部分远端保存检测、差异对账和受控恢复、长期票据容量、历史账号/新账号政策、完整登录/退出组合回归。这些不能延到已经正式启用之后。
5. 全部需要保留的存档业务改用KVMgr且正式路径通过后,令PlayFabMgr的运行引用、组件创建和自动同步归零;直接PF SDK外围依赖另清理,最后才移除包。保留`PlayFabMgr.cs`历史文件按用户目标处理其编译依赖。
**不建议**“一次性实现旧类全部同名函数→全局替换81文件→最后才加任何保护”。它看似减少决策,实际让旧`null/void/bool`隐藏KV错误,并使失败定位横跨业务。可接受的粗粒度是**一份总架构/合同评审+业务域连续实施检查点**,而不是每个调用点各审一次。
下一步在主会话:把此方法去向表和[修复Todo](M2-架构复审裁决与修复顺序-2026-09-29.md)合入R1完整方案,列精确白名单和验收矩阵后送原ZCode审查。固定地址`http://s101.local:18080/`;使用Unity CLI Loop,不使用UnitySkills,暂不打包。