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

16 KiB
Raw Blame History

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. 检查点与交付

  1. 方案完整审查、主会话核hash和逐项裁决,批准8文件及固定测试范围。
  2. 批内实现固定绑定与两键规则、所有列明通知上游;相应故障/迟到场景先建有效失败用例,再修正。每检查点更新日志,不逐调用停下。
  3. 跑专项与适用回归、Unity编译;合同内失败修复并回归继续,保留首败;合同/范围/架构变化才补审。
  4. 原文件diff/范围核对,封存源码、原始测试输出、hash,形成一份实施交付与独立复核提示词。
  5. 一次独立实施复核,主会话裁决关闭B01;3处从“首批候选”改为“B业务实现关闭,待C正式接线”,不声称整个4B3B/C或两条完整用户玩法已启用。

8. 自审与需独立裁定的重点

作者自审:不按调用数量凑批;所有存档点与实际通知入口列明;复用原Owner而无全局动态句柄;无真实数据操作;B→C门未松动;未称整个升级/社交/礼包链已安全。首批是可单独实现和验证的非消费业务能力,后续正式发布仍须完整B/C依赖。

请独立审查重点判断:固定实例构造作为B准备是否足够且最小;C创建新实例的前置是否与现有受控加载可衔接;普通Accepted后的内存/红点时序是否正确;通知系统权限是设备级但提示标记是账号级,两者的失效处理是否有遗漏;当前8文件是否遗漏必要上游。若此边界不能成立,指出具体调用链和最小修订,不直接扩大到全部服务或免除批级方案门。