保存独立KV用户服务、固定会话存档与原业务接线,以及C3d资料缓存和C3e本人统计写。 保留原PF/AGG路线、业务公式和时序;榜单读取、好友资料、公告、匹配及首场景闸仍待完成。 验证:ServiceHost两条件各33组646断言;Unity CLI Loop编译0错误269警告。 包含相关方案、评审裁决、测试及状态文档。未操作服务器、正式账号或打包。
9.1 KiB
M2 双提供方独立 KV 用户服务抽离方案(待完整评审)
日期:2026-09-29。状态:候选方案;未获生产代码白名单。本方案只处理正式 KV 登录、加载第 31 模块、原 handle 交接和退出的结构抽离;首场景、业务接口语义、公开身份与存档调用全量替换另批评审。依据用户最新双提供方约束和CP2B-T 限定关闭裁决。
已核源码事实与问题
SubBoostrap.RegisterServices已从SaveBackend捕获一次PlayerKVRuntimeSelection,KV 不创建PlayFabMgr;LoginStage却始终向IUserService注册PlayFabUserService(AGG 为其子类)。PFLoginAct对其下转,正式 KV 判断、错误和原 handle 就绪均读旧类;QuitAct也向旧类下转。- 正式 KV 会话、Coordinator、关闭和第 31 模块状态位于
PlayFabUserService的UserService.KVLoad.cspartial。PlayerKVGameplayBindings.Bind(PlayFabUserService)的第 31 模块直接调用该类的ResetForKVLoad/ReplaceFromKVDecoded,事务固定 31 模块并同步主线程 Apply;这些安全约束不能松动。CP2B-T 的普通请求传输在正式原 handle 交接后才发布。 IUserService同时包含共用登录资料、EntityKey和以playFabId命名的其他玩家资料方法。EntityKey的实际非注释读取在 Duel UI;UserId在社交、缓存键等处具有 PlayFab 身份含义。正式 KV 目前不公开IsLogin/IsAuthorized/UserId,也未取得可代替EntityKey的可信身份合同。GetDefaultName对长度小于 5 的 ID 使用id[0..5],不能直接把 101/102 当旧 PlayFab ID。第 2 个加载模块SmallGameData的构造函数要求IUserService;即使当前 Apply 只调用 Reset/Replace,纯粹不注册该接口也无法在现有 DI 下构造这 31 个模块。- 全客户端约有 213 处
IUserService文本引用、223 处PlayFabMgr.Instance调用分布于 82 个文件;登录、资料、存档不是一次改注册就能全链通过。AggUserService仍继承原类且 KV 明确不准入 AGG。Editor 的kv-loop依赖 PlayFab 身份,是单独的测试通道,不能充作正式 KV 路线。 LoginStage.RequestNextAct对 KV 仍阻止进入FishingStage;这个门在本批保持。正式地址固定http://s101.local:18080/,准入仅 101、102 且有现存 KV 档。本机服务器代码不作合同来源,也不修改。
方案比较与建议
| 方案 | 做法 | 取舍 |
|---|---|---|
| A:继续在 PlayFab partial 内分支 | 只改 DI/名称,仍由旧类承载两种登录 | 改动少,但 KV 仍依赖旧具体类和 SDK;不满足双提供方目标,否决。 |
B:一次性替换全量 IUserService 与 82 文件存档调用 |
同批引入独立实现并打开首场景 | 最终状态直达,但线上资料/社交/排行榜/写入合同未核;破坏面过大,当前否决。 |
| C:按登录纵切,随后按业务合同迁移(建议) | 保留原 PF 类及 SDK;本批抽离正式 KV 登录/第 31 模块/退出,冻结启动选择并保留阶段闸;后续批次按功能迁移 IUserService 和存档调用 |
每批可验证两模式;保留必要的临时门控,最终按清单销账。需先解决 IUserService 注册和暂不支持的成员语义,不能用静默空实现。 |
建议的目标边界
SaveBackend 一次捕获 → PlayerKVRuntimeSelection(会话内不可更换)
├─ playfab:PlayFabUserService / AggUserService → PlayFabMgr → PlayFab SDK
└─ kv:独立 KV 用户服务 → PlayerKVGameLoadCoordinator → 原 PlayerKVGameSessionHandle
├─ 第 31 模块接收 KV 身份/初始化意图
└─ CustomServerMgr 正式租约(仅交接就绪后)
- 启动选择是唯一来源;登录阶段仅按该选择注册对应用户服务。KV 失败不回退 PF;PF 模式不构造正式 KV 用户服务、不打开正式租约。AGG 维持 PF 且在选择 KV 时启动失败,不静默回退。
- 新 KV 类不得继承
PlayFabUserService、调用 PlayFab SDK、创建活动PlayFabMgr,不得制造假的 PlayFab ID、EntityKey或授权态。正式加载成功仍仅显示原 handle 就绪,直至真实业务合同和公开身份另审;当前FishingStage闸保留。 - 第 31 模块仅抽出“Reset、Replace、Discard 初始化意图”这组已有实际调用者所需的窄契约,让正式 KV 用户服务和既有受控验证入口各自提供接收对象。31 项顺序、Guard、Apply/回滚、主线程、旧 epoch 失效、通知后释放 Guard 与原 handle 发布时点保持。不得新造第二套 Coordinator、开关或存档管理器。
PFLoginAct用提供方中立的登录结果或窄 KV 会话能力处理就绪/错误,不再以PlayFabUserService下转判断正式 KV;输入账号写入服务的接口也要明确。QuitAct经同一会话能力观察关闭。PF 原登录、重试、Mail/IAP 和退出继续原行为。正式 KV 401/403、注销、应用退出均先使租约/身份失效,再观察关闭;在途旧回包不能恢复新会话。IUserService的字段和方法需要逐项归类:共用身份/资料;PF 专属 Entity/Profile;当前仅 PF 数据查询;尚无 KV 线上合同。不批准把 PF 专属方法在 KV 类中做空、返回默认成功或转调 PlayFab。第 2 个加载模块对当前IUserService的构造依赖必须同时解决;若现阶段无法为某成员给出真实 KV 语义,应以完整审查裁决“独立 KV 类显式失败并保持首场景闸”或“把构造依赖拆成窄契约”的最小方案,过渡路径登记最终清账。- 存档访问继续沿现有两路:PF 的
PlayFabMgr和 KV 的原 handle/KVMgr;本批不把 223 处旧调用机械替换,也不删仍供 PF 模式使用的类。PlayerKV*改名和Core/KVMgr → Services/PlayerData物理依赖修正按既有延期裁决另批处理。
分批实施与验收门
DP1(拟申请评审的最小批):只抽离正式 KV 登录/关闭、31 号模块接收和登录/退出阶段的旧类下转;保留受控验证和 kv-loop 原测试通道,但不能让它们成为正式 KV 的身份依赖。精确文件白名单须由审查对源码调用图确认后落盘;候选核心为 UserService.cs、UserService.KVLoad.cs、PlayerKVGameplayBindings.cs、LoginStage.cs、PFLoginAct.cs、QuitAct.cs、新增独立 KV 用户服务与必要合成测试。SubBoostrap.cs、现有选择类型只有在审查确认 DI 或冻结缺口时才入白名单。禁止触碰本机服务器、场景、Prefab、付费、社交和未审业务路由。
DP1 验收:同一构建分别选 PF/KV;PF 仍经过真实 PlayFabUserService → PlayFabMgr → PlayFabClientAPI;KV 只构造独立正式服务且 PlayFabMgr.HasLiveInstanceForKVLoad 为假,101/102 有档加载到原 handle 并停在登录阶段。无档/非名单/错误地址/AGG-KV 拒绝,401/403、Logout/退出、失败回滚、旧 epoch/在途响应和双次登录均不串号;第 31 模块与 30 个前序模块仍按原顺序。合成测试、ServiceHost 双配置、Unity CLI Loop Editor 编译与可取得的真实 Player 编译分别记录;真实网络和首场景没有跑就不得标记通过。
DP2 及以后(不在 DP1 白名单):先获得正式线上路由、身份、响应/错误/幂等合同,再按相同业务链成组迁移 IUserService 的资料/社交方法及 PlayFabMgr 直调;同时测 PF/KV 两模式读写、会话/重启。打开首场景前完成 Survey、排行榜、Mail、Friend、AntiCheat 等自动调用的真实合同和语义裁决。最终解除或删除所有开发期冻结路径、删闲置 KV 过渡 partial/测试入口,保留 PF 能力;按双提供方约束文件末尾清单逐项销账。
完整评审需集中裁决的未决点
- DP1 中
IUserService是否仍必须由 KV 服务实现以满足SmallGameData与其他 31 模块构造/注入?请追踪PlayerKVGameplayBindings.Bind所 Resolve 的模块、PFLoginAct、QuitAct真实实例化路径;比较显式失败且阶段封锁的 KV 实现与窄依赖拆分,给出不靠空方法、不伪造 PF 身份的最小 DI 方案。 - 第 31 模块的窄契约应如何定义并被正式 KV 与受控验证复用?核出需要保持的原始状态、通知时机、意图生命周期和回滚语义,避免复制完整 PF 服务。
IUserService.entityKey、UserId、ApiPlayerId、InternalId在阶段闸未开前以及未来首场景的明确语义分别是什么?哪些成员必须延期到可信线上合同后才可开放?Duel 的两处EntityKey读取和短 ID 默认名应列出处理批次。- PF/AGG 与 Editor
kv-loop保留行为、正式 KV 失败不回退、重复启动选择和场景/Prefab 序列化引用应如何验证?若方案需超出上述候选白名单,请明确原因和新增验证。
在独立报告核实受审 hash、主会话逐条裁决并写出精确白名单前,不修改生产代码或测试代码。