HarmonyOS应用实战-启示散页-50-发布截图别暴露用户题库:准备一套可复现的演示数据
HarmonyOS 应用实战 50发布截图别暴露用户题库把截图环境做成可回滚数据沙箱发布截图最怕一种“看起来很干净”的做法开发者先把自己的题库改名删掉几条提问历史再对结果页做几次手工打码然后拿这些图去交材料。单张图里可能只露出一个题库名连着首页、抽取页、结果页、收藏页一起看就能还原出用户最近问过什么、收藏过哪条答案、从哪个题库抽到过结果。答案之书是离线应用没有账号体系也没有服务端同步但这并不代表发布材料没有隐私边界。它的题库、收藏、提问历史都在本地Preferences页面展示时又会把答案正文、题库来源和最近问题带出来。第 50 篇要解决的不是“怎么打码”而是把发布截图前的本地数据切换做成可回滚的数据沙箱进入前备份真实数据截图时只展示演示数据退出后恢复原状并留下可复查的截图清单。这篇文章解决四件事从现有源码里找出发布截图会暴露哪些用户内容。说明为什么不能把截图准备逻辑塞进SeedLoader或页面回调。用现有 Repository 思路设计进入、安装、恢复三段流程。给出发布前复核表确认截图、日志和恢复路径都能解释清楚。先把泄露链路从源码里找出来第一个判断标准很简单页面上能出现的文字如果来自真实用户输入或真实本地题库就不能直接进入发布截图。答案之书当前已经有清晰的数据边界问题不在“找不到 owner”而在发布前没有把这些 owner 组合成一条临时数据沙箱链路。画面位置真实来源风险首页题库卡片DeckService.list()返回DeckSummary.name、count真实题库名和规模暴露抽取页参数路由里的question、deckId最近提问和当前题库暴露结果页答案AnswerPage.param.text答案正文直接入图结果页来源DeckService.get(this.param.deckId)题库来源暴露收藏页FavoriteService.list()收藏答案和来源题库暴露历史弹层QuestionHistoryService.list()用户问题原文暴露这张表比“发布前清空数据”更重要。清空只解决当次截图源码 owner 表能告诉我们每个页面为什么会出现真实内容以及应该从哪一层替换为演示数据。结果页已经把答案和题库名连在一起结果页是最典型的泄露点。它不仅展示答案正文还会异步读取题库名用户点击收藏时又把答案和来源题库一起写进收藏。Componentexportstruct AnswerPage{Stateprivatefavorited:booleanfalse;StateprivatedeckName:string;privateparam:AnswerParams{deckId:,answerId:,text:};privateasyncloadDeckName():Promisevoid{constdeckawaitDeckService.get(this.param.deckId);this.deckNamedeck?.name??;}privateonToggleFavorite():void{FavoriteService.add({text:this.param.text,fromDeckId:this.param.deckId,fromDeckName:this.deckName||undefined});}}这段代码本身没有错它是正常业务需要结果页应该知道答案来自哪个题库收藏也应该保留来源。但发布截图不能直接复用真实运行态。只要this.param.text来自真实抽取结果this.deckName来自真实题库名截图就不是演示材料而是用户内容的再展示。更稳的处理方式不是在AnswerPage里加一堆发布分支而是在进入截图环境前把页面会读取到的数据整体替换成受控演示数据。页面继续走原来的业务链路截图环境负责保证这条链路读到的是演示内容。历史和收藏必须一起处理很多发布图只检查首页和结果页忽略了底部弹层或二级页面。答案之书的提问历史和收藏都是真实 store一旦只替换题库收藏页和历史弹层仍会露出用户内容。classQuestionHistoryServiceImpl{asyncadd(text:string):Promisevoid{consttrimmed:stringtext.trim();if(!trimmed){return;}constall:string[]awaitQuestionHistoryRepository.loadAll();constfiltered:string[]all.filter((q:string):booleanq!trimmed);filtered.unshift(trimmed);constnextfiltered.lengthMAX_QUESTION_HISTORY?filtered.slice(0,MAX_QUESTION_HISTORY):filtered;awaitQuestionHistoryRepository.saveAll(next);AppStorage.setOrCreate(AppStorageKey.LastQuestionHistoryUpdateAt,Date.now());}}classFavoriteServiceImpl{asyncadd(payload:AddFavoritePayload):PromiseFavorite{constall:Favorite[]awaitFavoriteRepository.loadAll();constfav:Favorite{id:newId(fav),text:payload.text.trim(),fromDeckId:payload.fromDeckId,fromDeckName:payload.fromDeckName,createdAt:Date.now()};all.push(fav);awaitFavoriteRepository.saveAll(all);AppStorage.setOrCreate(AppStorageKey.LastFavoriteUpdateAt,fav.createdAt);returnfav;}}这两段源码说明了一个交付边界发布截图数据沙箱必须同时覆盖题库、收藏、提问历史和当前题库 id。只处理其中一项会制造“首页是演示数据收藏页是真实数据”的半干净状态。SeedLoader 不是发布截图入口当前项目已经有SeedLoader但它的目标是首次启动、默认题库缺失、默认题库损坏或种子版本升级时恢复默认题库。它不应该被改成发布截图入口。classSeedLoaderImpl{asyncrun(context:common.UIAbilityContext):Promisevoid{constids:string[]awaitDeckRepository.loadIndex();constdefaultDeck:Deck|nullawaitDeckRepository.loadDeck(DEFAULT_DECK_ID);constseededVersion:numberawaitPreferencesStore.getNumber(PrefStoreName.App,AppPrefKey.LastSeededVersion,0);constneedsSeed:booleanids.length0||defaultDecknull||defaultDeck.answers.lengthSEED_RESTORE_THRESHOLD||seededVersionDEFAULT_DECK_SEED_VERSION;if(!needsSeed){awaitthis.ensureCurrentDeck();return;}constdeck:Deckawaitthis.buildDefaultDeck(context);awaitDeckService.putRaw(deck);AppStorage.setOrCreate(AppStorageKey.LastDeckUpdateAt,deck.updatedAt);}}这里的 owner 是“默认题库自愈”。发布截图需要的是“真实数据备份、演示数据安装、截图完成恢复、证据留痕”。如果把两者混在一起冷启动时就很难解释当前题库为什么被替换也很难保证退出截图环境后真实收藏和历史已经恢复。演示场景要写成稳定合同演示数据不应该散落在页面里。它应该像一份发布素材合同本次截图使用哪个题库、哪个问题、哪些收藏、覆盖哪些页面。这样补图时才能复用同一套输入。interfaceReleaseScreenshotScenario{scenarioId:string;seedVersion:number;currentDeckId:string;decks:Deck[];favorites:Favorite[];questionHistory:string[];screenshotPages:Arrayhome|drawing|answer|favorites|settings;}constreleaseScenarioV1:ReleaseScreenshotScenario{scenarioId:release-screenshot-v1,seedVersion:1,currentDeckId:release-deck-daily,decks:[{id:release-deck-daily,name:发布演示题库,builtIn:false,colorKey:blue,answers:[{id:release-answer-1,text:先把问题写清楚再决定下一步。,createdAt:1760000000000},{id:release-answer-2,text:今天适合做一次小范围验证。,createdAt:1760000000000}],createdAt:1760000000000,updatedAt:1760000000000}],favorites:[],questionHistory:[今天要不要继续推进],screenshotPages:[home,drawing,answer,favorites,settings]};这里刻意使用固定 id 和固定时间。发布截图追求的是可复现不是模拟真实随机行为。随机答案、随机颜色、随机时间都会让补图和复查变得困难。备份用现有 Repository 粒度不要假装有整库导出当前PreferencesStore提供的是getString、setString、getJson、setJson、remove这些基础能力并没有一键导出或导入整个 store 的接口。因此文章里的落地方案不应该依赖当前项目不存在的整库能力。更贴近现有工程的做法是按业务 Repository 粒度备份题库全量、收藏全量、提问历史全量、当前题库 id。interfaceReleaseUserSnapshot{decks:Deck[];favorites:Favorite[];questionHistory:string[];currentDeckId:string;createdAt:number;}classReleaseSnapshotStore{asynccreateSnapshot():PromiseReleaseUserSnapshot{return{decks:awaitDeckRepository.loadAll(),favorites:awaitFavoriteRepository.loadAll(),questionHistory:awaitQuestionHistoryRepository.loadAll(),currentDeckId:awaitDeckService.getCurrentId(),createdAt:Date.now()};}}这个粒度更利于排障。恢复失败时可以分别判断是题库没有恢复、收藏没有恢复、历史没有恢复还是currentDeckId没有写回。整库黑盒导出看似简单出了问题反而难定位。题库恢复还要多一步因为当前DeckRepository只有loadAll()、saveDeck()、removeDeck()和saveIndex()没有一键替换全量题库的方法。发布截图能力可以在自己的边界里补一个很薄的批量写入器内部仍然走现有 Repository。classReleaseDeckSnapshotWriter{asyncreplaceAll(decks:Deck[]):Promisevoid{constoldIds:string[]awaitDeckRepository.loadIndex();for(constidofoldIds){awaitDeckRepository.removeDeck(id);}for(constdeckofdecks){awaitDeckRepository.saveDeck(deck);}awaitDeckRepository.saveIndex(decks.map((deck:Deck):stringdeck.id));}}这段代码不是为了绕过 Repository而是把“发布截图要临时替换题库全集”这个特殊动作收在发布准备层。页面和普通业务 Service 不需要知道它存在。安装演示数据时要刷新所有相关 live state演示数据安装不是把 JSON 写进去就结束。现有页面依赖AppStorageKey.LastDeckUpdateAt、LastFavoriteUpdateAt、LastQuestionHistoryUpdateAt这类时间戳触发刷新如果只写 Repository不发刷新信号页面可能仍显示旧数据。classReleaseScreenshotInstaller{asyncinstallScenario(scenario:ReleaseScreenshotScenario):Promisevoid{awaitnewReleaseDeckSnapshotWriter().replaceAll(scenario.decks);awaitFavoriteRepository.saveAll(scenario.favorites);awaitQuestionHistoryRepository.saveAll(scenario.questionHistory);awaitPreferencesStore.setString(PrefStoreName.App,AppPrefKey.CurrentDeckId,scenario.currentDeckId);constnowDate.now();AppStorage.setOrCreate(AppStorageKey.CurrentDeckId,scenario.currentDeckId);AppStorage.setOrCreate(AppStorageKey.LastDeckUpdateAt,now);AppStorage.setOrCreate(AppStorageKey.LastFavoriteUpdateAt,now);AppStorage.setOrCreate(AppStorageKey.LastQuestionHistoryUpdateAt,now);}}这一步要特别注意deck_index和deck:id的一致性。只写单个题库 JSON、不更新索引首页列表会读不到只更新索引、不清旧题库退出截图环境后又可能残留演示数据。退出时先恢复再宣布截图环境结束恢复顺序也要明确。先恢复真实题库、收藏和历史再刷新AppStorage最后记录本次截图场景已退出。顺序反了页面可能短暂读到空数据或演示数据。classReleaseScreenshotSession{privatesnapshot:ReleaseUserSnapshot|nullnull;asyncenter(scenario:ReleaseScreenshotScenario):Promisevoid{this.snapshotawaitnewReleaseSnapshotStore().createSnapshot();awaitnewReleaseScreenshotInstaller().installScenario(scenario);awaitReleaseChecklistRepository.markEntered({scenarioId:scenario.scenarioId,seedVersion:scenario.seedVersion,pages:scenario.screenshotPages});}asyncexit():Promisevoid{if(!this.snapshot){thrownewError(release screenshot snapshot missing);}awaitnewReleaseDeckSnapshotWriter().replaceAll(this.snapshot.decks);awaitFavoriteRepository.saveAll(this.snapshot.favorites);awaitQuestionHistoryRepository.saveAll(this.snapshot.questionHistory);awaitPreferencesStore.setString(PrefStoreName.App,AppPrefKey.CurrentDeckId,this.snapshot.currentDeckId);constnowDate.now();AppStorage.setOrCreate(AppStorageKey.CurrentDeckId,this.snapshot.currentDeckId);AppStorage.setOrCreate(AppStorageKey.LastDeckUpdateAt,now);AppStorage.setOrCreate(AppStorageKey.LastFavoriteUpdateAt,now);AppStorage.setOrCreate(AppStorageKey.LastQuestionHistoryUpdateAt,now);}}这段设计的核心是“页面不感知发布截图环境”。页面仍然走原来的DeckService、FavoriteService、QuestionHistoryService只是进入截图环境后这些 Service 读到的是演示数据。这样可以减少页面分支也更容易在发布后移除或关闭这段能力。日志复查不能只看有没有报错发布截图常常会配合日志排障。当前项目里的日志大多记录 id、count、错误信息例如结果页记录show answer id收藏服务记录总数。这种方向是对的发布材料不需要打印答案全文。// 结果页只记录 answerId避免把答案正文写入 hilog。hilog.info(DOMAIN,TAG,show answer id%{public}s,p.answerId);// 收藏服务只记录数量不记录收藏文本。hilog.info(DOMAIN,TAG,add favorite total%{public}d,all.length);// 提问历史只记录数量不记录问题原文。hilog.info(DOMAIN,TAG,add total%{public}d,next.length);发布前复查时要重点找反例question、answerText、deckName是否被直接写进日志分享、复制、收藏失败时是否把业务正文拼进错误信息。日志能帮助排障但不能成为第二条泄露渠道。发布前复核要覆盖五条路径发布截图完成前建议按五条路径复核。每条路径都对应一个真实 owner失败后能直接定位而不是只说“页面看起来不对”。路径操作通过标准失败时先看进入前备份进入截图环境前读取题库、收藏、历史、当前题库四类数据都有快照ReleaseSnapshotStore.createSnapshot()演示安装安装releaseScenarioV1首页、结果页、收藏页只显示演示内容ReleaseScreenshotInstaller.installScenario()页面刷新进入后重进首页、收藏页、历史弹层页面读取最新数据AppStorageKey.Last*UpdateAt日志复查搜索hilog附近的业务字段不出现问题原文和答案全文AnswerPage、FavoriteService、QuestionHistoryService退出恢复退出截图环境并冷启动原题库、收藏、历史、当前题库恢复Repository 批量恢复和CurrentDeckId如果某条路径当前还没有真机执行就在发布记录里写清楚它仍需实机复核。不要把静态源码复查、一次手动点击或文章示例说成完整运行证明。本地复查命令要围绕泄露字段写下面这些命令不是为了证明功能已经上线而是帮助写文章和做发布前复查时快速定位风险点。rg-nAnswerPage|param.text|deckName|FavoriteService.addD:\ProgramData\huawei\lesson\The_Book_of_Answers\libraryHSP\src\main\ets rg-nQuestionHistoryService|FavoriteRepository|QuestionHistoryRepositoryD:\ProgramData\huawei\lesson\The_Book_of_Answers rg-nhilog|question|answerText|fromDeckName|deckNameD:\ProgramData\huawei\lesson\The_Book_of_Answers\libraryHSP\src\main\ets rg-nPrefStoreName|AppPrefKey|AppStorageKeyD:\ProgramData\huawei\lesson\The_Book_of_Answers\libraryHAR\src\main\ets第一条找结果页泄露入口第二条找收藏和历史的持久边界第三条看日志有没有正文风险第四条确认恢复时要刷新哪些 live state。命令输出只能说明源码风险位置不能替代真机截图和退出恢复验证。常见问题与处理现象根因处理首页是演示题库结果页仍是真实答案只替换了题库列表没有替换当前抽取参数固定currentDeckId用演示题库完成抽取路径收藏页出现真实答案没有备份并替换favorite_list进入截图环境时用演示收藏列表替换退出时恢复历史弹层出现真实问题只清了页面状态没有写QuestionHistoryRepository用演示历史覆盖并刷新LastQuestionHistoryUpdateAt退出后当前题库丢失恢复了题库数据但没有恢复CurrentDeckId快照里保存当前题库 id退出后写回 Preferences 和 AppStorage补图时内容和上次不同演示场景用了随机 id 或当前时间固定scenarioId、答案 id、题库 id 和种子版本这些问题都不适合用 Toast 掩盖。Toast 只能提示当次操作不能证明发布截图来自干净数据也不能保证退出后真实数据恢复。小结第 50 篇真正要讲的是一个发布前数据沙箱现有源码已经证明题库、答案、收藏和历史都会进入页面发布截图前要把这几类数据统一换成演示场景截图后再恢复真实快照。这个方案的关键不是新增多少类而是把 owner 收清楚SeedLoader继续负责默认题库业务 Service 继续服务页面截图环境只负责备份、安装、恢复和清单。这样写出来的发布材料才可复现、可排障也不会把用户内容带进截图和日志。

