HarmonyOS应用实战-启示散页-65-收藏别只按文本判断:给重复答案建立稳定来源身份

📅 2026/8/1 19:32:06 👁️ 阅读次数 📝 编程学习
HarmonyOS应用实战-启示散页-65-收藏别只按文本判断:给重复答案建立稳定来源身份

HarmonyOS 应用实战 65:收藏别只按文本判断,给重复答案建立稳定来源身份

同一句答案可能出现在多个题库里。只按answerText判断收藏,会把“恋爱题库里的再观察”和“职业题库里的再观察”压成同一条。用户从收藏页回看时,不知道答案来自哪里,取消收藏也可能误删另一条。

本文解决四个问题:

  1. 把收藏身份从文本升级为来源身份
  2. 保存 deckId、answerId 和文本快照
  3. 兼容旧的按文本收藏
  4. 验证重复答案、删除题库和迁移路径


按文本去重会丢失业务来源

文本相同不代表业务含义相同。收藏应该记录用户当时收藏的是哪套题库里的哪条答案,而不是只记一段字符串。

故障链:两个题库都有同文答案 -> FavoriteService.has(text) 命中 -> 第二条无法收藏 -> 收藏页来源不可信

收藏的根问题是身份,不是按钮状态。同一句答案在不同题库里含义不同,只用文本去重会把来源压扁。

FavoriteIdentity 由来源组成

稳定身份至少需要 deckId 和 answerId。answerText 是展示快照,不应该成为唯一主键。

interfaceFavoriteIdentity{deckId:string;answerId:string;}interfaceFavoriteRecord{id:string;identity:FavoriteIdentity;answerTextSnapshot:string;deckNameSnapshot:string;createdAt:number;}

FavoriteIdentity要由来源字段组成。deckId + answerId表示用户收藏的是哪个业务对象,文本只是展示快照。

收藏服务按 identity 判断是否存在

同文本不同来源允许同时收藏;同来源重复点击才是取消或保持。

classFavoriteService{asynchas(identity:FavoriteIdentity):Promise<boolean>{constrecords=awaitFavoriteRepository.loadAll();returnrecords.some((item)=>item.identity.deckId===identity.deckId&&item.identity.answerId===identity.answerId);}asyncadd(record:FavoriteRecord):Promise<void>{if(awaitthis.has(record.identity)){return;}awaitFavoriteRepository.saveAll([record,...awaitFavoriteRepository.loadAll()]);AppStorage.setOrCreate('favorite.changedAt',Date.now());}}

服务按 identity 判断存在性后,同文本不同来源可以并存,同来源重复点击才会变成取消或忽略。

旧收藏迁移要标记 legacy

旧数据只有文本时不能凭空还原来源。迁移时应保留 legacy 状态,页面展示“来源未知”,而不是伪造 deckId。

interfaceLegacyFavoriteRecord{id:string;answerTextSnapshot:string;deckNameSnapshot?:string;legacy:true;}functionmigrateFavorite(raw:FavoriteRecord|LegacyFavoriteRecord):FavoriteRecord|LegacyFavoriteRecord{if('identity'inraw){returnraw;}return{...raw,legacy:true};}

旧收藏不能伪造来源。没有deckIdanswerId的历史数据只能标记 legacy,让页面诚实展示“来源未知”。

收藏页展示来源快照,不依赖题库仍存在

题库删除后,收藏仍应能展示当时的答案文本和题库名快照;如果来源仍存在,再提供跳转回题库。

@Componentstruct FavoriteCard{@Proprecord:FavoriteRecord|LegacyFavoriteRecord;build(){Column(){Text(this.record.answerTextSnapshot)Text('deckNameSnapshot'inthis.record?this.record.deckNameSnapshot??'来源未知':'来源未知')}}}

收藏页展示快照,是为了在原题库被删除后仍然可读。跳转回来源题库是增强能力,不应该成为展示收藏的前置条件。

验证重复答案和旧数据

准备两个题库包含相同答案,分别收藏,确认收藏页出现两条且来源不同。再准备旧格式收藏,确认不会被误判为新身份。

