HarmonyOS 7 + Node.js-JSON Schema:审核测试路径与版本事实的一致性门禁【鸿蒙心迹】

提交审核前,团队通常会检查 HAP、截图、隐私声明和权限配置,却容易把"审核说明"当成一段最后手写的文字。版本号已经升到 3.6.0,说明里还写着 3.5.2;功能入口从首页移到发票中心,步骤仍让审核人员点击旧按钮;测试账号已经轮换,文档里却保留过期别名。每一项都不复杂,组合起来却会让可用功能表现得像无法访问。

本文构造 ReviewPathGate 小工具,把审核说明改造成可验证的事实清单。任务为 REVIEW-PATH-0069,应用包名 com.example.ledger,版本 3.6.0 (30600),目标功能 invoice_import,入口 /pages/InvoiceImportPage,测试账号别名 reviewer_primary,操作步骤 4 条。演示从 4 个发现项收敛到 0,状态为 MANIFEST_LOADED → FACTS_COLLECTED → ROUTES_PROBED → MATERIAL_MATCHED → PASS。所有账号和数据均为虚构示例,不包含真实凭据,也不宣称经历过真实审核失败。

一、审核说明最大的问题是无法被程序理解

一段自然语言可能写得很清楚,但构建系统不知道里面的版本号是否过期,也不知道"点击发票导入"在当前页面是否真的存在。只要发布节奏紧,审核说明就会从上一版本复制过来,再由不同同事分别修改截图、账号和步骤。最终每个文件看起来都合理,组合后却描述了三套应用。

ReviewPathGate 不尝试自动代写审核材料,也不判断内容合规。它只验证团队能够确定的工程事实:候选包的 bundleName、versionName、versionCode;运行时导出的可审核路由;功能标识与步骤数量;测试账号是否使用已登记别名;截图清单是否指向同一版本。至于审核规则、资质和内容判断,仍以当前官方要求和人工复核为准。

这种边界很重要。脚本通过不等于应用必然通过审核;脚本失败却意味着材料内部已经自相矛盾,不适合继续提交。门禁的价值是减少低级事实错误,不是替代审核人员。

二、把审核路径变成一份机器可读清单

工具的入口是 review-manifest.json。它不保存密码,只保存凭据别名;真正的测试密码放在受控密钥系统中,由提交人员按流程填写。清单也不包含用户真实数据,示例数据集使用固定虚构发票号。

清单字段分四类:应用事实、功能入口、测试身份、材料引用。每个 feature 都有稳定 ID,页面改名时更新 route,步骤只引用 actionId。图片文件不写绝对路径,避免换机器后失效。版本字段必须同时写 name 和 code,不能只凭展示字符串判断新旧。

这段代码解决什么问题:把分散在聊天和文档里的审核路径收敛为结构化清单,为后续自动比对提供唯一输入。

json 复制代码
{
  "$schema": "./review-manifest.schema.json",
  "taskId": "REVIEW-PATH-0069",
  "bundleName": "com.example.ledger",
  "versionName": "3.6.0",
  "versionCode": 30600,
  "accountAlias": "reviewer_primary",
  "features": [{
    "id": "invoice_import",
    "route": "/pages/InvoiceImportPage",
    "steps": [
      "open_invoice_center",
      "tap_import",
      "select_demo_file",
      "confirm_preview"
    ],
    "screenshots": ["invoice_import_01.png", "invoice_import_02.png"]
  }]
}

清单读入后状态进入 MANIFEST_LOADED。这里最容易犯的错误是把密码也写进 JSON 并提交仓库。别名只表示"发布流程中需要哪一组测试身份",不提供秘密本身。CI 只能检查别名是否在允许列表中,不能打印密钥值,更不能把凭据打进 HAP。

JSON Schema 负责类型、必填字段、枚举和格式校验。例如 versionCode 必须是正整数,route 必须以 /pages/ 开头,steps 至少一条且不重复。Schema 只能证明结构合法,不能证明 route 存在,因此后面还要采集工程事实。

三、路由事实应由应用导出,而不是脚本扫描源码猜测

直接用正则搜索 .ets 文件不可靠。页面可能通过常量注册,路由也可能由模块组合;注释和测试样例还会产生假阳性。ReviewPathGate 在应用侧维护一份审核功能注册表,运行页面和构建脚本都消费同一份静态数据。

