Files
ft/CodexOutput/upserver/阶段4B分期与4B1登录存档准备方案-待评审-2026-09-23.md

18 KiB
Raw Blame History

阶段4B分期与4B1登录存档准备方案(待独立评审)

日期:2026-09-23。前置:4A实施复核裁决与关闭已完成。

本文是下一步提案,不是实施批准。当前仅完成文档准备,4B尚未编码,游戏仍使用原存档路径。 沿用主会话写方案→用户指定外部AI审查→主会话逐条裁决→明确边界后编码。正式地址固定 http://s101.local:18080/;Unity CLI Loop;不使用UnitySkills;暂不打包。

1. 现状与分期理由

已核源码 观察 设计约束
UserService.cs,LoginCore、LoginPlayFab、InitData LoginPlayFab在API登录前调用InitData;InitData调用CompareDatas、MMTest写入、LoadPlayerData和OnInitData 不能只在当前登录尾部插入一次KV Get,否则已产生PF初始化及写入副作用
UserService.cs,LoginApiServerCore、Logout、401订阅 重登/注销直接Dispose apiSession;401触发后台LoginApiServer KV Owner关闭前不能换Cookie或销毁session;401必须撤销当前存档上下文,不能在旧Owner上静默重新认证
PlayFabMgr.cs,CompareDatas、Awake、UpdateUserDataValue、SyncData PF时间戳与PlayerPrefs合并可写回,Awake启动40秒任务,普通写为void,全局Instance在回调执行时解析 KV路径不能复用PF缓存比较;全局当前账号路由无法识别旧回调归属;必须审查调用起点
AggUserService.cs,Login、InitData OnInitData订阅只在base.Login等待期间有效;初始化会补写渠道标记并修改传入字典 不能把不可变KV快照直接传给现有初始化,也不能延后到base.Login返回后再调用
DataCenter.PlayerData.cs,LoadPlayerData、LoadPlayerDataNew 先设置默认值再解析;NewPlayerDataDic仅在key存在时替换;Init/体力恢复链可保存 空KV、缺主档、解析失败和旧业务对象残留必须分别阻断;通用字符串字典合法不代表完整游戏档合法
QuitAct.cs,StartAsync 注销先同步失效,再await Hub释放,最后销毁RootCtx 保留先撤销身份的语义,后续增加可等待KV关闭;不得以等待网络为由延迟身份失效

上述观察是本轮静态读取,不宣称已经实现或运行验证。

2. 路线比较与推荐边界

方案 优点 代价/本提案选择
一次实施完整4B(登录、业务初始化、所有延后读写与退出) 一次交付实际游戏效果 跨全局facade、渠道回调和大量DataCenter/UI调用,当前只有分期合同,缺完整逐调用点清单;不推荐直接开工
4B内再分三个验收单元 每次可给出完整文件白名单和独立用例;先锁定session所有权与认证/读取顺序 推荐;4B1只做准备内核,不宣称游戏已切换,4B2/4B3继续独立详审
按业务key逐个PF/KV混用 单次改动表面小 同次初始化可能拼接两份档;跨key依赖和自动回退难证明,维持原方案不采用
单元 要解决的具体问题 编码前所需审查 启用范围
4B1 登录存档准备 API session所有权、认证→Bridge Open/Get→安全快照、失败清理、注销/取消真正排水 本文§3–§8完整合同、文件边界与矩阵 仅离线合成宿主;不接UserService/游戏,不向正式服发请求
4B2 游戏加载与内存隔离 PF只供暂留资料;严格业务档校验;清空/重建本账号业务对象;初始化副作用和OnInitData时序;401/退出接线 单独列完整加载字段/模块、缺失与损坏策略、默认值来源、写入副作用、初始化失败清理和精确文件清单 在完整启用门前只做替身/受控加载验收
4B3 普通读写入口 固定后端facade、固定句柄、逐个延后回调捕获、结构化持久化失败、定时/暂停Flush 重新生成调用盘点,逐项标明同步/异步、句柄捕获起点、取消点、可变业务对象归属及允许修改文件 4C消费流程、tickets有界历史和正式场景数据边界未完成前,不启用实际含消费玩法

此分期仅细化4B,不撤回整体阶段4目标。4B1通过不等于4B通过;4B通过不等于G4通过。

3. 4B1职责与公开合同

建议新增 PlayerKVLoginContext(每次登录尝试一个实例),命名空间沿用 game.PlayerDataStorage。它拥有一个新建的S101AccountSession和一个4A Bridge,持有但不向外发布固定Handle。它不引用Unity、PlayFab、GContext、全局manager或任何玩法类。