相关新闻

Git版本控制完整教程

Git版本控制完整教程

1、Git简介与核心价值:1.1、Git在开发中的核心作用:Git 在开发中可以管理好我们的代码,包括代码的更改、删除、合并等,极大提升团队协作效率和开发效率。实际运用场景:在学校给的作业任务中,要我们敲一个记…

2026/7/29 19:56:33 阅读更多 →
InceptionTime震撼登场:时间序列分类领域的AlexNet革命

InceptionTime震撼登场:时间序列分类领域的AlexNet革命

InceptionTime震撼登场:时间序列分类领域的AlexNet革命 【免费下载链接】InceptionTime InceptionTime: Finding AlexNet for Time Series Classification 项目地址: https://gitcode.com/gh_mirrors/in/InceptionTime InceptionTime是GitHub加速计划中的明星…

2026/7/29 19:56:33 阅读更多 →
计算机毕业设计之基于SpringBoot的餐饮库存追踪系统设计与实现

计算机毕业设计之基于SpringBoot的餐饮库存追踪系统设计与实现

随着信息技术的飞速发展和互联网的普及,线上管理平台已成为当今社会经济发展的重要驱动力之一。本研究旨在设计并实现一个基于Java的餐饮库存追踪系统,在技术选择上,本项目采用了JAVA语言,MySQL数据库编程,使用springb…

