原生鸿蒙像素画板实战 19:远程素材缓存

📅 2026/7/22 17:07:10
原生鸿蒙像素画板实战 19:远程素材缓存
给素材库接网络难点不在于发一次请求而在于网络不可用时编辑器仍然要能打开。素材面板不应该因为远程地址配置错误、接口返回空内容或 JSON 格式异常而变成一片空白。bitArt 把远程素材当作“可选增强”本地素材始终是可靠兜底缓存则负责减少重复请求并向界面明确暴露数据是否已经过期。远程素材必须是可失败的增强路径如果页面启动时只等远程接口返回离线用户会失去全部素材。更隐蔽的是内存缓存直接把内部数组暴露给组件某个组件排序或 push 后下一次打开素材库就得到被意外修改的全局数据。接口 JSON 也不能只要能解析就相信categories 和 materials 不是数组时后续渲染同样会失败。网络提供者与缓存仓储如何分工网络素材只能改善体验不能成为编辑器能否启动的前提。本地快照和缓存共同保证网络失败时仍有一份可用内容。缓存不能直接返回可被页面改写的引用MaterialLibraryService 的 getSnapshot 先问缓存没有缓存就立即返回本地快照并保存副本。refreshSnapshot 才尝试远程远程返回合法数据时与本地快照合并否则再回退本地。MaterialCacheRepository 在读取时克隆 categories 和 materials并计算 stale 标记。缓存过期并不等于数据不能用它只是告诉界面“可以尝试刷新”而不是让素材突然消失。远程提供者先检查是否启用远程素材再请求固定的 snapshot JSON。请求失败、空 body、JSON 解析异常或结构不合法都返回 null。这些失败被限制在服务内部页面只会接到一份仍可显示的 MaterialLibrarySnapshot。获取素材快照时缓存和本地来源先于网络请求保证首屏可用。static getSnapshot(): MaterialLibrarySnapshot { const cachedSnapshot MaterialCacheRepository.getSnapshot(); if (cachedSnapshot ! null) return cachedSnapshot; const localSnapshot LocalMaterialProviderService.getSnapshot(); MaterialCacheRepository.saveSnapshot(localSnapshot); return localSnapshot; } static async refreshSnapshot(): PromiseMaterialLibrarySnapshot { const remoteSnapshot await RemoteMaterialProviderService.fetchSnapshot(); if (remoteSnapshot ! null) { const mergedSnapshot MaterialLibraryService.mergeWithLocalFallback(remoteSnapshot); MaterialCacheRepository.saveSnapshot(mergedSnapshot); return mergedSnapshot; } return MaterialLibraryService.getSnapshot(); }远程与本地合并时要保留本地兜底本地与远程分类合并时先保留远程分类再按 ID 补入远程中没有的本地分类素材数组则把远程项和本地项并列确保本地模板永远可见。缓存 TTL 设为 6 小时过期时标记 stale但不立刻清空数据。这个策略适合非关键内容优先保证编辑器可用再争取内容新鲜度。static getSnapshot(): MaterialLibrarySnapshot | null { if (MaterialCacheRepository.cachedSnapshot null) return null; const snapshot MaterialCacheRepository.cloneSnapshot(MaterialCacheRepository.cachedSnapshot); snapshot.stale Date.now() - MaterialCacheRepository.cachedAt MATERIAL_CACHE_TTL_MS; return snapshot; } static saveSnapshot(snapshot: MaterialLibrarySnapshot): void { MaterialCacheRepository.cachedSnapshot MaterialCacheRepository.cloneSnapshot(snapshot); MaterialCacheRepository.cachedAt Date.now(); }缓存状态不等于创作项目状态素材缓存属于仓储层不属于项目持久化。清缓存只影响下次取得素材的来源不会删除用户的画布、图层或已导入的素材。界面可以根据 sourceLabel 和 stale 做轻量提示但不应把网络状态混入当前项目的脏状态。缓存的 stale 标记是内容状态不是项目修改刷新素材不应该让用户画布变脏。if (!NetworkApiClientService.isRemoteMaterialsEnabled()) return null; const result await NetworkApiClientService.getText(/materials/snapshot.json); if (!result.ok || result.body.length 0) return null; try { const dto JSON.parse(result.body) as RemoteMaterialSnapshotDto; if (!Array.isArray(dto.categories) || !Array.isArray(dto.materials)) return null; return { categories: dto.categories, materials: dto.materials, updatedAt: dto.updatedAt ?? Date.now(), stale: false, sourceLabel: 远程素材 }; } catch (_error) { return null; }网络素材链路里应该主动防住的失败远程失败时返回 null 交给上层回退不要把异常继续抛到素材面板。缓存读取和写入都要克隆快照避免组件修改全局数组。缓存过期只表示建议刷新不等于应该清空当前可用素材。远程素材接口里不应携带账号、私密地址或令牌。文章中的示例只描述公开快照结构真实部署时应由网络配置服务处理地址与开关。把“素材数据正确”与“网络配置正确”分开排查时也更容易定位问题。缓存策略要经得起离线场景远程缓存最有价值的测试不是网络正常而是网络故意不正常。可以先禁用远程开关、再模拟 404、空 body、JSON 语法错误和字段类型错误逐次打开素材库。每一种情况都应得到本地快照而不是空列表或未捕获异常。随后恢复一个合法远程快照确认它会与本地分类合并本地模板没有被远程内容挤掉。缓存副本也值得单测。取出一次快照后对调用方数组做排序或临时 push再重新取缓存第二次结果不应该继承第一次的修改。若发生继承说明仓储把内部引用暴露给了界面。最后模拟超过 TTL检查 stale 标记出现但素材仍能导入。这样能把“内容不新鲜”和“内容不可用”这两种状态清楚分开。怎样确认网络失败不会清空素材网络检查要故意制造失败响应只有回退行为正确才算素材链路可靠。关闭远程素材开关或断开网络确认素材库仍显示本地内容。模拟空响应、非法 JSON 和缺少数组字段的响应确认页面没有崩溃且回退到本地快照。刷新后修改某个页面使用的数组重新读取缓存确认原缓存没有被污染。等待或模拟 TTL 过期确认快照标记为 stale 但内容仍可用于导入。远程素材检查记录 断网后仍显示本地素材 异常响应不让面板崩溃 缓存读取返回副本 过期内容带 stale 标记但仍可导入远程缓存当前不承担的职责当前缓存是进程内内存缓存不保证应用重启后仍保留远程快照也不实现增量更新、签名验证或复杂离线同步。对当前素材规模而言本地兜底比把网络缓存做成另一套持久化系统更重要。这一篇的重点是让远程素材失败时编辑器仍有可靠的本地内容。素材库中的一个典型可编辑来源是拼豆图案下一篇把字符模板怎样落成图层、颜色和画布讲清楚。