Files
ft/CodexOutput/upserver/客户端迁移详细修改方案.md

304 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 客户端迁移详细修改方案 v1
2026-09-16执行顺序更新:按用户要求优先确认PlayFab存档到KV的读写适配,当前阶段顺序与验收门以[TODO.md](TODO.md)为准。本文A/B/C保留作技术范围分组。KV联调所需的最小测试登录属于测试准备,不要求先重构完整游戏登录。
日期:2026-09-15。状态:技术评审完成,待用户确认本文的开发边界与行为裁决。
本文是当前修改方案入口,覆盖此前草案中被新 KV 实现替代的结论。本文不代表代码已实现或正式联调已通过。
## 1. 基线、目标与交付边界
- 客户端:C:/projects/ft,feat/upserver,f40a816cc;工作区已有 RTService 的 SkipNegotiation + WebSockets 修改。
- 服务端:C:/projects/N3-Server-CN,main,858fe7c;KV 功能提交 a3d3faa。
- 正式 API:http://s101.local:18080/;Hub:http://s101.local:18080/gamehub。
- Unity:2022.3.62f2c1;沿用现有 HttpClient、Newtonsoft.Json、官方 .NET SignalR 客户端。
- 身份唯一来源:涂游 SDK 数字 userId,传输统一为十进制字符串。禁止把旧 PlayFab hex ID 转成数字后当作涂游 ID。
### 交付划分
| 批次 | 可审查交付 | 完成定义 |
| --- | --- | --- |
| A | 正式地址、独立 KV 客户端、缓存与同步模块 | 配置与契约测试通过,KV 模块不依赖 PlayFab |
| B | 涂游登录、KV 初始化、会话和实时连接接入 | 专用账号可完成登录、恢复本人存档、写后读和业务消息验证 |
| C | 好友/资料/排行/公告/匹配等外围迁移,移除 SDK | 所有保留业务能力有替代实现,Unity/热更新编译与相关回归通过,包和程序集不再依赖 SDK |
B 必须先关闭第 10 节列出的登录资料来源问题;C 不包含在“仅修改 KV 接口”的放行范围内。当前可完成 A 的具体实现;B/C 的条件不满足时不通过删除功能、空响应或临时生成假身份绕过。
## 2. 已核实的接口契约
### 2.1 登录
POST /account/Login,JSON 请求:
```json
{"playerId":"10006464"}
```
playerProfile 是可选字段。首次读取存档前不上传本地默认等级/昵称/头像,避免 AccountService 将默认值写回已有玩家资料。
成功响应字段:state=0、internalId、createTime、svrTime、newCreate;成功时签发 Cookie。HTTP 200 不代表 state 成功。客户端接收专用 DTO,字段显式映射;日期按 UTC 解析。缺少必需字段、失败 state、非法 JSON 都不能进入游戏。
### 2.2 KV
全部为 POST、JSON、Cookie 鉴权。用户 ID 取 NameIdentifier,不放在 KV 请求 body 中。
| 路由 | 请求示例 | 成功响应 | 行为 |
| --- | --- | --- | --- |
| playerdata/Get | {} | {"data":{"Lv":"12"}} | 读取当前账号全部键值 |
| playerdata/Get | {"keys":["Lv","NewPlayerDataDic"]} | {"data":{...}} | 缺失 key 省略 |
| playerdata/Set | {"data":{"Lv":"13"}} | 200,空正文 | 新增/覆盖提交的键,保留其他键 |
| playerdata/Delete | {"keys":["Lv"]} | 200,空正文 | 删除指定键,缺失键无操作 |
- Get 的 keys 省略/null/空列表均读全部;Delete 的空列表无操作;Set 的空字典无操作。
- Get 成功必须有非 null 的 data 对象。空正文、data 缺失或格式错误不是空存档。
- V 是字符串;JSON 对象由客户端先序列化成字符串,再放进 data。不能把整个存档对象直接替换成 HTTP data 对象的内部字段。
- MySQL PlayerKV:主键 (PlayerId,K),K 最大128字符,V 为 longtext,UpdateTime 由服务端写入。
- Get **不返回 UpdateTime 或版本**。当前服务端无条件更新,没有条件版本写入/幂等请求号,不能承诺跨设备并发安全。
- Set/Delete 以 HTTP 成功判定,不能用响应内容是否为空判定。
### 2.3 SignalR
保留 CookieContainer,WebSockets-only,SkipNegotiation=true,仍执行 SignalR 协议握手。业务登录用的 HttpClient 与 Hub 必须共享当前会话 Cookie,旧账号会话不能被新账号复用。
## 3. 模块与文件修改清单
以下是计划路径,尚未创建的源文件用“新增”注明;实施时不修改服务器源码。
| 文件(相对 Client) | 改动职责 |
| --- | --- |
| Assets/Scripts/AOTScripts/GConstant.cs | 共用正式 API 默认常量 |
| Assets/Editor/DebugConfig.cs | 更新 s101 API 常量;菜单校验同时核对 API 字段 |
| Assets/Scripts/Core/CustomServerMgr.cs | 同一基址、显式 HTTP 结果、会话捕获/失效、请求取消;保留旧业务方法签名以控制影响面 |
| Assets/Scripts/Services/PlayerData/PlayerKVModels.cs(新增) | Get/Set/Delete DTO、业务错误种类、同步状态 |
| Assets/Scripts/Services/PlayerData/PlayerKVClient.cs(新增) | 远端请求、严格响应解析、空响应成功判定;无 Unity 生命周期逻辑 |
| Assets/Scripts/Services/PlayerData/PlayerKVSyncState.cs(新增) | 账号缓存、待写队列、序号、批次确认与三方比较;不依赖 MonoBehaviour |
| Assets/Scripts/Services/PlayerData/PlayerKVCacheStore.cs(新增) | 按环境/账号隔离保存快照与待同步记录、加载校验、备份及写入失败报告 |
| Assets/Scripts/Core/PlayFabMgr.cs | B批切换时保留 MonoBehaviour 类名和 .meta GUID,作为业务兼容入口;内部委托新 KV 模块,同批替换登录调用的 UserDataRecord 签名。A批不激活替换,避免旧登录流程调用未初始化的新KV |
| Assets/Scripts/Services/UserService.cs | 新登录 DTO、await 授权/KV 初始化、身份/时间/状态管理;PlayFab 调用按批次拆除 |
| Assets/Scripts/Services/AggUserService.cs | loginInfo.StrUserId 接入,成功/失败进度、绑定信息与资料初始化顺序 |
| Assets/Scripts/Stages/PFLoginAct.cs、LoginStage.cs、LoginStageCtx.cs | 明确涂游入口/编辑器入口,移除把 PlayFab Title 当初始化依据的条件;防止非 AGG 分支漏改 |
| Assets/Scripts/Services/RTService.cs | 已有直连选项;补首次连接注册、释放订阅和换号会话隔离 |
| Assets/Scripts/SubBoostrap.cs | 注册 KV 依赖,校验最终配置在服务构造前生效 |
| Assets/Scripts/GampPlay/FishingB/FishingBehaviorB.cs | 立即同步失败后玩法状态/重试语义,不能仅日志忽略 |
| Assets/Scripts/UI/Turntable/FishingTurntablePanel.cs | 仅成功时设置 isDataSynced;重试同一存档值,不重复扣费 |
保留 PlayFabMgr 类名是为了避免所有场景和玩法引用一起重命名;这不代表继续依赖 SDK。最终包依赖检查与类名是否包含 PlayFab 分别判断。新代码使用业务命名。
## 4. 配置和身份规则
1. API 与 Hub 均从 EventApiURL 解析,缺省用共用常量;URL 必须为绝对 HTTP(S),规范末尾斜杠后组合相对路由。
2. 远端配置先加载、本地 debugconfig.json 后覆盖。实施需定位实际 persistentDataPath,仅改 EventApiURL;不得通过整套调试菜单顺带覆盖静态资源、RTM 和 Func 字段。
3. 云配置发布不在客户端代码批次内。包上配置若仍提供旧值,默认常量不会覆盖它;验收需检查实际最终值。
4. 发布客户端必须由涂游 SDK 登录获取 ID;ID 缺失或非法立即失败,不回退设备 ID、PlayFab ID 或随机 ID。
5. 编辑器允许显式输入分配给联调的十进制测试 ID,不能把短数字默认拼接成真实用户,也不能把任意测试 ID 当作已完成渠道认证。
6. 在字符串层保留 ID;需要验证范围时使用 long.TryParse + InvariantCulture。当前涂游包装字段为 int,不能通过自行改类型声称上游已支持 long。
7. 清理三个已定位 hex 转换点:FriendService 邀请码、SurveyService 用户尾号、DinggingSceneData 随机种子;修改必须连同编码/解码及玩法可复现测试评审。
8. 历史 JSON 字段 PlayFabId/FriendPlayFabId 仍按服务端现有契约使用,值是涂游 ID,不能单端改字段名。
## 5. 登录和退出时序
### 登录状态
Idle → ChannelAuthenticated → ApiAuthenticated → LoadingKV → Initializing → Ready。
任一步失败进入 Failed;存档冲突进入 Conflict;退出/换号增加 sessionGeneration,失效所有旧请求回调。
1. 确认最终配置,初始化新的会话容器。
2. 涂游登录成功,验证数字 ID;已有 SDK 登录态取 TYAccountInfo.userId。
3. await account/Login;检查 HTTP、DTO、state;暂存身份及 Cookie。
4. 用 svrTime 校时,用 createTime/newCreate 初始化账号生命周期标志。
5. await playerdata/Get({}),将云端字典与当前账号本地记录按第6节比较。失败不 LoadPlayerData、不启动写入、不返回 Done。
6. 缓存模块先就绪,允许 LoadPlayerData 与 Agg InitData 中现有同步调用记录变更;此时网络同步仍暂停。
7. 完成必要资料来源处理及 LoadPlayerData、OnInitData,初始化成功后启动同步循环。
8. 准备好聊天/邮件/实时订阅后发布现有 EvtAPISvrLoginSuccess,避免这些消费者先于存档初始化读取状态。内部 ApiAuthenticated 状态不通过旧公共事件提前广播。
9. Login 返回 Done;SignalR 若连接失败独立报告实时连接状态。完整联调必须再确认其连接与业务收发,不能以 Done 代替实时成功。
### 重登和退出
- 同一会话多个401/403合并为一次重登,登录请求本身不触发递归重登。
- KV请求捕获发起时的 ID、会话 generation、Cookie 所属 HttpClient;旧请求不能在排队后使用新账号客户端发送。
- 退出先停止接收业务写入并保存待同步记录,再停止/释放 Hub 和网络请求;不依赖应用退出时网络请求一定完成。
- 换号创建新 CookieContainer;旧回调不得确认或修改新账号队列。
- 已发出的旧请求即使取消,也可能在服务器提交;取消只表示客户端停止等待,不能解释成服务端未写入。
## 6. 没有服务器时间戳时的存档方案
### 6.1 路线评审
| 方案 | 优点 | 问题 | 裁决 |
| --- | --- | --- | --- |
| 云端全量覆盖本地 | 简单,适合干净首次登录 | 离线或失败待写修改丢失 | 仅用于没有待写记录时 |
| 本地时间较新就覆盖云端 | 接近旧实现 | 新API无远端时间,客户端时钟也不可靠 | 不采用 |
| 云端基线 + 待写日志 + 值的三方比较 | 保留失败修改,检测可见冲突,不需修改服务器 | 无法提供服务器版本锁、跨设备强一致性 | 推荐本期有限保证方案 |
| 服务端新增revision/条件写/幂等号 | 可解决并发覆盖和超时确认问题 | 超出已有API,需双方另行实现 | 多设备强一致需求的前置工作 |
推荐方案仅保证本客户端正常顺序发送的队列正确性;不承诺多设备并发或超时请求延迟提交下绝不回退。若本期要求后两者,裁决改为先补服务端契约,不能仅靠客户端重试满足。
### 6.2 本地持久化
使用版本化缓存文件,路径包含环境指纹和涂游 ID,例如 persistentDataPath/player-kv/v1/<originHash>/<id>.json;originHash 由规范化 API origin 计算,避免主机特殊字符与跨服混用。
单份快照包含:formatVersion、环境、playerId、最后确认的云端键值、pending 修改及其本地序号/base存在标记/baseValue、本地专用值。每个键最多一个待写“最新目标值”,正在发送的批次使用独立不可变快照。
- 文件写入临时文件后替换,保留上个完整备份;加载校验归属与格式。主文件损坏读备份;都损坏则报告恢复失败并保留原文件,不能静默当新账号。
- 修改后尽快保存完整记录;更新方法仍为同步入口,优先保证待写记录持久化后再安排网络写入。写入失败进入 StorageError,立即同步返回失败,不能声称已可靠保存。
- 不复制 LocalData_<旧PlayFabId>/LastUpdateTime_<旧PlayFabId>。即使旧 ID 恰好只含数字,也不能认定与涂游 ID 同源。
- remoteStore=false 的值保存在 localOnly 区,仅本人本环境使用,不加入远端队列,避免和远端确认基线混为一谈。
### 6.3 每键比较规则
首次登录无本地pending:云端为基线,成功空字典表示服务器当前无键。业务默认值仅在存档模块初始化后按现有玩法规则生成。
有本地pending时,取 B=编辑前最后确认的云端值,L=本地待写值,R=本次Get值,存在/不存在单独记录;空字符串不等于不存在。
| 条件 | 处理 |
| --- | --- |
| R == L | 云端已具备目标值,确认该pending;适用于上次响应丢失后的常见恢复 |
| R == B | 未观察到云端变化,保留L并允许按队列提交 |
| R与B、L均不同 | 标记Conflict,保留三份数据,不自动覆盖任何一侧 |
| 没有可靠B | 除R==L可确认外均标记Conflict,不猜测先后 |
Conflict 状态不开放玩法写入,用现有错误/重试入口提示存档存在冲突;重试不自动丢弃本地记录。日志只记键名、错误类别和摘要,不输出完整存档。人工数据恢复工具不包含在本批。
字典值中的JSON采用字符串精确比较;序列化格式差异可能产生保守冲突,禁止为了减少冲突把未知业务JSON合并。
## 7. 写入队列与立即同步
### 正常批量
- 保留40秒周期作为常规同步触发;每批最多10个key属于客户端节流策略,不宣称是新服务器限制。
- 取消旧5个缓冲桶最多50个不同key的限制;新的待写字典不能静默丢弃第51个key。
- 同一会话只允许一个KV变更HTTP请求正在等待;普通Set、立即Set、Delete共用排队入口。
- 发送时复制 key/value/sequence,不将后续可修改字典直接交给请求。
- 成功确认只移除当前pending中sequence与批次一致的项;若发送v1期间产生v2,v1确认后v2继续待写,并更新其base为已确认v1。
- 失败保留目标值和基线,不把失败项伪装为已同步。
### 失败分类
| 情况 | 动作 |
| --- | --- |
| 未认证401/403 | 暂停写入、合并重登;恢复时重新读取并比较 |
| 400及明确无效请求 | 停止该项自动重试,报告key/类型;保留原始记录 |
| 连接错误/超时/500 | 写入结果可能未知,冻结后续写并进入重读恢复流程;不得立即发送反向扣款/回滚 |
| Get也失败 | 保留本地状态并等待重连,不默认空云端 |
| 缓存持久化失败 | StorageError,阻止新的需可靠存档的操作 |
恢复重读可以发现很多“已提交但响应丢失”的情况,但不能排除旧请求稍后提交,也不能提供服务器事务序号保证。并发及超时严格一致性仍受第6.1节边界约束。
### SyncDataImmidiate 兼容语义
保留现有签名 Task<bool> SyncDataImmidiate(string key,string data),内部以同一队列立即调度;true只在该请求目标已获远端成功确认时返回。失败或结果不确定返回false,同时保留状态和待恢复记录。
调用者不能在超时后重新执行一次“减余额”生成新值,应重试已捕获的目标存档。false不等于服务端未执行。
- 捕鱼:原代码先本地扣体力再发请求;需要冻结该操作的重复触发,在未确认时走恢复/重试,禁止简单补回再重复扣导致双写。该玩法状态处理与KV入口一起验收。
- 转盘:仅true时设置isDataSynced;false走重试/恢复,不因返回了布尔值就解锁结果。
- KV存储不是扣款事务接口,不能承诺支付或扣道具的exactly-once语义;如玩法需要此保证,应由服务器业务接口提供操作幂等性。
### 删除
PlayerKVClient 提供 DeleteAsync 以覆盖现有服务器契约,写入规则同样按key顺序串行;删除意图用显式标记,不将null或空字符串当作删除。当前无业务删除调用时不新增玩法入口;删除恢复也用存在标记做三方比较。
## 8. HTTP 层与接口建议
新增结构化返回:Success、StatusCode(无HTTP响应时为空)、ErrorKind、Body,不改变已有CustomServerPost的string/null返回契约以避免扩大影响。
```csharp
// 方案接口,尚未实施;所有请求绑定调用时的会话。
Task<KVGetResult> GetAsync(IReadOnlyList<string> keys, CancellationToken token);
Task<KVWriteResult> SetAsync(IReadOnlyDictionary<string, string> data, CancellationToken token);
Task<KVWriteResult> DeleteAsync(IReadOnlyList<string> keys, CancellationToken token);
```
KVGetResult = 成功标志 + Dictionary<string,string> Data + HTTP/错误信息;KVWriteResult = 成功标志 + HTTP/错误信息。Get解析失败必须失败,Set/Delete不解析空正文。所有例外映射到取消、网络、HTTP、协议四类,不吞异常返回空存档。
沿用15秒单次请求超时;恢复重试使用有上限的退避(2/5/10/30秒),仅在会话有效且无Conflict/StorageError时继续。退出取消所有后台循环,Task.Delay带取消令牌。
## 9. SignalR 配套修正
1. 保留已修改的两个直连选项及Cookie。
2. 在每次新HubConnection创建后,先注册全部已有业务处理器,再StartAsync;不把首次注册仅挂在Reconnected。
3. 同一HubConnection重连保留已有处理器,不重复注册;新连接则从持久订阅表重建。
4. WaitForAPISvrLogin保存/释放订阅,重复调用不重复建连;Release等待停止/释放或由会话协调器观察其完成。
5. 会话更新先结束旧Hub,再绑定新Cookie重建;旧连接回调按generation过滤。
6. 用服务器已有Test(arg1,arg2)返回OK验证请求/响应;推送验证使用测试环境已有消息,不调用生产强制下线管理接口。
## 10. 登录资料与SDK彻底移除的剩余裁决
新增KV解决存档承接,但不自动生成旧PlayFab提供的所有字段。
| 能力 | 当前事实 | 本方案裁决 |
| --- | --- | --- |
| 本人昵称/头像 | 登录不返回;SearchFriend可返回基础PlayerInfo,但缓存失败可能返回Unknown默认数据 | 可作为查询候选,禁止把默认值当可靠资料再回写;B批需确定权威来源或扩展登录资料响应 |
| GetHomePage | 返回好友邀请/推荐列表 | 明确排除为本人资料恢复接口,修正旧文档候选建议 |
| 上次登录时间 | LoginResp不返回 | 优先服务端返回登录前时间;若采用新KV键记录需明确首次缺失策略,不将当前时间冒充历史时间 |
| 地区 | LoginResp和KV均不自动提供 | 地区榜适配前确定来源,不默认伪造国家/大洲 |
| 渠道绑定 | SDK与旧KV均有标志 | 渠道SDK为账号绑定权威;旧KV标志不得自行升级认证状态 |
| 好友 | 有GetFriendsList,Friends是JSON字符串数组 | 可迁移;Level直接映射,LastLogin缺失需单独展示裁决 |
| 他人KV | 新KV仅限本人 | GetPlayLv等改用资料查询;不向私有KV接口增加任意playerId |
| 普通/地区等级榜 | 当前服务端有活动榜 | 不能直接互换,需要等价契约或明确产品范围 |
| 公告 | 未确认等价接口 | C批条件未满足 |
| PlayFab匹配票据 | DuelRT不是票据服务 | C批条件未满足 |
| EntityKey | 新登录不提供 | 移除其真实消费者后才能删除;不能构造伪EntityKey骗过编译 |
因此SDK包删除必须最后执行。阶段内SDK包暂留不代表整体目标已完成,也不允许正式进入仍按旧身份调用PlayFab的功能。
## 11. 验证矩阵
新增纯逻辑测试放入 Client/Assets/Tests/PlayerData/(新Editor测试程序集;引用按现有HotUpdate与Unity Test Framework定义),测试核心状态机使用内存远端/缓存替身;测试不用硬编码正式账号。
| 编号 | 场景 | 明确预期 |
| --- | --- | --- |
| L01 | 涂游ID缺失/非数字/超范围 | 不发业务登录,不调用PlayFab回退 |
| L02 | HTTP200但state失败或字段缺失 | 不发布登录成功,不加载KV/玩法 |
| L03 | 登录成功、KV失败 | 不返回Done,不生成空存档写回 |
| L04 | KV成功、初始化失败 | 不启动网络写队列/登录成功事件 |
| K01 | Get {} 返回data={} | 成功空存档,与网络失败区分 |
| K02 | 指定keys含缺失项 | 只返回已有项;不构造值为null的伪记录 |
| K03 | Set/Delete 200空正文 | 成功;其他HTTP错误均保留待写 |
| K04 | 发送v1时编辑v2 | v1确认不删除v2;最终再次提交v2 |
| K05 | 51个以上不同key | 全部记录并可分批同步,无静默丢失 |
| K06 | 请求已提交但响应丢失,重登R=L | 恢复确认,无二次扣减操作 |
| K07 | R=B与R不同于B/L | 前者保留待写;后者Conflict且不写云端 |
| K08 | 空值、缺失、删除 | 三种状态区分,删除缺失键幂等 |
| K09 | remoteStore=false | 保留本地专用值,不上传 |
| K10 | 本地文件损坏/磁盘写失败 | 备份恢复或StorageError,不重置存档 |
| K11 | A账号/服切到B,A请求晚返回 | B缓存/队列/会话不受影响 |
| K12 | 暂停/恢复/退出 | 单同步循环、待写已保存,不依赖退出HTTP完成 |
| G01 | 转盘立即写失败 | 不设置isDataSynced,不重复生成扣减 |
| G02 | 捕鱼扣体力结果未知 | 进入恢复,不补回后重扣 |
| S01 | 连接前已订阅事件,首次连接收到推送 | 首次即可处理,重连无重复回调 |
| S02 | WebSocket直连及重连 | 无negotiate请求,Cookie鉴权、Test返回OK |
正式联调用已分配测试账号:写独立测试key → Get核对 → 清除该账号本地测试缓存影响并重新登录读取 → Delete测试key → 再读确认不存在。只清理本次创建的测试key,不清空真实玩家存档。记录客户端版本、目标地址、部署版本、迁移状态、HTTP状态与脱敏结果。
测试分层:纯逻辑测试通过 → Unity脚本编译 → 测试环境HTTP+MySQL持久化 → 正式部署专用账号验证。服务端现有7个InMemory测试只能证明其覆盖的单进程语义;数据库主键/事务、HTTP授权和部署迁移需实际验证。
## 12. 执行任务与验收门
- [ ] A1:配置入口修改,验证最终API/Hub基址,保留其他服务地址。
- [ ] A2:DTO/HTTP/KV客户端实现,覆盖Get解析、200空响应、授权与超时分类测试。
- [ ] A3:纯KV状态与持久化实现,先覆盖K04~K12失败用例,再接MonoBehaviour。
- [ ] A4:审核所有CompareDatas/立即同步/remoteStore=false调用,以测试宿主验证新KV入口;保留当前生产登录及PlayFabMgr调用链,防止半切换破坏已有初始化。
- [ ] B1:关闭第10节资料来源条件,完成身份/登录阶段/SDK回调具体映射的补充评审。
- [ ] B2:同一批次切换PlayFabMgr内部与登录调用签名,按第5节接入登录与会话、按第9节修正实时订阅,运行L/S用例。
- [ ] B3:审核捕鱼与转盘完整交互路径,运行G用例,验证恢复不会重复消费。
- [ ] B4:专用账号KV与实时端到端验收,输出脱敏记录。
- [ ] C1:为剩余SDK直调功能确定替代接口与产品裁决。
- [ ] C2:移除所有运行/编译依赖,清理manifest、lock、asmdef GUID、StripHint、link和热更新输入;Unity重新生成派生文件。
- [ ] C3:执行相关玩法回归和热更新构建验证,全部通过后才能宣称SDK移除完成。
## 13. 评审裁决与待确认项
| 评审项 | 裁决 | 依据 |
| --- | --- | --- |
| 正式地址/涂游身份/三项KV路由/Cookie | 已核实 | 用户确认及服务端源码 |
| 空响应成功、全量读空keys、本人隔离 | 通过 | DTO/Controller/Service实现一致 |
| 兼容业务入口、独立KV模块、失败不丢队列 | 通过 | 控制影响面并覆盖现有可靠性缺口 |
| 无时间戳缓存恢复 | 条件通过 | 用户接受第6节有限保证与冲突阻止写入;不是跨设备强一致 |
| A批开发边界 | 技术评审通过,待用户确认 | 本文给出职责、契约、失败处理和验收;不要求改服务端 |
| B批完整游戏登录 | 条件未关闭 | 昵称/头像、历史时间、地区与仍存SDK消费者需明确处理 |
| C批彻底删除SDK | 暂不放行 | 公告/等级榜/匹配等尚无完整替代裁决 |
| 执行结果 | 未执行 | 本次仅文档,已有SignalR选项修改保持不变 |
建议本次确认:先实施A批;采用第6节“云端基线+待写日志+冲突阻止自动覆盖”,接受现有接口有限并发保证。若要求多设备/超时条件下严格防覆盖,则先增加服务端版本条件写入,再放行存档模块。