2026/7/29 19:56:33 阅读更多 →

最新新闻

百度网盘直链解析:智能绕过限速的高效下载解决方案

百度网盘直链解析:智能绕过限速的高效下载解决方案

百度网盘直链解析:智能绕过限速的高效下载解决方案 【免费下载链接】baidu-wangpan-parse 获取百度网盘分享文件的下载地址 项目地址: https://gitcode.com/gh_mirrors/ba/baidu-wangpan-parse 还在为百度网盘下载速度限制而烦恼吗?百度网盘直链解…

2026/7/29 20:08:37 阅读更多 →
picocom实战指南:掌握Linux串口通信的6个专业技巧

picocom实战指南:掌握Linux串口通信的6个专业技巧

picocom实战指南:掌握Linux串口通信的6个专业技巧 【免费下载链接】picocom Minimal dumb-terminal emulation program 项目地址: https://gitcode.com/gh_mirrors/pi/picocom picocom是一款轻量级的Linux串口通信工具,专为嵌入式开发、物联网调试…

2026/7/29 20:08:37 阅读更多 →
猫抓插件终极指南:三步免费下载网页视频音频,开启资源获取新体验!

猫抓插件终极指南:三步免费下载网页视频音频,开启资源获取新体验!

猫抓插件终极指南:三步免费下载网页视频音频,开启资源获取新体验! 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch …

2026/7/29 20:08:37 阅读更多 →
告别U盘格式化噩梦:Ventoy如何用“魔法口袋“装下所有操作系统

告别U盘格式化噩梦:Ventoy如何用“魔法口袋“装下所有操作系统

告别U盘格式化噩梦:Ventoy如何用"魔法口袋"装下所有操作系统 【免费下载链接】Ventoy A new bootable USB solution. 项目地址: https://gitcode.com/GitHub_Trending/ve/Ventoy 你是否曾经为了测试不同操作系统而反复格式化U盘?是否因…

2026/7/29 20:08:37 阅读更多 →
taskt:零代码桌面自动化工具完全指南

taskt:零代码桌面自动化工具完全指南

taskt:零代码桌面自动化工具完全指南 【免费下载链接】taskt taskt (pronounced tasked and formely sharpRPA) is free and open-source robotic process automation (rpa) built in C# powered by the .NET Framework 项目地址: https://gitcode.com/gh_mirrors…

2026/7/29 20:08:37 阅读更多 →
Ark-Pets:将明日方舟干员变为你的智能桌面伙伴的终极指南

Ark-Pets:将明日方舟干员变为你的智能桌面伙伴的终极指南

Ark-Pets:将明日方舟干员变为你的智能桌面伙伴的终极指南 【免费下载链接】Ark-Pets Arknights Desktop Pets | 明日方舟桌宠 (ArkPets) 项目地址: https://gitcode.com/gh_mirrors/ar/Ark-Pets 你是否曾幻想过让《明日方舟》中那些可爱的干员走出游戏&#…