注册表不等于导航实现。它只暴露审核所需的 route、入口动作和最小前置条件,不包含页面实例或 UIContext。这样 Node.js 脚本可以读取导出的 JSON,ArkTS 页面也能在内部诊断页展示同一结果。

这段代码解决什么问题:建立由应用代码维护的审核功能注册表,避免构建脚本通过脆弱的源码正则推断页面是否存在。

ts 复制代码
export interface ReviewableFeature {
  id: string
  route: string
  actions: string[]
  requiresAccount: boolean
}

export const REVIEWABLE_FEATURES: ReviewableFeature[] = [{
  id: 'invoice_import',
  route: '/pages/InvoiceImportPage',
  actions: [
    'open_invoice_center',
    'tap_import',
    'select_demo_file',
    'confirm_preview'
  ],
  requiresAccount: true
}]

export function findReviewFeature(id: string): ReviewableFeature | undefined {
  return REVIEWABLE_FEATURES.find(item => item.id === id)
}

注册表必须与真实导航入口放在同一模块或由同一源文件生成,不能再维护一份永远落后的副本。状态 FACTS_COLLECTED 表示候选包版本、注册表和材料索引已经收集,不表示页面在运行时一定可达。

实际项目可以在测试构建中提供只读诊断页,按 featureId 执行导航 smoke test;正式包不需要暴露内部列表。页面生命周期结束后要清理测试订阅和临时文件,不能为了审核工具在生产环境常驻调试服务。

四、版本事实要从候选产物链路采集

最危险的做法是脚本读取开发者手填的另一个 version.json。如果它和实际构建配置不同,只会让两个错误文件互相证明。ReviewPathGate 要求 CI 在候选构建完成后生成 candidate-facts.json,内容来自当前工程配置和产物检查步骤;审核清单只与这份事实文件比较。

脚本本身不硬编码某个 DevEco Studio 目录结构。不同工具链版本的输出路径可能变化,CI 应把事实文件路径作为参数传入。若找不到候选事实,结果是 FACTS_MISSING 并阻断,而不是回退到 Git 分支名推断版本。

这段代码解决什么问题:逐项比较审核清单与候选包事实、路由注册表和账号别名,输出稳定的发现项而不是模糊日志。

ts 复制代码
type Finding = { code: string; field: string; expected: string; actual: string }

function audit(manifest: ReviewManifest, facts: CandidateFacts): Finding[] {
  const out: Finding[] = []
  compare(out, 'BUNDLE_MISMATCH', 'bundleName', manifest.bundleName, facts.bundleName)
  compare(out, 'VERSION_NAME_MISMATCH', 'versionName', manifest.versionName, facts.versionName)
  compare(out, 'VERSION_CODE_MISMATCH', 'versionCode', `${manifest.versionCode}`, `${facts.versionCode}`)

  if (!facts.allowedAccountAliases.includes(manifest.accountAlias)) {
    out.push({ code: 'ACCOUNT_ALIAS_UNKNOWN', field: 'accountAlias',
      expected: 'registered alias', actual: manifest.accountAlias })
  }

  for (const feature of manifest.features) {
    const registered = facts.features.find(item => item.id === feature.id)
    if (!registered || registered.route !== feature.route) {
      out.push({ code: 'ROUTE_NOT_REGISTERED', field: feature.id,
        expected: feature.route, actual: registered?.route ?? 'missing' })
    }
  }
  return out
}

演示初始发现项为 4:说明版本仍是 3.5.2、versionCode 30502、route 指向旧页面、账号别名未登记。修复后清单和候选事实都变成 3.6.0 (30600)、/pages/InvoiceImportPage 与 reviewer_primary,发现项归零。

比较函数只输出别名和字段,不输出密码。即使 CI 日志被下载,也不应该包含可登录信息。实际项目还应对报告访问范围和保留周期做限制。

五、可达性不是字符串相等,需要最小 smoke test

route 在注册表中存在,不代表功能真的能走通。页面可能依赖登录、远端开关、初始化数据或权限。ReviewPathGate 把四个步骤映射为可观察动作,在测试环境使用虚构账号和固定演示文件执行最小路径:进入发票中心、点击导入、选择演示文件、确认预览。

