尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

羽球搭子 HarmonyOS 实战(19):云端 Repository 的本地优先封装

羽球搭子 HarmonyOS 实战(19):云端 Repository 的本地优先封装
📅 发布时间:2026/7/22 15:07:58

一、球场网络不应该决定比分能否保存

体育馆常见弱网、临时断网和后台切换。若计分页必须等待云端 200 响应才显示保存成功,用户会重复点击,甚至在网络恢复前丢掉整场结果。本地优先的含义是:领域结果先写入本地存储并立即驱动 UI,云端同步作为随后发生的可靠任务;同步失败要可见、可重试,但不能回滚已经确认的本地比分。

Repository 位于页面与 HTTP 客户端之间,负责本地 ID 与云端 ID 映射、登录门禁、版本读取、事件提交、冲突重试、快照拉取和归档处理。页面只关心“本地保存是否完成”和“云端当前是已同步还是待重试”。网络访问和错误分类可参考HarmonyOS 网络管理开发指南。

二、把本地成功与云端成功拆开

一次比分保存至少有两种结果:本地写入成功、云端同步成功。用单个boolean无法表达“本地成功但云端失败”,页面容易把可继续使用的数据误报为整体失败。返回结构应保留两个维度和可重试原因。

场景localSavedcloudSynced页面反馈后续动作
未登录,本地写入成功truefalse已保存到本机登录后可同步
已登录,同步成功truetrue已保存并同步无
断网truefalse已保存,等待网络写入失败队列
版本冲突后重试成功truetrue已同步最新版本更新本地版本
本地写入失败falsefalse保存失败阻止离开并重试

这种结果模型能保持用户心智稳定:看到“已保存本地”就不会担心比分消失,同时又知道协作者暂时看不到最新结果。

三、新对局先建立 ID 映射

离线创建的对局使用本地 ID。首次同步时上传摘要与详情,服务端返回云端 ID、版本和邀请信息;Repository 保存别名映射,后续事件都解析成云端 ID。若上传成功但应用在拉取详情前退出,下次仍能从映射继续,不会重复创建云端对局。

static async uploadLocalSession(localId: string): Promise<CreateSessionResult> { const summary = SessionStore.findSummary(localId) const detail = SessionStore.getDetail(localId) if (summary === undefined || detail === undefined) { throw new Error('local session not found') } const request = toCreateSessionRequest(summary, detail) request.clientContext = ClientContextProvider.current() const result = await HttpClient.post<CreateSessionResult>('/sessions', request) SessionStateStore.saveCloudState(result.id, result.version, result.inviteCode) SessionStateStore.saveAlias(localId, result.id) return result }

ID 映射必须是双向可查:提交事件时从本地找到云端 ID;拉取云端详情时也要找到对应本地 ID,以便替换原有场次而不是列表里出现一份重复副本。

四、已存在对局优先提交事件

比分变化不必每次上传完整快照。事件请求包含幂等clientEventId、baseVersion、类型和载荷。服务端接受后返回新版本,客户端再拉取详情并缓存,确保本地最终与服务端权威快照一致。

static async recordScore(change: ScoreChange): Promise<boolean> { if (!AuthStore.isSignedIn()) return false const sessionId = SessionStateStore.resolveSessionId(change.sessionId) const baseVersion = await this.ensureKnownVersion(sessionId) if (baseVersion <= 0) return false const request: RecordEventRequest = { clientEventId: SessionStateStore.createEventId('score_updated', sessionId, change.matchId), baseVersion, type: change.previousFinishedAt > 0 ? 'score.updated' : 'match.finished', payload: { matchId: change.matchId, scoreA: change.scoreA, scoreB: change.scoreB, finishedAt: change.finishedAt } } return this.sendWithConflictRetry(sessionId, request) }

首次完成比赛使用match.finished,对已完成比分的修正使用score.updated。明确事件类型有利于服务端审计、实时推送和统计重算,不要把所有变化都模糊成“update”。

五、409 冲突需要读取服务端版本

两个设备基于相同版本提交事件,后到的一方会收到 409。客户端不能盲目无限重试,也不能继续使用旧baseVersion。Repository 从响应体读取serverVersion,更新本地版本后只重试一次;若仍失败,转入快照替换或补偿队列。

private static async sendWithConflictRetry( sessionId: string, request: RecordEventRequest ): Promise<boolean> { try { const result = await this.recordEvent(sessionId, request) if (!result.accepted) return false await this.pullAndCacheDetail(sessionId) return true } catch (error) { const serverVersion = this.readConflictVersion(error as Error) if (serverVersion <= 0 || serverVersion === request.baseVersion) return false SessionStateStore.updateVersion(sessionId, serverVersion) const retry = { ...request, baseVersion: serverVersion, clientEventId: SessionStateStore.createEventId('score_retry', sessionId, request.payload.matchId) } const result = await this.recordEvent(sessionId, retry) if (result.accepted) await this.pullAndCacheDetail(sessionId) return result.accepted } }
错误是否自动重试收敛策略
网络超时不立即连点写入待同步队列,网络恢复后执行
401否刷新登录或要求重新认证
404否拉取列表确认是否被归档/删除
409 且有新版本一次更新 baseVersion 后重试
409 再次失败否拉取快照或人工确认
5xx延迟重试指数退避并保留本地数据

