HarmonyOS应用实战-启示散页-65-收藏别只按文本判断:给重复答案建立稳定来源身份
HarmonyOS 应用实战 65:收藏别只按文本判断,给重复答案建立稳定来源身份
同一句答案可能出现在多个题库里。只按answerText判断收藏,会把“恋爱题库里的再观察”和“职业题库里的再观察”压成同一条。用户从收藏页回看时,不知道答案来自哪里,取消收藏也可能误删另一条。
本文解决四个问题:
- 把收藏身份从文本升级为来源身份
- 保存 deckId、answerId 和文本快照
- 兼容旧的按文本收藏
- 验证重复答案、删除题库和迁移路径
按文本去重会丢失业务来源
文本相同不代表业务含义相同。收藏应该记录用户当时收藏的是哪套题库里的哪条答案,而不是只记一段字符串。
故障链:两个题库都有同文答案 -> 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};}旧收藏不能伪造来源。没有deckId和answerId的历史数据只能标记 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,才能同时保护重复答案、来源追踪和删除后的可读性。