这里不做像素级自动点击,也不声称替代完整 UI 测试。应用在调试构建中为每个 actionId 暴露可查询状态,测试驱动真实业务方法,并等待稳定结果。任何步骤超时都记录在对应 action 上,页面退出后取消等待器,防止旧回调污染下一次测试。

这段代码解决什么问题:用 generation 隔离一次审核路径探测,确保旧步骤回调不会把新一轮结果误标为通过。

ts 复制代码
class ReviewProbe {
  private generation = 0
  private state: 'IDLE' | 'RUNNING' | 'PASS' | 'FAILED' = 'IDLE'

  async run(feature: ReviewableFeature): Promise<void> {
    const current = ++this.generation
    this.state = 'RUNNING'
    try {
      for (const action of feature.actions) {
        await probeAction(action, 3000)
        if (current !== this.generation) return
      }
      this.state = 'PASS'
    } catch (error) {
      if (current === this.generation) this.state = 'FAILED'
    }
  }

  cancel(): void {
    this.generation++
    this.state = 'IDLE'
  }
}

状态在四步完成后进入 ROUTES_PROBED。cancel() 与页面离开成对调用,generation 让晚到 Promise 失去提交权。实际项目的 probeAction 应走可测试的业务接口,不要通过全局单例直接修改 UI 状态;涉及网络时固定测试租户和数据集,并明确失败是环境不可用还是功能错误。

上图是 DevEco Studio 风格的演示配图,不是实际 IDE 截图或审核证据。左侧显示 manifest、schema、route registry 和审计器;中间是版本、路由比对逻辑;右侧模拟器显示 REVIEW-PATH-0069;底部 HiLog 记录 findings 4→0 与 exitCode 2→0。

六、材料一致性检查要抓"引用关系"

截图文件存在还不够。ReviewPathGate 为每张截图维护 sidecar 元数据,记录 featureId、versionName、locale、deviceClass 和 captureSet。脚本验证截图是否属于 invoice_import、是否来自 3.6.0、是否覆盖清单列出的页面。它不分析图片内容,也不把生成图当成真实运行证据。

审核说明中的按钮名称可能本地化,actionId 则保持稳定。中文材料可以写"导入发票",英文材料写"Import invoice",两者都引用 tap_import。这样文案调整不会导致路由脚本误判,同时本地化材料仍能逐项复核。

演示将材料事实收敛为 12 项:3 个应用身份字段、1 个账号别名、1 个功能、1 条路由、4 个步骤、2 张截图,最终覆盖 12/12。这个数字只表达清单覆盖率,不代表审核通过概率。

七、运行页展示的是"提交准备度"

ReviewPathGate 页面标题为"审核路径门禁"。它显示任务 REVIEW-PATH-0069、bundleName、版本、功能 ID、route、账号别名和步骤数。当前状态 MATERIAL_MATCHED,进度 92%,表示结构、候选事实、路由与材料已完成匹配,等待最终报告签名。

红色标注只指向 /pages/InvoiceImportPage 和 12/12,让读者看到门禁依据。页面不会显示密码,也不会提供"一键登录真实审核账号"。按钮"生成提交报告"只导出脱敏 JSON 和摘要,凭据仍由人工在受控流程中提供。

09:21、Wi-Fi、5G、71% 电量属于演示视觉口径。实际审核材料的设备与时间应来自真实采集记录,不能用这张生成图替代商店截图或审核证据。

八、诊断页必须保留修复前后的差异

如果工具最终只显示绿色 PASS,团队无法知道门禁解决了什么。诊断页保留初始 4 项:VERSION_NAME_MISMATCH、VERSION_CODE_MISMATCH、ROUTE_NOT_REGISTERED、ACCOUNT_ALIAS_UNKNOWN;修复后同一任务 findings 变为 0,exitCode 从 2 变为 0。

状态链完整显示 MANIFEST_LOADED → FACTS_COLLECTED → ROUTES_PROBED → MATERIAL_MATCHED → PASS。红圈标出 4 → 0 和 exitCode 2 → 0。这比"审核材料已检查"更有可追溯性,也方便定位哪次版本变更引入了路径漂移。

报告还记录候选事实摘要 c8e4,但不记录文件绝对路径和凭据。摘要用于确认页面看到的报告与 CI 产物属于同一轮,不承担密码学签名或供应链证明;若需要更高保证,应使用组织现有的制品签名方案。