六、拉取详情后再推进已应用版本

“获取了事件列表”不等于“本地已应用最新状态”。若先把版本推进到最新值,随后详情拉取失败,下次补偿会认为没有缺口,永远跳过未应用变化。版本应该表示本地已经缓存的快照版本,只在详情成功转换并写入SessionStore后更新。

static async pullAndCacheDetail(sessionId: string): Promise<CloudSessionDetail> { const detail = await this.getDetail(sessionId) if (detail.status === 'archived') { this.discardLocalSession(detail.id) return detail } const localId = SessionStateStore.localIdForCloudSession(detail.id) const bundle = toLocalSessionBundle(detail, localId) SessionStore.upsertCloudSession(bundle.summary, bundle.detail) SessionStateStore.save({ sessionId: detail.id, version: detail.version, status: detail.status, updatedAt: Date.now() }) return detail }

这条顺序保证版本和快照绑定。归档状态则先清理本地别名和缓存,避免首页重新展示已被协作者关闭的对局。

七、事件失败后用快照替换兜底

当比分事件因历史版本差异无法合并,但本地仍保存完整对局详情,可以提交session.replaced快照作为明确兜底。快照替换不是常规路径,因为载荷更大、冲突影响更广;它适合在事件重试失败后由补偿任务执行,并带上当前baseVersion。

static async replaceLocalSnapshot(localId: string): Promise<boolean> { const cloudId = SessionStateStore.resolveSessionId(localId) const summary = SessionStore.findSummary(localId) ?? SessionStore.findSummary(cloudId) const detail = SessionStore.getDetail(localId) ?? SessionStore.getDetail(cloudId) if (summary === undefined || detail === undefined) return false const baseVersion = await this.ensureKnownVersion(cloudId) if (baseVersion <= 0) return false const result = await this.recordEvent(cloudId, { clientEventId: SessionStateStore.createEventId('session_replaced', cloudId), baseVersion, type: 'session.replaced', payload: toSnapshotPayload(summary, detail) }) if (result.accepted) await this.pullAndCacheDetail(cloudId) return result.accepted }

补偿任务要记录重试次数、最后错误和版本范围,达到上限后停止自动执行并提示用户,不能在后台无限请求。

八、用断网与多设备场景验收

Repository 验收不能只看正常请求。应把本地保存、版本冲突、归档和恢复链路串起来,确认任何失败都不会让本地比分倒退。

1. 未登录完成一场比赛,确认本地统计立即更新,应用重启后结果仍在。 2. 登录并同步,确认本地 ID 只对应一个云端对局,列表没有重复项。 3. 断网修改比分,确认提示“已保存本地”,失败记录可见且不会反复弹窗。 4. 恢复网络执行重试,确认服务端版本推进并拉取最新详情。 5. 两台设备基于同一版本提交,确认后一台只做一次 409 版本刷新重试。 6. 服务端归档对局后拉取详情,确认本地缓存和别名被清理,首页不再显示旧项。 7. 让详情拉取失败,确认已应用版本不提前推进,下一次补偿仍能发现缺口。

九、总结

本地优先 Repository 的关键不是“请求失败就 catch”,而是把两种成功拆开:比分先可靠落盘,云端随后通过带版本的事件收敛。新对局建立双向 ID 映射,已有对局提交幂等事件,409 只刷新版本重试一次,详情成功缓存后才推进已应用版本,事件无法合并时再使用快照补偿。这样弱网不会阻断球场计分,多设备协作也有清晰的冲突边界。

相关新闻

  • 棋牌类游戏二级直付通商户的生存法则:选对一级伙伴与守住防沉迷底线
  • iOS开发必备:第三方库分类与选型实战指南
  • Flash内容迁移与HTML5替代方案实践指南

最新新闻

  • 苏州黄金回收哪家靠谱?6 区直营门店实测,大众点评真实评价告诉你答案 - 奢侈品回收机构参考
  • 甄选2026纯棉T恤厂家, 娇灿服饰官方介绍 - 企师傅推荐官
  • 温州劳力士官方售后服务中心|官方网站权威公布(2026年7月最新) - 劳力士售后服务官网
  • 知网AIGC检测报告怎么看?段落分布图和逐段标注读懂了降AI效率翻倍
  • 界面组件DevExpress WinForms v23.2 - 数据可视化功能升级
  • 2026年7月PCB板AOI检测高速相机选择参考丨关键参数 - 信息热点

日新闻

  • AI云原生实战05-金融AI上云最难的不是技术,是“不出事“——TCE银行风控架构拆解
  • 2026年GEOSEO优化公司选型深度测评:五大硬核标准严选,这六家重塑搜索增长新格局 - 品牌前沿专家
  • **核验!2026年7月卡地亚香港**售后网点地址及服务电话公告 - 卡地亚服务中心

周新闻

  • SaaS软件行业GEO实践:AI搜索时代的品牌可见性与获客新路径
  • 什么是PCTFE?医药高端包装的“防潮王牌“材料
  • 【JVM调优实战】16-可视化利器-JConsole-VisualVM-JMC

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号