验证:同文本不同 deck 可同时收藏;同 identity 再点不重复;删除来源题库后收藏卡片仍展示快照;legacy 收藏不伪造来源。

重复答案和旧数据要放在同一轮验证里。否则新逻辑看似正确,却可能在迁移旧收藏时误删用户内容。

收藏身份排查表

如果收藏逻辑看起来异常,先看主键由什么组成。

现象先看哪里修复
同文答案不能分别收藏是否按 text 去重改用 deckId+answerId
删除题库后收藏空白是否只读 live deck保存快照
旧收藏来源乱是否伪造来源标记 legacy

排查收藏异常时先看主键组成。只要主键还是文本,后续所有来源展示和取消收藏都会不稳定。

收藏身份要和展示快照分开验

deckId + answerId解决的是身份问题,answerTextSnapshot + deckNameSnapshot解决的是展示问题。两者都要验,不能只看按钮是否变成已收藏。

验证样本: 1. deckA.answer01 = 再观察一天 2. deckB.answer09 = 再观察一天 3. 两条都收藏,列表应出现两条,来源不同。 4. 删除 deckA 后,deckA 的收藏仍显示快照,但不提供跳转。

这个样本能同时覆盖重复文本、来源展示和删除后的可读性。

旧数据迁移不要伪造确定性

旧收藏只有文本时,系统不知道它来自哪个题库。更负责任的处理是标记 legacy,并在 UI 上弱化来源,而不是根据文本反查一个“看起来可能”的题库。

旧数据状态处理原因
只有答案文本标记 legacy无法还原来源
有题库名快照仅用于展示题库名可能重名或改名
后续再次收藏同文答案生成新 identity不与 legacy 强行合并

迁移文章尤其要守住这个边界:不确定就是不确定,不能为了列表整齐伪造业务事实。

交付记录看三类证据

收藏重构完成后,至少留下三类证据:重复答案样本、旧数据迁移样本、删除来源题库后的截图或记录。缺任一类,都不能说收藏身份链路完整。

证据证明什么
重复答案样本文本不再是唯一主键
legacy 样本旧收藏不会被误删或伪造来源
删除题库样本收藏页不依赖 live deck 才能展示

如果只是代码里新增了FavoriteIdentity,还没有这些行为证据,只能算模型设计完成。

收藏取消也要按 identity 处理

收藏文章如果只讲新增,仍然不完整。取消收藏同样要按FavoriteIdentity删除,否则用户取消 deckA 的“再观察一天”,可能把 deckB 里同文答案也删掉。

asyncfunctionremoveFavorite(identity:FavoriteIdentity):Promise<void>{constrecords=awaitFavoriteRepository.loadAll();constnext=records.filter((item)=>{if(!('identity'initem)){returntrue;}returnitem.identity.deckId!==identity.deckId||item.identity.answerId!==identity.answerId;});awaitFavoriteRepository.saveAll(next);AppStorage.setOrCreate('favorite.changedAt',Date.now());}

这段删除逻辑故意保留 legacy 记录,因为 legacy 没有可靠来源,不能被一次新身份取消误伤。验证时要同时做“取消新收藏”和“保留旧收藏”两件事,才能证明身份治理没有造成数据丢失。

人工评审时看收藏页是否依赖实时题库

收藏页应该能在来源题库被删除后继续显示收藏快照。评审时可以先收藏一条答案,再删除对应题库,然后重进收藏页。若页面空白或崩溃,说明它仍然依赖 live deck,而不是依赖收藏记录自身。

状态收藏页表现允许动作
来源题库存在显示答案和题库名,可跳转来源查看来源
来源题库删除显示答案快照和题库名快照不提供来源跳转
legacy 收藏显示答案快照,来源未知可删除,不参与来源跳转

这一步能补足模型之外的体验证据。用户收藏的是“当时有价值的答案”,不是一条必须依赖题库存在才能打开的弱引用。

小结

收藏的身份不能只靠文本。用 deckId 和 answerId 表示来源,用文本和题库名作为展示快照,再把旧数据标记为 legacy,才能同时保护重复答案、来源追踪和删除后的可读性。