Skip to main content

权威状态与客户端表现分离设计

本页接收旧前后端架构方案中仍有独特价值的设计:操作幂等、权威状态应用、快速操作和表现分离。它是未统一落地的设计提案,不表示当前Project2已具备统一ChangeSet、revision、receipt或五层架构,也不授权替换后端框架。当前功能事实见各功能页及所属实现仓库。

旧方案中的Wenjing/ET候选分支、旧MR状态、2026-08-03性能结果和迁移排期不能作为2026-10-15主干事实,已不搬入本页。服务端实际路由从当前Project Workspace registry读取,不由这篇设计重定义。

权威与共享边界​

资产、抽奖、体力、任务进度、成长、支付等业务结果由服务端决定;Unity提交玩家意图并展示已确认结果。客户端不能上传“扣费后余额/抽奖结果/体力最终值”并要求服务端信任。

可共享wire请求/响应/推送DTO、稳定消息ID/错误码/枚举、客户端安全只读投影和无Unity/数据库/网络副作用的确定性纯计算。数据库文档/索引、服务端权威Handler/Actor、会话lease、密钥/签名/支付验证、管理能力与反作弊决策不进入客户端包。Unity控件、Prefab、输入和设备实现也不反向混入服务器契约。

协议与生成结果来自真实单一源,记录source commit/path、generator version、source hash和generated hash;生成代码不双端手工各改。客户端包只包含需要且可公开给客户端的内容。CI可检查生成一致性,不能因此在本地执行校验。当前任务采用clean break时直接更新所有消费者,不把“协议只能追加字段”当默认保留旧版本的理由。

操作身份与状态身份​

一次写意图使用稳定 OperationId。同一逻辑操作的网络重试复用原ID,新玩家意图使用新ID。OperationId去重命令,ChangeSetId + domain revision去重状态应用,两者不可互相替代。

建议消息结构:

OperationRequest
operation_id
expected_revision? # 仅确需并发前提的操作
domain_request

OperationResult
operation_id
outcome # committed / rejected / duplicate
error
change_set?
domain_result? # 例如抽奖展示顺序,不是再次发奖依据

ChangeSet
change_set_id
origin_operation_id? # 服务端主动变化可以为空
server_time
domain_changes[] # 强类型、带版本的权威post-state

统一envelope而不设计“字符串路径+任意值”万能补丁。资产、体力、英雄、任务等各自有强类型变化;按真实功能需求决定采用范围,不为所有操作提前建设通用平台。

服务端在接受写操作时记录receipt。同一ID+规范化payload返回同一结果,同一ID但payload不同明确拒绝。超时只表示客户端尚不知结果,不表示服务端未提交;重试复用ID或查询receipt,不能再次创建抽奖/扣费。

每个domain aggregate/entity带单调revision。客户端只接受更新版本的post-state,相同revision不重复应用;旧revision忽略,出现缺口时按契约补齐或取snapshot。RPC先到/push先到/重复投递均进入同一状态应用通道。跨域原子业务先在服务端一次提交,再发布含多个typed change的结果,不能先展示部分成功。

邮件到达、补偿、自然结算、跨设备或后台任务不一定有本机Command;它们仍有ChangeSetId和可恢复投递身份。重连从revision补齐或请求snapshot,UI响应已应用的状态事件,不直接把网络callback视为最终界面状态。

客户端职责​

View 输入
-> 不可变 Command
-> CommandHandler 用例编排
-> RPC 或本地用例
-> Application 应用权威结果 / Model、Store
-> 不可变 Presentation 输入
-> View 渲染
层职责不承担的职责
View采集输入、派发意图、订阅/解绑、渲染直接组包、写资产、判断权威成功
Command不可变意图参数;真正写意图带操作ID持有控件、Panel、动画或可变Model
CommandHandlerlane策略、体验级前置检查、RPC/超时/取消/receipt查询通过客户端预测生成最终奖励
ApplicationRPC/push汇入、校验去重、revision推进、更新store并发已提交事件引用具体Panel/Animator/音效或播放展示
Presentation用本次不可变结果编排动画/音效/弹窗/导航,支持跳过/关闭/静态降级再扣费、再请求抽奖、补奖或回滚服务端提交