与4A的职责差异:4A要求外部已经认证且不拥有session;4B1负责建立认证session、按顺序取档,并保证Bridge真正Close后才Dispose该session。复用4A串行/票据/读接口,不复制或修改其实现。

3.1 输入与输出

  • 创建时捕获数字ID、绝对缓存根、long epoch、轻量 Func<long,bool> isCurrent,之后不可变;ID规则复用现有正数Int64十进制规则并规范化。正式公开构造使用固定s101基址,测试替身/API覆盖仅internal。
  • PrepareAsync(CancellationToken lifetimeToken):单次操作;同实例重复调用返回同一任务,不再登录、不换Cookie、不产生第二个Owner。首个调用的token定义本次上下文生命期,后续调用的token不替换它;接口文档和测试明确这一点。调用者若仅停止等待,不得据此新建上下文绕过Close。
  • 返回 PlayerKVLoginPrepareResult:Prepared、Error、AccountError、OpenError、OwnerError、KVError、SyncStatus、HTTP状态(可空)、CleanupFailed,以及非敏感身份字段PlayerId/Epoch/OwnerInstanceId。失败时Data=null。
  • Prepared仅表示“认证成功且取到当前Ready快照”,不表示新档/有效业务档/已初始化/可以进游戏。成功Data为独立不可变字符串字典;允许成功且Count=0,必须显式 IsEmpty=true。该空结果来源于成功Get,不能被消费方当作建档批准。
  • CloseAsync():首次调用同步撤销上下文(方法返回Task前已不可再发布有效结果),返回共享可等待任务;重复调用返回同一任务。不提供同步Dispose或超时后强制释放。
  • IsCurrent:当前代际仍有效、未撤销、session/Cookie仍匹配;业务不能仅凭历史Prepared布尔值判断当前有效。已返回快照不会被销毁,但其归属过期后不得用于初始化或写入。
  • 本阶段不公开session、Cookie、Handle、Update/Delete/Flush接口,不调用CustomServerMgr.UseAccountSession;未来接线若需非拥有式session绑定或写句柄,必须在4B2/4B3方案中明确扩展,不临时绕过私有所有权。

3.2 错误分类

Error 条件 数据/收尾规则
None 认证、Open、Read及最终归属检查成功 Prepared=true;返回快照与身份;IsEmpty只表示空字典
Validation ID/缓存根/委托等无效 不创建传输;Data=null
Cancelled / Stale 本次token取消/外部代际失效/关闭 不发布可用快照;清理完成前不完成失败返回(CloseFailed除外),不借新session
AuthenticationFailed S101登录结构化失败 原AccountError和HTTP状态保留;Dispose本次session,未发KV Get
OwnerRejected Bridge Open失败 原OpenError/OwnerError/KVError/SyncStatus按可得信息保留,不用默认Ready代替未知状态
NotReady Open完成但Read拒绝,如Conflict/Unknown/StorageError 原状态保留,Data=null,不读旧PF/PlayerPrefs补档
Unexpected 工厂/回调或预期外异常 Data=null;只记录异常类别,不记录值/请求/Cookie;仍执行清理
CloseFailed Bridge或session清理失败 CleanupFailed=true,保留原失败原因与所持对象/任务;禁止重开;不把失败Close标成功

AccountError等现有错误字段没有适用值时用可空字段,不能用None暗示该阶段已成功。

4. 顺序、关闭与并发规则

4.1 准备链

  1. 同步验证固定输入和外部归属;进入单次Preparing。取消或过期请求不分配新传输。
  2. 新建S101AccountSession,await LoginAsync;失败不调用Bridge。成功后再次核对epoch、生命周期及session身份。
  3. 使用同一session和固定缓存根/epoch调用4A Bridge.OpenAsync,接受4A“真正收尾后才放行”的保证。期间任何取消必须继续等待底层构造/传输收尾,不只看上层排队取消返回。
  4. 固定Handle.ReadLocalSnapshot;保留Ready/NotReady/Stale区别,获取不可变快照。最后同步检查上下文撤销标志和外部归属后,才完成Prepared结果。
  5. 不调用业务初始化、默认建档、PF加载、PlayerPrefs、写入或Flush。任何失败走统一关闭路径。

4.2 单次实例与跨实例责任

一个context只代表一次登录,不提供重登方法。调用方必须await旧context.CloseAsync成功后才能创建下一context;这一顺序在4B1组合测试中演示,并作为4B2登录Gate的必测合同。4B1不宣称能阻止任意外部代码故意新建多个context。Close失败的对象必须保留,禁止通过重新new对象规避关闭失败;同账号缓存锁只是兜底,不能替代跨账号关闭屏障。

4.3 防止失败与Close互等