九、门禁如何接入发布流程

最简单的接入点是在候选 HAP 构建后、上传前执行 Node.js 审计脚本。命令读取 review-manifest.json、schema、candidate-facts 与截图 sidecar,生成 review-audit.json。发现项大于零返回 exitCode 2;环境或输入缺失使用另一个退出码,避免把工具故障误判为材料错误。

脚本不应自动修改清单。自动把旧版本号替换成新版本看起来省事,却可能掩盖说明文本和截图仍属于旧功能。门禁只报告差异,由负责人判断更新材料还是回退候选包。修复后重新运行,新的报告覆盖当前任务的临时输出,但历史报告作为发布记录保留。

CI 中还应固定 Node.js 与依赖锁文件,JSON Schema 校验器升级时运行样例库。审核 FAQ 和平台字段可能变化,规则配置要记录更新时间;遇到无法验证的政策项,只在报告里标记 MANUAL_REVIEW_REQUIRED,不能凭脚本猜测合规结论。

十、把"可审核"当成一个工程接口

ReviewPathGate 最终解决的不是审核平台本身,而是团队内部材料与候选包之间的事实漂移。结构化 manifest 说明要测什么,应用注册表说明入口在哪里,candidate-facts 说明提交的究竟是哪一版,smoke test 说明四步能否执行,sidecar 说明截图属于哪个功能和版本。

演示从版本 3.5.2/30502、旧 route 和未知账号别名出发,得到 4 个发现项;修复到 3.6.0 (30600)、/pages/InvoiceImportPage 和 reviewer_primary 后,12/12 事实匹配,状态进入 PASS。它是工程示例,不代表真实商店审核结果。

提交前的最小复核包括:manifest 不含密码;版本事实来自候选产物链路;路由注册表与真实导航共享来源;步骤使用稳定 actionId;smoke test 的监听和超时能够释放;截图 sidecar 与版本一致;失败报告不被自动吞掉;官方审核要求仍由人工按当前日期复核。

当审核说明成为可验证接口,团队就不必在每次发布前重新"相信某份文档应该没问题"。脚本给出可复现事实,人工负责政策判断和最终材料质量,两者分工比一份万能检查表更可靠。

1. Schema 要验证结构,也要拒绝多余字段

审核清单属于发布输入,拼错字段时不应被静默忽略。JSON Schema 可以对顶层和 feature 对象设置 additionalProperties: false,将 versionCodee 这样的笔误直接变成结构错误。步骤数组设置最小项数和 uniqueItems,route 使用受控 pattern,accountAlias 限制为普通标识符,避免有人误把邮箱或密码贴进去。

Schema 本身也要有版本。清单新增 captureSet 等字段时,提高 schemaVersion,并为旧清单提供显式迁移脚本。迁移结果必须经过人工 diff,不能在 CI 中悄悄改写源文件。结构变更和业务事实变更分开提交,审核更容易。

2. 测试账号管理必须与报告彻底分离

reviewer_primary 只是一把查找钥匙。CI 校验允许列表时只知道它存在、用途为审核、尚未过期,不读取密码。真正提交材料时,由有权限的人从密钥系统获取或在平台安全字段中填写。报告不能回显账号、密码、验证码种子或登录 token。

账号轮换时先登记新别名并完成 smoke test,再更新 manifest,最后撤销旧账号。若先删除旧身份,当前候选的路径测试会失去基线;若长期保留多个别名,又会扩大暴露面。门禁可以检查 expiry 和 owner 字段是否存在,但凭据是否满足组织安全策略仍需人工确认。

3. 环境不可用与功能失败要使用不同结论

测试租户维护、演示文件缺失、远端服务超时都可能让路径探测失败。工具应区分 ENVIRONMENT_BLOCKED 与 FEATURE_FAILED:前者说明当前无法形成审核结论,后者说明已到达应用但业务动作不符合预期。两者都阻断上传,却需要不同负责人处理。

不要在环境失败时沿用上一轮 PASS。每份报告绑定 taskId、候选摘要和生成时间,旧结果不能证明新包可达。若必须离线提交,清单应明确哪些步骤只能人工验证,并把状态设为 MANUAL_REVIEW_REQUIRED,而不是绿色 PASS。

4. 路由注册表需要明确所有者

