【OpenHarmony/HarmonyOS】从本地模型到云端 Schema:AGC 对象类型、字段映射与版本治理

📅 2026/7/25 5:01:00
【OpenHarmony/HarmonyOS】从本地模型到云端 Schema:AGC 对象类型、字段映射与版本治理
【OpenHarmony/HarmonyOS】从本地模型到云端 SchemaAGC 对象类型、字段映射与版本治理把一个 ArkTS class 放进common/models并不代表云数据库已经接通拥有一份cloud_db_schema.json也不代表客户端生成类与云端控制台保持一致。在“迷宫坦克派对”中Cloud DB 对象类型、生成类、ObjectTypeInfoHelper和 AGC 排行榜指南已经形成了清晰的接入骨架但仓库内也真实存在字段缺失、时间类型漂移和权限边界偏宽等问题。本文不把原型写成上线能力而是从现有文件出发讲透 Schema 如何成为跨端契约。☁️一、先说明当前能力边界项目当前本地排行榜由ScoreManager与 Preferences 承担。docs/AGC_Leaderboard_Guide.md的开头也明确写着未来计划接入 AGC目前项目使用本地ScoreManager进行模拟示例依赖和 API 还要求接入时查询实际版本。仓库同时存在cloud_db_schema.json四个 Cloud DB 对象类型的 JSON 描述common/models/*.tsCloud DB ObjectType 编译器生成的类ObjectTypeInfoHelper.ts客户端对象类型、字段、索引与版本信息AGC_Leaderboard_Guide.md从本地排行演进到云排行的设计指南。因此准确表述应是“已有云端对象模型和接入资料”而不是“已完成生产级云排行”。Cloud DB 对象存储与 AGC 游戏排行榜也是两个不同方向前者可保存自定义实体后者是面向排行榜业务的服务。选型前要先确定需求不能因为都属于 AGC 就混为同一套 API。二、四个对象类型各自解决什么问题对象类型主键主要字段设计意图PlayerStatsuidlevel、exp、winRate、bestScore、updatedAt玩家成长与最佳成绩MatchRequestrequestIduid、status、roomId、createdAt匹配队列请求GameRoomroomIdplayerA、playerB、state、lastFrameData双方房间状态BattleRecordrecordIdwinnerId、loserId、duration、timestamp对局结果记录这是一种典型的“玩家 → 匹配请求 → 房间 → 战绩”链路flowchart LRA[PlayerStats 玩家]--B[MatchRequest 匹配请求]B-- C[GameRoom 房间]C -- D[BattleRecord 战绩]D --A这张图表达的是对象设计关系不代表当前客户端已经跑通完整云端匹配。尤其是真人 3v3、服务端权威判定和生产排行在仓库中仍没有完整闭环。三、ObjectTypeInfoHelper 是客户端侧契约ObjectTypeInfoHelper.getObjectTypeInfo()返回对象类型元数据其中包括objectTypeName云端对象名称objectTypeClass对应 ArkTS 类fields字段类型、主键、非空和默认值indexes索引名称、字段与排序方向schemaVersion当前对象类型版本。当前文件末尾是return {objectTypes: [//BattleRecord/ PlayerStats /GameRoom / MatchRequest ],schemaVersion:7};版本号为 7说明这份生成元数据并非“第一版随手定义”。问题在于仓库里另一份 JSON Schema 与它并不完全一致。如果不知道哪一份是从控制台导出的权威版本仅看schemaVersion无法保证契约正确。四、真实差异一BattleRecord 的时间类型漂移 ⚠️根目录cloud_db_schema.json声明{fieldName:duration,fieldType:Long},{fieldName:timestamp,fieldType:Long,notNull:true}但ObjectTypeInfoHelper.ts中二者都是Stringduration:{fieldName:duration,fieldType:String,isPrimaryKey:false,notNull:false},timestamp:{fieldName:timestamp,fieldType:String,isPrimaryKey:false,notNull:true,defaultValue:0}生成的BattleRecord.ts也使用string。三份契约对照如下字段JSON SchemaHelperArkTS 生成类durationLongStringstringtimestampLongStringstring这种漂移可能导致写入失败、排序异常、历史数据转换困难或不同开发环境生成出不兼容代码。timestamp如果以字符串排序100可能排在20前面如果字符串还混入日期格式问题会更严重。建议统一为带单位的数值字段例如durationMs与timestampMs。字段迁移前先确认云端实际数据类型不要仅修改本地 JSON 后假设云端随之变化。五、真实差异二PlayerStats 多了两个字段生成的PlayerStats.ts与 Helper 都包含userName:string昵称;avatar:string头像资源路径;Helper 中也有对应的String字段和默认值。但是根目录cloud_db_schema.json的PlayerStats只到updatedAt没有userName和avatar。字段JSON SchemaHelper/生成类风险userName不存在存在客户端认为可写云端可能拒绝或忽略avatar不存在存在头像映射无法跨端保持一致这里不能简单下结论说“JSON 一定旧”或“生成类一定错”因为仓库无法证明哪一次控制台导出更晚。正确动作是建立来源信息每次生成记录控制台环境、Schema 版本、导出时间与生成工具版本然后只允许权威源生成其他文件。六、索引不是装饰它必须对应查询模式当前 Helper 给PlayerStats.bestScore定义了降序索引indexes: [ {indexName:index_score_desc,indexList: [ {fieldName:bestScore,sortType:DESC} ] } ]这个索引适合“按最高分倒序取前 N 名”。但根目录 JSON Schema 的PlayerStats没有保存该索引仍是一处差异。MatchRequest则定义了(status ASC, createdAt ASC)复合索引indexName:index_status_created,indexList: [ {fieldName:status,sortType:ASC}, {fieldName:createdAt,sortType:ASC} ]它对应“筛选 matching 状态再按最早请求优先”的队列查询。字段顺序非常关键如果实际查询只按createdAt或经常先按 uid 查请求这个索引不一定覆盖。索引设计应从查询清单反推查询过滤排序建议索引全球最高分无/赛季bestScore DESCscore 或 seasonscore待匹配请求statusmatchingcreatedAt ASCstatuscreatedAt玩家最近战绩playerIdtimestamp DESCplayerIdtimestamp房间恢复roomId无主键已覆盖当前BattleRecord没有playerId timestamp一类索引而它又拆成 winner/loser 两个字段。若未来要查“我的全部战绩”可能需要调整数据模型或分别查询再合并。七、权限模型需要和“谁拥有这条数据”对齐cloud_db_schema.json为四类对象配置了相同权限permissions: [ {role:World,rights: [Read] }, {role:Authenticated,rights: [Read,Upsert,Delete] } ]从文件字面看世界角色可读已认证角色可读、写入和删除。对于公开排行榜世界可读可能符合展示需求但如果“任何已认证用户”都能更新任意PlayerStats、删除战绩或修改房间就不符合最小权限原则。生产设计至少应回答用户是否只能写自己的uid记录bestScore是否允许客户端直接提交BattleRecord由客户端还是可信服务端创建匹配请求能否被其他用户删除lastFrameData是否含有不应公开的网络或会话信息权限不能只在客户端校验因为修改客户端即可绕过。高价值分数、奖励与胜负结果应有服务端验证、签名事件或可信计算链路。本文提出的是演进要求不表示仓库当前已经部署了相应服务端。八、不要把客户端分数天然当成可信数据当前GameStats.score由本地引擎计算结算后交给本地ScoreManager。将这一路径直接换成云写入能实现多设备展示却不能自动防作弊。sequenceDiagram participant Cas客户端participant Vas校验服务participant DasCloud DB/排行榜C-V: 提交局号、事件摘要、分数、幂等键 V-V: 校验身份、时长、规则与重复提交 alt 校验通过 V-D: 写入权威战绩/更新最佳分 D--C: 返回排名结果else校验失败 V--C: 返回稳定错误码end轻量项目可以先接受“娱乐性排行榜”的弱可信度但要在产品说明中承认边界并把奖励发放与排行榜展示分离。只要排行关联虚拟资产或竞赛奖励服务端权威就不再是可选优化。九、生成文件为什么不应该手改四个模型文件头都有DO NOT EDIT。直接把duration: string改成number短期能让本地编译通过却会制造新的三方不一致云端实际对象类型 ≠ 本地JSON≠ 手改生成类正确链路应该是flowchart LRA[权威 Schema]--B[对象类型编译器]B-- C[生成模型类]B-- D[ObjectTypeInfoHelper]C -- E[客户端构建]D -- EA-- F[Schema 契约测试]C -- F D -- F如果必须在业务中使用更友好的字段名或类型应新增 Mapper/DTO而不是让生成层承担显示格式和领域规则。十、Schema 演进要区分兼容与破坏性变化变更通常兼容性处理建议新增可空字段较好客户端提供默认回退新增非空字段有风险先补历史数据再收紧约束字符串改 Long破坏性双写/迁移/灰度读删除字段破坏性先停止写入跨版本观察后删除修改主键高风险新对象类型或完整迁移修改索引影响性能先验证查询与数据量收紧权限影响旧客户端提供版本门槛和错误处理以BattleRecord.timestamp为例推荐采用扩展迁移而不是原地强转新增timestampMsLong 字段新客户端双写旧字段与新字段后台任务回填历史记录读路径优先新字段缺失时解析旧字段观察旧客户端占比停止旧字段写入并最终清理。如果数据尚未真实上线可直接统一 Schema 并重新生成但仍应保留一次契约检查避免下次再次漂移。十一、客户端 Mapper 隔离云模型页面不应直接依赖 Cloud DB 生成类的每一个细节。可以将云模型转换为应用模型interface LeaderboardItem { uid: string; displayName: string; avatarId: string; bestScore: number; updatedAtMs: number; } function toLeaderboardItem(source: PlayerStats): LeaderboardItem { return { uid: source.getUid(), displayName: source.getUserName() ||玩家, avatarId: source.getAvatar() ||avatar_default, bestScore: Math.max(0, source.getBestScore()), updatedAtMs: Math.max(0, source.getUpdatedAt()) }; }这样即使云端默认值、字段名或 SDK 对象形式变化ArkUI 页面仍消费稳定的LeaderboardItem。Mapper 也是处理userName/avatar是否存在、时间单位和默认头像的唯一位置。十二、本地 云端的同步策略AGC 指南提出断网或未登录时使用本地排行登录后展示云端排行。要让它真正可用还需定义写策略本地先写还是云端先写离线队列失败提交保存哪些字段何时重试幂等键同一局重试不能产生多条战绩冲突策略最高分可取 max昵称则需按版本/更新时间处理读取状态缓存数据、刷新中、云端失败分别如何显示退出登录是否清除云缓存如何保留本地个人数据。最高分适合使用单调合并bestScore max(local, cloud)但总局数、胜率不能简单取最大值。它们需要基于不可重复的对局记录聚合否则多设备同步会重复计数。十三、把 Schema 契约检查放进 CI 这次仓库中的差异完全可以自动发现。检查器至少应比较对象类型集合 ├─ 字段名集合 ├─ 字段类型 ├─ 主键与notNull├─ 默认值 ├─ 索引字段及顺序 └─Schema版本/来源元数据推荐测试用例用例失败条件BattleRecord 类型契约JSON 为 Long、Helper 为 StringPlayerStats 字段契约一侧缺 userName/avatar索引契约JSON 缺失 bestScore DESC默认值契约空字符串生成成字面量双引号映射往返Long 时间转换后精度丢失旧数据读取新增字段导致旧记录无法展示生成代码可不参与普通风格检查但必须参与契约检查和编译检查。“生成的”不等于“天然正确”它只意味着错误可能来自上游 Schema 或生成输入。十四、上线前检查清单 ✅明确 Cloud DB 与游戏排行榜服务的选型不混用概念确认哪份 Schema 是唯一权威源统一duration/timestamp的 Long/String 类型对齐PlayerStats.userName/avatar字段修正带额外引号的字符串默认值复核bestScore与匹配队列索引将权限收敛到数据所有者和可信服务建立本地缓存、离线队列和幂等策略对客户端分数定义可信度与防作弊边界用测试环境验证旧版/新版客户端兼容发布前运行 Schema 契约检查日志中不记录 Token、完整设备信息和个人数据。十五、总结 ✨“迷宫坦克派对”已经为云端演进准备了四个对象类型、生成类、Helper、索引和一份 AGC 接入指南这是值得继续完善的工程基础。但源码也明确告诉我们BattleRecord的两个时间字段存在 Long/String 漂移PlayerStats的userName/avatar在两份 Schema 中不一致索引和权限也需要以真实查询和数据所有权重新审视。从本地模型走向云端最重要的不是多写一个上传方法而是建立唯一权威 Schema、可重复的代码生成、DTO/Mapper 隔离、版本迁移、最小权限、离线幂等与契约测试。完成这些之前应把云排行称为“接入设计”或“原型准备”完成这些之后云端数据才有机会成为可维护、可升级、可解释的生产契约。推荐标签OpenHarmonyHarmonyOSArkTSAppGalleryConnectCloudDBSchema云数据库版本治理