必须区分两项任务:底层prepare工作结束信号(workSettled)与对外Prepare结果。workSettled只表示认证/打开/读取不再执行;对外失败结果可继续等待清理。

  • Close先原子撤销本context,锁外取消context自身的CTS,再await workSettled,随后await bridge.CloseAsync,最后Dispose账号session。
  • prepare路径成功或失败都在finally完成workSettled;失败路径只能在完成该信号后等待统一Close。Close不得等待对外Prepare任务,避免“Prepare等Close、Close又等Prepare”的环。
  • workSettled完成时Bridge内部可能仍有排队收尾;仍必须await Bridge.CloseAsync,不能把它省掉。
  • Close在Prepare之前被调用时,将本实例永久关闭,并使工作信号无需等待;后来Prepare返回结构化Cancelled,不发请求。
  • Prepare/Close并发发布、同实例重复Prepare/Close、token注册/取消/释放的临界区必须统一设计。锁内只改状态/捕获任务,不Cancel、不调用外部委托、不执行I/O、不await。
  • 取消回调只同步撤销并触发非阻塞收尾,不等待Close;后台收尾任务必须保留并可由Close观察,不能遗失异常。
  • 外部epoch可以任意时刻变化;4B1不轮询。下一边界检查立即拒绝失效结果,调用方在代际切换时必须调用Close。已返回快照不是永久授权。

4.4 资源关闭次序

撤销业务准入 → 取消本次生命周期 → 等prepare工作settled → await Bridge.CloseAsync → Dispose S101AccountSession → 释放context CTS/注册。

Bridge Close失败时保留session、bridge及故障任务,不提前销毁Cookie/HttpClient;失败状态永久阻塞该实例。session.Dispose失败也保留CloseFailed,不能当作可继续登录。Close从不隐式Flush,Unknown不自动重试。关闭期间不处理游戏退出、Hub或渠道SDK;这些由4B2在组合层按此顺序接入。

4.5 外部归属委托

采纳4A P3-3:只读取捕获ID/epoch及撤销标志;不I/O、不等待任务、不触发登录/关闭,不获取与Close反向依赖的锁。异常视为失效并保留异常类别诊断,进入安全清理。不要使用“当前全局账号相等”替代本次context身份;同号重登也必须区分epoch。

5. 业务档及未来接线的硬边界

  • 4B1返回成功空快照是协议事实,不是新档批准;newCreate=true亦不自动授权。4B2默认策略为现有账号空KV阻塞,不复制PF/PlayerPrefs,不写默认档。新玩家建档另定显式流程。
  • 4B1不验证NewPlayerDataDic、GuideSaveData、MMTest等业务格式,不声明可玩。4B2在调用现有LoadPlayerData前完成完整业务校验与清理方案;不能只验证一项JSON就宣布完整档有效。
  • 4B2需要为InitData/MMTest/Agg渠道标记/体力恢复等写入副作用逐项选择延迟、暂存或禁写策略,并验证失败回滚/重建业务内存;未审清不得接游戏。
  • 4B3必须按回调创建时的context捕获归属。仅让PlayFabMgr.Instance转发到“当前Handle”不能阻止旧回调写新账号。每条修改还须防止旧回调先修改共享业务对象、再被存储层拒写的情况。
  • PF身份暂留作资料及外围依赖,ApiPlayerId作KV身份;不全局改UserId、不删SDK。普通保存本地Accepted与远端Confirmed明确区分;立即消费留4C结构化处理。
  • tickets有界历史仍为长期会话启用门,4B1不新增票据、不修改Coordinator来顺便解决此问题。

6. 4B1文件白名单(仅待审提案)

类型 精确路径/职责
新生产文件 Client/Assets/Scripts/Services/PlayerData/PlayerKVLoginContext.cs:单次认证/准备/资源所有权与统一关闭
新结果模型 Client/Assets/Scripts/Services/PlayerData/PlayerKVLoginPrepareResult.cs:错误、阶段证据、不可变快照及身份
新meta 上述两文件对应 .meta
新合成测试目录 CodexOutput/upserver/KVLoginContextTests/:Program.cs、显式链接真实生产源码的csproj、独立runs及诊断输出
文档 本方案、4B1任务/实施/交付/审查文件、主日志与共享状态入口

禁止修改S101AccountSession、4A Bridge/Owner/结果、3A状态/磁盘、3B Coordinator、KV Client、旧测试Program/csproj、UserService、PlayFabMgr、DataCenter、Stages、服务器、包/asmdef。如发现现有公开接口不足或旧测试包含通配符导致编译问题,先记录最小差异并另行补审,不能边写边扩大范围。