2026/7/29 20:07:37 阅读更多 →

日新闻

【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

一、本文介绍 🔥本文在RT-DETR多模态融合目标检测中引入RLAB残差线性注意力模块,可在不同模态特征交互阶段进行多次残差细化,使可见光、红外等特征在尺度、语义和空间位置上更好对齐;随后将细化特征与解码器输出拼接并生成Q、K、V,通过线性注意力自适应强化关键通道、目…

2026/7/29 0:00:23 阅读更多 →
AI编程系列02:合并知识功能,给 AI 问数和 RAG 场景打基础

AI编程系列02:合并知识功能,给 AI 问数和 RAG 场景打基础

AI编程系列02:合并知识功能,给 AI 问数和 RAG 场景打基础 在上一期「AI编程系列」中,我们学习了如何构建一个基础的 AI 问答系统,通过简单的输入输出让模型回应问题。但现实世界中的 AI 应用往往需要处理更复杂的场景:…

2026/7/29 0:00:23 阅读更多 →
AI智能体开发实战:从工具调用到企业级部署

AI智能体开发实战:从工具调用到企业级部署

1. 从被动问答到主动执行:AI Agent的范式转变过去两年,大语言模型最显著的应用形态是聊天机器人——用户提问,AI回答。但真正的生产力革命发生在2023年下半年:当AI学会主动调用工具完成任务时,生产力工具的历史被彻底改…

2026/7/29 0:00:23 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/28 12:04:22 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/29 14:34:28 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/29 15:00:03 阅读更多 →

月新闻