Files
ft/CodexOutput/upserver/B3BatchMigration/B01-通知提示与已读状态批级方案-待评审.md

120 lines
16 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.
# B01:通知提示与俱乐部已读状态批级方案
2026-09-28,主会话;待完整独立方案审查,未批准编码。本批属于4B3B的业务接线准备,**不是4B3C正式启用**。按F01–F12统一流程,一份方案、一轮方案审查、连续实施、一轮批末实施复核。
## 1. 当前分类与选批
复用既有B3DesignAudit扫描器,不修改扫描源码,在独立目录current-scan生成当前快照。旧223处全部可映射:B3-C050原PlayerData.SavePlayerData的写已提取到SerializeAndSavePlayerData,源调用保持;新增B3-N001是PlayFabMgr.StartKVGameplayLoopSync中的内部计时调用,源于之前Editor验证接线。本轮没有新增生产调用。
| 分类 | 当前数量 | 含义 |
|---|---:|---|
| 首批候选 | 3 | B3-C170、C177、C178,本方案详审 |
| 待迁移分类 | 200 | 按业务/读取/生命周期/交易等分派,未完成各自上游完整审计 |
| 已有限定处理、正式仍待 | 4 | 复用已有R1/4C/Editor证据,不能宣称正式路径全部完成 |
| 保留PF待专项 | 13 | 旧PF兼容/身份/SDK/公共资料,不改成本人KV |
| 本次排除 | 4 | GM路径,后续单列,不等于永久允许不迁移 |
逐项见《调用分类与现状映射.json》,旧ID保持稳定,当前扫描ID另列。方法名候选不是语义调用关系;当前224处也不是全部可机械替换的任务。扫描仍不涵盖所有反射/UnityEvent绑定和新的显式KV调用,不能以数字清零关闭B。
三个选择比较:
- 本批选择**通知提示/已读标记**:非消费、固定键;覆盖合法缺键、内存提交顺序、异步系统授权和多个UI入口,适合复用固定句柄的第一组业务合同。
- 不按“前30个Update”选:足迹涉及高位保持与社交奖励,问卷涉及网络响应与奖励,不能因末端一行保存就混入低风险批。
- 不先做定时器/加载发布:属于C,依赖B操作归属;不绕过原B→C门。
本批不改通知系统权限含义、经济公式、奖励、身份、俱乐部网络API或其他红点。ShowNotice仍是“已展示/处理过提示”而不是“操作系统已授权”;IsOpenClubRed写"1"仍表示已读,内存IsOpenClubRed=false。
## 2. B/C边界与固定归属
本批只实现固定Owner下的业务方法和调用者持有原服务的规则。**不修改SubBoostrap/LoginStage/UserService注册,不开放SaveBackend=kv,不获取4B1私有handle,不从PlayerKVGameplayLoop取句柄,不生成v4许可。** B验收使用既有合成session/Bridge;默认PF构造与已有受控Editor玩法保持。
复用PlayerKVGameSessionHandle与PlayFabMgr.ReadKV/WriteKV,不再造会话管理器/通用Registry/动态当前账号路由。为DefaultSettingService、ClubService提供internal固定KV构造入口:参数是非null、当前可读的既有handle,存于readonly字段;原无参构造继续表示PF。KV构造失败不能退回PF。LocalNotificationService的KV构造接收该次固定DefaultSettingService及配置依赖,不能在授权回调恢复后重新Resolve当前账号服务。
这些是生产可用的业务实现,不限UNITY_EDITOR;测试入口必须Editor或独立合成宿主。正式C尚未接入,不能因此宣称玩家现在已能使用此新KV路径。C未来必须为每次登录创建新的这些绑定实例,重新发布到游戏容器;同一实例不允许改绑新handle。旧实例仍只指向旧Owner,旧回调即使持有同一账号ID也失效。C还须撤销/退订旧实例及通知订阅;本批不把设备级通知服务生命周期重构提前完成。
B2受控加载的无参对象Reset/Replace继续照旧。固定KV实例不参与跨账号原地改绑;即便测试人为对旧实例Reset/Replace成新数据,旧Owner失效后也必须在写业务字段前拒绝。不得以ReferenceEquals(service)或账号ID相同代替Owner有效性检查。
本批不放行任何真实社交界面,也不声称整个ClubService已迁移。其捐赠、签到、网络回调及身份另批处理;C不得仅因SetOpenClubRed完成就启用所有俱乐部功能。
## 3. 每调用规则表
| 稳定ID/当前成员 | 上游与首次副作用 | 固定归属及处理 | 失败/验证 |
|---|---|---|---|
| B3-C177 DefaultSettingService.ShowNotice getter | CheckRequestPermission、通知面板;原首次修改为_showNotice缓存赋值 | 每次先ReadKV(构造捕获的Owner,"ShowNotice"),即使缓存已有也不可跳过有效性检查。成功且Found=false才返回false;Found=true严格沿用原值=="1"规则,其余原字符串为false,不规范化写回 | Stale/NotReady/LoadGuardActive拒绝,不能返回false冒充未提示;不赋缓存、不调用旧PF |
| B3-C178 ShowNotice setter | CheckRequestPermission;等级通知弹窗DestroyUI;LocalNotificationService.OpenSettings授权完成。原首次修改为_showNotice=value | KV先检查Owner有效、计算目标"1"/"0",以固定Owner写;本地Accepted后再改_showNotice。同值只有当前有效快照已是目标值时才可不写,不能只看旧内存缓存 | Validation/Blocked/StorageError不改提示状态,不自动重试;可能提交保留原结果,不清pending、不回写旧值;失败不继续关闭界面/触发后续动作 |
| B3-C170 ClubService.SetOpenClubRed | FishingSocialPanel.Start;原先IsOpenClubRed=false及SetClubRed发生在保存前 | KV先用固定Owner核有效并读取该键,按M14既有语义得到当前OpenRed(键缺失=true;键存在且值为任意非null字符串,包括空字符串/"0",均为false;不能改为仅"1"才已读);需要写时先WriteKV("IsOpenClubRed","1"),Accepted后再改字段并执行原SetClubRed。若有效快照已读则同步内存为false,不再次写 | 失败不改IsOpenClubRed、不发红点更新;旧服务在新Owner后不能改共享内存或红点。不能把此结果称远端Confirmed |
普通状态采用本地Accepted语义,随后由原会话同步器保存,不给这两个标记新增每点击必Flush或票据等待。原票据留在既有系统,不伪造远端已确认。Owner在写已接受后被撤销:保留A已接受的事实;再次检查有效性后才发布UI副作用,若失效停止显示更新,不回滚/重放写入。
结果表达沿用原PlayerKVKeyReadResult/PlayerKVEditResult,不新增另一套通用错误模型。现有bool属性/void入口在KV拒绝时抛出带错误分类的InvalidOperationException;UI和async void边界捕获、记录并停止本次动作,不能冒充false/成功。原提交不确定时在该绑定服务内记录故障阻断后续修改,保留原EditResult供限定诊断;不实现人工解冻。PF分支业务语义保持。
## 4. 完整通知上游与UI边界
存档调用少不等于只改保存方法。本批包括以下必要上游,统一在一份方案中处理:
| 入口 | 现状 | 本批合同 |
|---|---|---|
| LocalNotificationService.OpenSettings | initing先置true;await Init后重新Resolve ISettingService写ShowNotice;异常可能不复位initing | 启动前捕获原服务并验证其KV归属;使用async Task内部实现,由原void入口统一观察异常;finally只复位本实例initing。await后核原服务再写,不能Resolve新服务;无效时不安排后续账号相关通知/订阅 |
| LocalNotificationService.Init | 非Editor系统授权await后调度通知并订阅EventRankData | KV模式捕获本次设置服务,在请求前及await后核原Owner;过期则不调度/订阅或设置isInit。系统已弹出的权限结果不能撤销,必须明确不代表可写新账号。PF原分支保留 |
| SettingNotificationsGradePopupPanel | 点同意先调用DestroyUI(写ShowNotice),再打开系统设置;取消也写已提示 | 初始化时捕获设置和通知服务;回调先校验旧实例/原Owner,提示写被拒则停止,不关闭别人的UI、不打开设置。同意/取消的原业务含义保持;同一处理中的重复点击不重复请求 |
| SettingNotificationsPopupPanel | 同意关闭后Resolve通知服务 | 初始化时捕获通知服务;点击前校验,先拒绝陈旧入口,再执行原流程;不在延迟操作中重新取当前服务 |
| SettingPopupPanel.OnToggleNotification | 开启时Resolve通知服务;关闭时ShowUI弹窗 | 沿用已有settingService引用,初始化时捕获配套通知服务;点击前核归属,不由旧界面启动新账号操作。新弹窗创建归属由未来C UI生命周期统一保证,本批不声称修复所有UI加载回流 |
| GiftNoticePanel.OnClickYES | 关闭后Resolve通知服务 | 仅通知按钮同上;不改礼包展示/领取,也不开放该奖励业务。原OnClickNO的设备PlayerPrefs不迁移 |
| DebugFunc.NotifySettingTest | 同步Resolve到局部service再调用 | 原代码已捕获局部引用,不新增存档;保持不改,LocalNotificationService统一校验。不以GM作为真实验收入口 |
| PlayerData.LevelChange→CheckRequestPermission | 调用前已有升级、地图和事件副作用 | 本批仅保证CheckRequestPermission内部提示读写拒绝不会变成false或写新Owner;**整个LevelChange的上游归属未完成**,留核心进度批,C前不得用本批声明升级全链安全 |
| FishingSocialPanel.Awake/Start→SetOpenClubRed | Awake已捕获ClubService,Start先执行社交UI/API行为,再标记已读 | 固定KV模式在对应UI入口开始处核已捕获ClubService的有效性,至少在本批红点修改前核原Owner;不Resolve替代服务。调用失败记录并停止该段后续,整个社交API链另批,不扩大本批验收 |
无需修改ISettingService/ILocalNotificationService的公开签名。内部可用具体服务的受限检查方法供同程序集UI使用;无参PF服务检查保持原行为,不检查当前全局SaveBackend来切路。UI原有无关按钮、格式和资产不动。失效旧UI只停止旧回调,不能用类型名销毁可能属于新会话的同名窗口。
错误日志只记键名、Owner标识和错误类别,不记Cookie或完整存档。系统权限已经改变与游戏提示标记保存失败分别记录;不以存档失败反向重复请求系统权限。
## 5. 白名单与明确不做
运行源码限8个既有文件(均在Client/Assets/Scripts下):
1. Services/SettingService.cs:固定KV构造、ShowNotice读写、检查/错误边界;其他本地设置不改。
2. Services/ILocalNotificationService.cs:固定依赖构造、原实例捕获、OpenSettings/Init的恢复校验和finally;调度公式不改。
3. Services/ClubService.cs:固定KV构造、已读标记读取/提交/检查;不改捐赠/签到/身份/API实现。
4. UI/Setting/SettingNotificationsGradePopupPanel.cs:原服务捕获、点击检查及失败中止。
5. UI/Setting/SettingNotificationsPopupPanel.cs:同上。
6. UI/Setting/SettingPopupPanel.cs:只处理通知入口。
7. UI/Shop/GiftNoticePanel.cs:只处理通知入口,不涉及礼包奖励。
8. UI/Social/FishingSocialPanel.cs:固定ClubService的本批提示状态入口校验和错误边界。
测试允许新增 `CodexOutput/upserver/B3BatchMigration/B01Tests/` 下独立宿主;如需真实Unity对象验证,新增 `Client/Assets/Editor/KVPromptStateValidation.cs` 及其meta,仅暴露固定B01用例入口、空闲Editor运行,使用fake transport。禁止任意反射方法执行器/真实API降级,不改原测试以掩盖回归。
测试优先编译/调用实际源文件并用最小必要平台shim;Unity验证实际服务/面板,不以重新手写一套算法替代生产入口。平台权限在离线shim或Editor分支模拟,不触发用户系统权限弹窗/推送。若必须新增其他生产文件或改公开接口/注册逻辑,先具体补审,不能借白名单延伸为通用框架。
不改Core/PlayFabMgr、Owner/Bridge、B2加载器与31模块合同、UserService/注册、通知包、数据格式、PF SDK。这里的默认PF兼容不授权KV失败回落PF。历史Editor KV Loop仍是已关闭验证链,未因此扩展或重新运行。
## 6. 验收矩阵
| 项 | 必须验证 |
|---|---|
| P01 正常读/缺键 | ShowNotice缺键false、"1"true、"0"/空字符串按旧规则false;只有成功快照的缺键才缺省,不自动写 |
| P02 拒读 | Stale/Closing/NotReady/加载guard活跃时不赋缓存,不当false,不转PF,即使_showNotice已有值也拒绝 |
| P03 普通提交 | 首次写正确原字符串/固定Owner,Accepted后才改内存/红点;同值不重复写;记录原票据但不声称已远端确认 |
| P04 故障 | Validation/Blocked/StorageError以及LocalCommitMayHaveSucceeded:不继续UI/系统副作用,不回滚、不自动重试;故障阻断后续修改 |
| P05 旧回调 | A通知授权挂起→撤销A→B创建→完成A;B缓存、ShowNotice、红点、网络请求均不变,A不重新Resolve B;同账号新epoch也覆盖 |
| P06 同对象污染 | 人为让旧Club实例Reset/Replace装入另一组数据后再触发旧操作,原Owner检查必须在字段/红点变化前拒绝;不以对象引用相等过关 |
| P07 权限时序 | 重复点击/授权失败/异常均观察并finally清initing;过期后不设置isInit、不新增通知订阅;系统授权与存档标记语义分离 |
| P08 UI入口 | 四个通知UI入口和Social标记调用捕获原服务;旧实例回调不销毁新同名UI,不触发新账号系统/存档操作;失败停止后续段 |
| P09 非消费范围 | 礼包展示、领取/捐赠/签到/升级经济数据不改;原无参PF入口及设备本地设置语义保持 |
| P10 回归与编译 | 既有42/86/29等适用基础回归、4B3A固定入口、B2 M14/31模块受影响回归;Unity Editor编译及适用SERVICE_HOST构建。平台分支做编译验证,已记录默认非Editor错误单列,不掩盖新增错误 |
fake transport必须固定.invalid地址且绝不转发。关闭原合成Owner并排空后释放临时缓存。旧样本/真实101、102/permit/claim不变。B01不要求真实账号写入或再钓鱼;真实正式路径和连续操作属于后续C/4D集成范围。
## 7. 检查点与交付
0. 方案完整审查、主会话核hash和逐项裁决,批准8文件及固定测试范围。
1. 批内实现固定绑定与两键规则、所有列明通知上游;相应故障/迟到场景先建有效失败用例,再修正。每检查点更新日志,不逐调用停下。
2. 跑专项与适用回归、Unity编译;合同内失败修复并回归继续,保留首败;合同/范围/架构变化才补审。
3. 原文件diff/范围核对,封存源码、原始测试输出、hash,形成一份实施交付与独立复核提示词。
4. 一次独立实施复核,主会话裁决关闭B01;3处从“首批候选”改为“B业务实现关闭,待C正式接线”,不声称整个4B3B/C或两条完整用户玩法已启用。
## 8. 自审与需独立裁定的重点
作者自审:不按调用数量凑批;所有存档点与实际通知入口列明;复用原Owner而无全局动态句柄;无真实数据操作;B→C门未松动;未称整个升级/社交/礼包链已安全。首批是可单独实现和验证的非消费业务能力,后续正式发布仍须完整B/C依赖。
请独立审查重点判断:固定实例构造作为B准备是否足够且最小;C创建新实例的前置是否与现有受控加载可衔接;普通Accepted后的内存/红点时序是否正确;通知系统权限是设备级但提示标记是账号级,两者的失效处理是否有遗漏;当前8文件是否遗漏必要上游。若此边界不能成立,指出具体调用链和最小修订,不直接扩大到全部服务或免除批级方案门。