测试需注入创建session/bridge的internal工厂,用现有S101AccountSession的handlerFactory和Bridge internal handler构造,不新增公共任意传输入口。生产默认固定地址、Cookie和禁重定向沿用现有实现。工厂只允许离线测试故障注入,不替换生产所有权约束。

7. 4B1验收矩阵

以下均为计划,尚未运行。并发使用TaskCompletionSource/事件闸门,不用Sleep断言先后。所有登录/Get只走synthetic.invalid替身;缓存位于本次run目录。

编号 场景 必须观察结果
B1-01 正常认证/Get/读取 请求顺序正确,同一Cookie归属;Prepared及快照正确;无Set/Delete/PF/初始化回调
B1-02 非法ID/根/空委托、提前取消、提前代际失效 Data=null,0传输,结构化错误
B1-03 认证HTTP/协议/拒绝/网络失败 保留具体AccountError/HTTP状态;0 KV Get;本session已清理
B1-04 初始Get失败、Busy、Conflict、Unknown、StorageError 不返回可玩数据,保留原错误和状态,不回退PF、不写空档;清理成功后新context可由调用者打开
B1-05 成功Get返回空字典,分别newCreate true/false Prepared且IsEmpty=true;无建档、初始化或任何变更请求
B1-06 同context重复Prepare(不同token) 同一任务、仅一次登录/Get,后续token不替换首个生命期;定义有测试
B1-07 认证忽略取消时Close 同步失效,Close与对外失败结果等真实返回;不提前Dispose传输;底层结束后清理
B1-08 Get忽略取消时Close,之后准备102 101底层任务与Bridge排水完成、session再Dispose;测试调用者等待旧Close后才启动102;无Cookie/快照串号
B1-09 Read前/后epoch失效及同号重登 失效优先,不发布旧快照;旧结果归属检查false;Owner不复用
B1-10 Prepare失败与Close同时发生 workSettled/对外结果分离,无互等;测试以闸门推进并有整体超时保护
B1-11 Prepare前Close、并发重复Close、取消回调重入 关闭幂等、任务不遗失、CTS无重复释放问题;关闭后Prepare零请求拒绝
B1-12 Bridge Close/清理异常 CloseFailed与原错误都保留;session未提前Dispose;context不重开;不假成功
B1-13 isCurrent/工厂异常 结构化Unexpected或Stale,Data=null;可释放资源全部按顺序释放,失败仍记录
B1-14 pending/Unknown已有缓存 pending只读呈现且Close零写;Unknown拒读且不重试;不改变既有恢复语义
B1-15 输出与不可变性 仅用例名/状态/计数/脱敏身份摘要;无Cookie/Token/完整值/用户路径;快照脱离内部字典
B1-16 回归与编译 4A18组、42/86/29、Review、3C离线8/8、KVLiveTests只build、Unity CLI Loop编译;未授权修改文件hash不变

采纳P3-1:每条命令保留完整stdout/stderr和真实退出码;若86套件再次失败,保留首轮用例名及原输出,再诊断,不覆盖或只报告后续成功。已有偶发失败根因未定位。

8. 交付、评审问题与自审

实施获批后交付:生产/测试文件完整SHA256、实施前后范围hash、逐行矩阵、各命令/退出码、每个检查点日志、限定自审及外部实施复核提示词。所有实际测试以独立run记录;不访问正式服,不运行KVLiveTests或3C --live-s101。

外部审查必须分别裁决:

  1. 是否接受4B分期,以及4B1仅为准备内核、不提前接游戏的边界。
  2. session所有权、workSettled与失败结果分离、Close异常保留是否完整,是否存在取消/重复操作互等或资源泄漏。
  3. 成功空快照与失败Data=null是否足够清楚,是否会被误解为游戏初始化放行。
  4. internal替身工厂和精确白名单是否足以实现;若需扩旧接口,先给最小差异,禁止默认扩大。
  5. 矩阵是否覆盖生命周期竞争,P3待查项是否准确保留,4B2/4B3启用门是否存在被绕过的路径。

主会话文档自审:分期目的、职责/非职责、错误、生命周期、资源关闭、文件边界和测试均已写明;没有将后续业务档校验或所有回调隔离当成4B1已经解决。4B2/4B3明确属于未来独立方案,不能以本文件批准其编码。原工作区改动与历史证据保留,不自动提交Git。

流程检查表:

  • 读取既有阶段方案、4A复核、登录/存档/退出及渠道初始化相关源码。
  • 沿用已有用户约束,比较三条路线并提出明确推荐;本轮无需重问地址、工具或打包策略。
  • 写出4B1完整提案和逐条验收合同,完成文档自审。
  • 用户指定外部AI独立审查本方案;主会话核对版本后逐条裁决。
  • 书面方案及边界获批后再实施;当前未开始编码。