旧提案命名ChangeSetInbox/ChangeSetDeduplicator/typed handler仅用于表达职责,不要求无条件新增对应类。已有CommandEvent保有实际价值的日志/中间件可以继续使用;当选定替换某一旧行为时直接更新调用方并删除旧路径,不以legacy adapter长期双轨运行。

快速操作与并发​

操作性质候选策略例子
经济/奖励写入同一aggregate串行;下一意图有界排队或明确拒绝抽奖、购买、领奖、强化
可合并设置latest-wins/coalesce,提交最终意图音量、画质、跳动画偏好
独立对象按entity分组并行不同邮件阅读、不同装备查询
只读查询有界并发、取消已过时查询、缓存后刷新列表/详情/预览
纯表现本地立即反馈、可取消按压反馈、页面过渡

分组key可含domain/role/aggregate/entity,避免全局busy让不相关功能互相阻塞。队列有上限,满时明确提示而非静默丢意图。经济操作可先给按压/loading反馈,但余额和奖励以权威结果为准。取消等待/动画不等于取消服务端已接受请求。

快速十连等重复操作究竟忽略、只排一个还是全部排队,需要功能规则决定;本页不默认替产品选择。客户端表现快与允许无界写并发是两回事。

体力样板​

候选状态包含 base_value/recovery_started_at/buy_count/ad_count/revision/server_time。登录/重连取权威snapshot;战斗开始、购买、广告及其他奖励统一产生 StaminaChanged(post_state)。

客户端可以按base_value、恢复开始时间和服务器时间投影当前显示值,但不把每秒显示结果写回权威Model。服务端push通过同一应用入口。回前台、时钟漂移过大或revision缺口时重新取snapshot,不连续猜delta补偿。

此方案旨在替代多处猜测性AddPhysicalValue/Increment写入;若实际采用,须同步全部涉及路径,删除旧权威写入,不保留临时adapter。需理解的业务边界包括满体力停止恢复、奖励可超上限、购买与push乱序、请求超时、跨设备和断线自然恢复;这些是设计关注项,不是本轮新增测试用例授权。

抽奖样板​

点击普通十连提交 DrawLotteryCommand(pool,count=10),处理器选择对应角色抽奖lane并创建操作身份;服务端校验账号、卡池、配置和可用状态,查幂等receipt,原子提交消耗/保底/奖励/英雄与库存,再返回ChangeSet和展示顺序。

Application只应用一次资产/抽奖/英雄变化。Presentation使用本次operation的不可变snapshot,不从不断变化的live inventory反推本轮抽到什么。随机、保底、消耗和奖励全部服务端权威;domain_result只能表达揭示顺序/表现标签,不能成为再次入库凭据。

跳过、关页、切后台、断线不能改变已提交结果;重开可从已确认结果恢复静态展示。失败不播成功动画;表现异常记录并静态降级,不触发第二次抽奖或补发。当前各抽奖功能是否已满足这些要求,应在功能实现事实里核实,本页不以旧候选MR代替证据。

采用范围与证据​

按真实问题采用:重复扣费、RPC/push多处写入、表现读取漂移和全局并发阻塞分别有对应设计收益。若现有功能没有这些问题,不能仅为“统一架构”扩大改造。公共envelope不能牺牲domain类型,分组队列不能引入无实际消费者的新平台,后端更换更不能由本文顺带执行。

源码审阅、精确head外部CI与功能画面证据分别说明源码、构建和玩家行为,不能相互代替。字段/协议/交付参数不明时明确缺口;已有任务授权内的可逆工作连续推进,不复制旧Producer审批、Wenjing切服门禁或默认测试任务。

来源:旧 standards/client-server-feature-architecture.md 的共享边界、权威状态、幂等、并发和应用/表现设计。移除过期分支/候选环境状态、框架白标维护流程和历史迁移排期。未修改游戏源码,未新增或扩写测试。本地验证:根据项目策略禁止执行。