功能开发者在新增或迁移审核入口时负责更新 registry,发布负责人只维护 manifest。这样 route 事实来自代码所有者,材料意图来自发布流程。若两者都由发布人员临时修改,脚本可能让错误路径"自洽",却仍无法运行。

代码评审中可以要求路由变化同时更新对应 feature 测试。删除页面前先检查是否仍被 manifest 引用;发现引用时,CI 给出 ROUTE_NOT_REGISTERED 和 featureId,而不是等提交当天才由人工发现。注册表只包含稳定业务入口,不应把临时调试页纳入审核路径。

5. 本地化材料要共享 actionId,而不是共享文案

按钮文案会随语言和产品迭代变化,actionId 才是稳定关联键。每个 locale 的说明文件将 tap_import 映射为本地化文本,脚本检查四个 action 是否都有文案和截图引用。中文缺一条、英文多一条都会形成发现项,但不会要求不同语言显示完全相同的字数或布局。

截图 sidecar 还应记录语言和设备类别,避免把中文手机图误用于英文平板材料。图片内容仍需人工打开检查,脚本只验证元数据与引用关系。生成式演示图可以用于技术文章说明,不能冒充真实应用审核截图;本文四张配图正是演示素材,不进入商店提交集合。

6. 报告需要稳定、可比较、可归档

review-audit.json 中每个 finding 使用固定 code、field、expected 和 actual,排序也保持稳定。这样两次报告可以直接 diff,不会因为对象遍历顺序变化产生噪声。报告摘要 c8e4 来自规范化后的非敏感事实,任何输入变化都会生成新摘要。

归档时把 manifest、candidate-facts、schema 版本、报告和截图 sidecar 放进同一发布记录,但不包含凭据。若审核过程中平台要求补充说明,新的材料创建新的 report generation,不覆盖原始记录。这样团队能回答"哪一版包、哪一份路径、哪次修复产生了当前提交",而不是只剩一张绿色截图。

7. 上线前做一次人工反向走查

自动门禁通过后,安排未参与功能开发的人按审核说明从头走一遍。操作者只能看到提交材料,不能依赖内部口头知识。若他需要询问"发票中心在哪里"或"演示文件放哪",说明清单结构正确但文字仍不够可执行。

人工走查还应验证退出、重试和空状态。审核人员可能输入错误一次、返回上一页或遇到网络抖动,应用不能因此进入无法恢复的页面。工具负责事实一致,最终体验仍要由人观察;二者一起完成,才是真正的提交准备度。

走查结果也应回写为报告中的人工签名项,只记录操作者、时间和结论,不记录测试密码。这样自动检查与人工判断共享同一批次编号,又不会混淆各自责任。

参考资料:

相关推荐
传奇开心果编程1 小时前
【ArkUI提高练中学】第7课:性能调优与稳定性治理
学习·ui·华为·harmonyos
liangshanbo12151 小时前
Monorepo 工程化面试题:如何设计 Web + Node.js 中间层 + Shared + Scripts 的 Monorepo?
前端·node.js
李游Leo14 小时前
HarmonyOS 7 + Spatial Recon Kit-Core File Kit:3DGS 重建产物的原子发布与中断恢复【鸿蒙心迹】
3d·华为·harmonyos
HwJack2015 小时前
【共创稿事节】HarmonyOS 7空间排布原则:视锥、舒适区与可达性
microsoft·华为·harmonyos
李游Leo16 小时前
HarmonyOS 7 Core Vision Kit + Image Kit:超分批处理的 PixelMap 预算、失败降级与结果原子替换【鸿蒙心迹】
harmonyos
李游Leo16 小时前
HarmonyOS 7 AbilityAccessCtrl + ArkUI:权限弹窗重入治理与提审证据链【鸿蒙心迹】
华为·harmonyos
李游Leo17 小时前
HarmonyOS 7 Spatial Recon Kit + Preferences:重建会话中断恢复与脏任务回收【鸿蒙心迹】
华为·harmonyos
李游Leo18 小时前
《HarmonyOS 7 ArkGraphics 3D 空间设计开发实战》03:glTF模型加载、实例复用与资源生命周期【鸿蒙心迹】
3d·harmonyos
李游Leo18 小时前
HarmonyOS 7 + ArkTS-HUKS:精准碰一碰载荷的签名校验与重放窗口【鸿蒙心迹】
华为·harmonyos