“隐私政策入口在中文版好好的。”这句话并不能证明一份准备上架的包在所有语言里都能把用户带到正确的页面。应用的首页可能有一个“隐私政策”按钮,关于页里也有一个,法语资源却还指向重构前的页面名。界面没有崩溃,文案没有乱码,自动截图也可能完全正常;点进去才发现是空白页或者回到了设置首页。这次不把问题归结为某条审核政策,也不假设我曾提交过真实审核。我要做的是一个本地提交前的工程检查:用 Node.js 读取项目维护的路由目录、语言入口映射和页面清单,逐个检查“某语言的隐私入口是否能解析为当前工程登记的页面”。它可以提高自查覆盖率,但并不代表通过华为 AppGallery 审核,也不能代替真机手动点击和外链合法性核查。Demo 叫 RouteProof,构造会话 RP-1818。支持三种语言:zh-CN、en-US、fr-FR。前两种语言的入口是 PrivacyHome,法语配置意外保留 PrivacyPolicyOld。结果应显示 已通过 2/3、失败 fr-FR、HOLD_ROUTE,按钮叫“重查入口”。这是本篇固定的演示数据,所有插图、脚本输出与说明都围绕这一组值,不使用真实审核帐号或内部发布资料。## 一、可达性检查不是把所有字符串搜一遍项目里的一个路由名称至少会出现在三个地方:UI 菜单动作、页面注册清单、多语言配置。字符串搜索可以告诉你旧名字还存在,却不能判断它出现在注释、埋点还是可点击入口。更麻烦的是,使用 Navigation 与 NavDestination 时,一个页面能否展示还取决于路由注册、目标内容以及运行时栈状态。当前脚本只检查静态登记的一致性,不假装覆盖全部动态导航行为。为了让检查成为 CI 的一部分,我会先定义自己的配置输入,而不是从构建产物里猜测业务路由。约定 routes.json 保存静态可访问页面名,privacy-entry.json 声明每种语言点击隐私入口对应的路由,release.json 声明要检查的语言列表。配置文件是团队自定义格式,并不是华为平台规定的上传字段或系统模块配置。必须把“语言存在”与“目标页面存在”拆开判断。只检查 fr-FR 在字符串资源里有“隐私政策”四个字毫无意义;真正要看它点击后路由名是否命中允许访问的页面。反过来,某个历史页面文件还留在磁盘上也不意味着它被当前导航图注册。检查采用明确列出的 routes 白名单,不遍历任意目录推断注册关系。## 二、构造一个必然失败的反例先把输入当作普通 JSON,检查函数只依赖显式参数。下例第一段是核心审核模型,不读取真实 AppGallery API,不发送网络请求。name、locale 都是业务字段,返回的 HOLD_ROUTE 也是本地自定义状态。javascript// tools/route-proof.mjs(核心函数)export function auditPrivacyEntries(config) { const routeNames = new Set(config.routes.map((route) => route.name)); const rows = config.locales.map((locale) => { const target = config.privacyByLocale[locale]; const ok = typeof target === 'string' && routeNames.has(target); return { locale, target: target ?? '(missing)', ok }; }); const passed = rows.filter((row) => row.ok).length; return { taskId: 'RP-1818', passed, total: rows.length, failedLocales: rows.filter((row) => !row.ok).map((row) => row.locale), state: passed === rows.length ? 'ROUTE_READY' : 'HOLD_ROUTE', rows };}````routeNames` 是唯一可信的静态注册清单来源。对 `undefined`、空字符串和未登记的旧名称,一律给出失败记录。这里没有模糊匹配、大小写自动修正,也不会因为语言只差一个地区后缀就自行回退到英文;默认回退可能掩盖产品需要明确提供的文案与入口。为了可复现,示例是三种语言、两个页面,真实工程应把测试数据与生产配置分开存放。紧接着构造固定输入:javascriptconst release = { locales: [‘zh-CN’, ‘en-US’, ‘fr-FR’], routes: [{ name: ‘PrivacyHome’ }, { name: ‘SettingsHome’ }], privacyByLocale: { ‘zh-CN’: ‘PrivacyHome’, ‘en-US’: ‘PrivacyHome’, ‘fr-FR’: ‘PrivacyPolicyOld’ }};const report = auditPrivacyEntries(release);console.log([RouteProof] ${report.taskId} passed=${report.passed}/${report.total});console.log([RouteProof] ${report.taskId} failed=${report.failedLocales.join(',')});console.log([RouteProof] ${report.taskId} state=${report.state});// 设计期预期输出:// [RouteProof] RP-1818 passed=2/3// [RouteProof] RP-1818 failed=fr-FR// [RouteProof] RP-1818 state=HOLD_ROUTE这三行是运行此纯函数可推导的**样例输出**,不是我在某台设备的 HiLog 中读取的真实日志。结果解释也有边界:`2/3` 表示静态配置命中,不代表中文版和英文版用户真的完成了全部隐私信息阅读;`HOLD_ROUTE` 表示工具要阻止团队自己的发布工作流继续执行,不是官方平台给包的审核状态。真正提交前还要对页面内容、文案与外链进行人工复核。## 三、工具要能拒绝发布,而不仅发一句提醒要把这件事变成可复用的脚本,关键不是做漂亮报表,而是让命令行退出码可靠。`process.exitCode=1` 用于向 CI 表示本轮检查未通过;不要直接调用 `process.exit()`,以免流里的输出还没写完就结束进程。下面代码展示文件读取、类型校验以及失败时的非零退出码。Node.js 的 `readFile` 属于标准库,项目不用额外安装打包工具。javascript// tools/check-release.mjsimport { readFile } from ‘node:fs/promises’;import { auditPrivacyEntries } from ‘./route-proof.mjs’;try { const raw = await readFile(‘config/release-routes.json’, ‘utf8’); const parsed = JSON.parse(raw); if (!Array.isArray(parsed.locales) || !Array.isArray(parsed.routes) || typeof parsed.privacyByLocale !== ‘object’ || parsed.privacyByLocale === null) { throw new Error(‘invalid release route shape’); } const report = auditPrivacyEntries(parsed); console.log([RouteProof] ${report.taskId} passed=${report.passed}/${report.total}); console.log([RouteProof] ${report.taskId} failed=${report.failedLocales.join(',')}); console.log([RouteProof] ${report.taskId} state=${report.state}); if (report.state !== ‘ROUTE_READY’) process.exitCode = 1;} catch (error) { console.error(‘[RouteProof] configuration_error’, error); process.exitCode = 1;}```脚本不应该尝试替产品自动修正法语入口。换成英文页面也许能够打开,却可能违背本地化要求,甚至把用户引到另一版政策文本。工具提供的是失败的精确位置:fr-FR → PrivacyPolicyOld,由研发或本地化维护者决定应改映射、补页面还是恢复正确的路由注册。输出需保持脱敏。隐私政策链接有时包含环境参数、授权临时令牌或者预发布域名,这些都不适合直接复制到 CI 公共日志。此处只打印语言代码和路由名,不打印任何用户数据、密码或查询参数。若真的检查 Web URL,可另做 https、域名白名单和重定向链路检查,但那属于另一个测试层,不应说静态路由审计已经完成了网页可达性验证。## 四、从配置缺口反推界面结果RouteProof 的演示页面模拟一个发布前检查面板。头部是“RouteProof · 审核入口自检”,任务 RP-1818;主卡片显示“语言 3 项、通过 2/3、失败 fr-FR”,状态为 HOLD_ROUTE。列表依次显示 zh-CN READY、en-US READY、fr-FR MISSING_ROUTE。底部按钮“重查入口”,用于提醒开发人员重新执行校验。这个 Demo 没有接入真实的 AppGallery Connect 审核接口,图片不能作为提交状态截图。测试设计至少要覆盖六种情况:映射为空、名称拼错、页面注册缺失、重复语言配置、配置 JSON 不合法、页面存在但运行中因权限或登录导致不可达。脚本能识别前三种静态不一致和非法结构;对于重复 key,普通 JSON.parse 会覆盖后者,必须增加独立的输入规范检查;运行时登录和权限问题则需要应用端自动化或手动回归。只要把这些范围说明白,就不会误把一个小检查器宣传成审核自动化平台。当前示意中的 HOLD_ROUTE 不是业务故障恢复完成状态,而是有意阻断构建链路。因此不要显示进度 100%,也不应在红色异常卡之外摆一个绿色“审核已通过”标签。若最后修正 fr-FR 为 PrivacyHome,纯函数会预期返回 ROUTE_READY、3/3;但这也只是本地规则通过,提交人员仍然要确认页面文案与最新平台要求。## 五、交付结论与技术边界我更愿意把这件事理解为“让一个本来很晚才出现的入口错误,提前暴露给可定位的责任人”。多语言资源检查关注的是文字,多页面导航检查关注的是目标,发布检查关注的是是否有未修复的风险;这三者相互补充,不能只看其一。团队最好将本地检查结果随构建编号归档,避免不同批次引用不同配置,却没有人能解释差异来源。落地时可把 node tools/check-release.mjs 放到持续集成的发布前步骤,配置统一使用仓库中版本控制的文件,脚本发现问题就返回非零退出码。实际手机回归应在 zh-CN、en-US、fr-FR 三种环境逐个点击入口,并核查是否出现正确内容、滚动阅读是否正常、返回栈是否符合预期。静态通过后仍需人工检查真实隐私声明、权限用途与三方 SDK 信息。文章的页面与日志都是生成演示,并未进行 DevEco 编译、真机审核、外链探测或实际 AppGallery 提审。严格而言,本文能够证明的是“静态规则如何表达、如何给出阻断结果”,而不是“该应用必然符合所有上架条件”。采用这套小工具的价值,恰恰在于它承认自己不知道哪些东西,并将未覆盖部分交还给正确的测试流程。官方/一手资料:- HarmonyOS NavDestination 子页面与生命周期- AppGallery Review Guidelines 概览- Node.js fs/promises readFile- Node.js process.exitCode

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