AppGallery Connect×Node.js:JSON5版本号与权限声明提交前闸门【鸿蒙心迹】
临近提交应用版本,开发团队经常会在“功能已经改完”和“配置真的准备好了”之间反复确认。应用能编译,不代表版本号比上次高;工程里声明了一个权限,也不等于大家都记得它是什么时候加进去的。等到上传产物以后再核对这些细节,处理成本往往比提前查一遍更高。这次以一个虚构的 MuseBoard 内容应用为对象,设计名为 ReleaseGate 的本地检查台。它只读取源码配置,提供提交前的版本与权限差异提示;不会调用 AppGallery Connect 的审核接口,更不能模拟官方审核结果。本文的界面和检查报告属于可复现的样例数据,不是某个真实应用的上架经历。
## 一、把“本地自检”和“平台审核”划一条线先列出本轮准备检查的样例:应用包名 com.demo.museboard,目标产品配置 default,准备发布的版本是 2.3.1,版本码 20301;团队记录的上一版本码为 20300。权限白名单只有 ohos.permission.INTERNET,而当前模块声明里还存在 ohos.permission.CAMERA。它不一定是非法权限,但属于需要业务方解释的增量。ReleaseGate 给这份样例报告分配 RG-1024,预期状态是“需人工确认”,具体计数为 5 项通过、1 项提醒、0 项阻断。计数是本文预设的规则结果,不能理解为平台返回的评分。尤其是权限用途、隐私说明、截图内容等审核事项,静态脚本没有语义判断能力。选择只检查少量高价值字段,还有一个工程原因:检查越贪心,越容易把本地约定说成官方门槛。真正需要阻断构建的,应是字段缺失、版本码倒退或配置解析失败这类团队内部可以明确证明的问题;不认识的新权限,先给提醒,再交给人确认实际使用场景与申请规范。## 二、别把 JSON5 当作普通 JSON 解析HarmonyOS 工程常见 AppScope/app.json5、entry/src/main/module.json5 以及工程级 build-profile.json5。这些配置允许 JSON5 语法,如果直接用 JSON.parse,一行注释或尾逗号就可能让脚本报错。Node.js 标准库负责文件读取,第三方 json5 只负责语法解析,职责比较清楚。下面的代码解决“稳定读取三份配置”的问题。脚本命名为 tools/release-guard.mjs,在工程根目录执行;示例需要先安装 json5 依赖。读取失败时抛出错误,交给外层命令行任务处理,不能静默跳过缺失文件。javascriptimport { readFileSync } from 'node:fs'import { resolve } from 'node:path'import JSON5 from 'json5'const readConfig = (file) => JSON5.parse(readFileSync(resolve(process.cwd(), file), 'utf8'))const app = readConfig('AppScope/app.json5').appconst build = readConfig('build-profile.json5')const moduleConfig = readConfig('entry/src/main/module.json5').module在正式流水线中,依赖版本应进入锁文件,避免不同构建机解析行为不一致。readConfig 的作用很窄:它不会分析打包产物,更不会自动读取某个上传到 AGC 的包。路径以工作目录为基准,因此 CI 启动目录一旦改变,就可能读错工程;推荐构建脚本显式指定工程根路径。## 三、版本有效值可能被产品配置覆盖比较版本号之前还要知道到底比较哪一个。华为在工程级 build-profile.json5 的文档里说明,products 中可以配置 versionCode 和 versionName,构建时相关字段可覆盖 app.json5 的声明。这意味着只打开 AppScope/app.json5 看数字,可能不是目标产品的实际构建值。下面的代码固定检查名为 default 的目标产品,优先读取该产品覆盖字段,若未设置则回退到 app.json5。这里的基线 20300 来源于团队自己保存的上次发布记录,不假装它来自官方接口。javascriptconst productName = 'default'const product = (build.app?.products ?? []) .find((item) => item.name === productName)const versionCode = product?.versionCode ?? app.versionCodeconst versionName = product?.versionName ?? app.versionNameconst previousVersionCode = 20300const versionErrors = []if (!Number.isInteger(versionCode) || versionCode <= previousVersionCode) { versionErrors.push('versionCode 未高于团队记录的上一版本')}if (typeof versionName !== 'string' || versionName.trim() === '') { versionErrors.push('versionName 为空')}这里特意使用空值合并运算符 ??,而不是 ||:前者只在字段缺失时回退,遇到非法的零值仍会交给校验逻辑识别。previousVersionCode 不是任何 SDK 常量,应来自可信版本台账。不同 product 可能对应不同渠道或构建变体,所以流水线不能默认拿一个版本码替所有产物签字。还有一个不能忽略的细节:本文检查的是源码配置的推导值。真正签名或打包后的产物内容仍应单独核对,特别是在多产品和自动化参数替换场景里,不能因为源码检查通过就认定最终 HAP/APP 已经完全一致。
## 四、权限检查应该给出“新增了什么”权限不是越少越好,也不能因为出现 CAMERA 就直接判定不合规。我们真正需要的是把“本次声明”与“团队确认过的清单”做一次差异比较。示例中白名单只有 INTERNET,CAMERA 因此进入提醒项。后续业务负责人可以确认相机入口、隐私声明以及权限申请时机是否匹配,再调整清单。下面实现最简单的差异检测。module.json5 的字段位置按照 module.requestPermissions 读取,并取出其中的 name。无效结构和重复项都值得另外记录;示例先聚焦新增权限,避免把特定权限的运行时授予规则硬编码成未经核实的平台审核政策。javascriptconst accepted = new Set(['ohos.permission.INTERNET'])const permissions = moduleConfig.requestPermissions ?? []const names = permissions.map((item) => item.name)const warnings = names .filter((name) => typeof name === 'string' && !accepted.has(name)) .map((name) => `新增权限需人工确认:${name}`)const report = { reportId: 'RG-1024', bundleName: app.bundleName, versionName, versionCode, passed: 5, warning: warnings.length, blocked: versionErrors.length, status: versionErrors.length > 0 ? '禁止提交' : (warnings.length > 0 ? '需人工确认' : '检查通过')}console.info('[ReleaseGate] report=' + report.reportId)console.info('[ReleaseGate] status=' + report.status)````passed: 5` 是当前演示规则集的人工设定值,并非以上三段代码自动计算出的全部通过项。将这个数字用于真实流水线前,必须把每项检查抽象为结构化结果并统计;否则新增规则以后计数会失真。当前样例的 warnings 长度为 1、versionErrors 长度为 0,因此报告为“需人工确认”。如果 `app.bundleName` 不符合项目登记值,也应该进入阻断规则,但本节并未冒充已实现那项校验。## 五、把报告呈现得像工程信息,而不是审核结论为方便移动端评审,可以额外做一个 ArkUI 报告查看页面。它展示静态检查的快照,而不负责运行 Node.js 脚本。手机不能直接把 PC 上的 `release-guard.mjs` 当成 ArkTS 运行;真实产品需要通过文件导入、服务端接口或其他可信通道传送报告 JSON。下面只给出最小的静态页面状态与展示片段。arktsinterface GateSummary { reportId: string passed: number warning: number blocked: number status: string}@Entry@Componentstruct ReleaseReportPage { @State info: GateSummary = { reportId: ‘RG-1024’, passed: 5, warning: 1, blocked: 0, status: ‘需人工确认’ } build() { Column({ space: 12 }) { Text(‘发布检查台’).fontSize(26).fontWeight(FontWeight.Bold) Text(this.info.reportId) Text(this.info.status).fontColor(‘#B76A0A’) Text(通过 ${this.info.passed} / 提醒 ${this.info.warning} / 阻断 ${this.info.blocked}) }.width(‘100%’).padding(24) }}````GateSummary负责报告的显示,不含“批准提交”的动作。真实项目中,如果要让审核负责人在手机上看到这份检查结果,应该同时显示规则版本、生成时间、被检查的产物标识,并提供原始文件追溯路径;当前只是界面设计,不具备远程同步和平台审核能力。图 03 的演示手机状态栏设为10:42,顶部显示 RG-1024,版本 2.3.1 / 20301,结果“需人工确认”,下方突出 ohos.permission.CAMERA新增权限提醒。它把“通过 5 / 提醒 1 / 阻断 0”放在同一视图里,便于同伴快速定位还没解释清楚的配置变化。## 六、调试与落地检查比一条绿色提示更重要本地自检首先要做反例:将版本码暂时改回20300,应得到版本未前进的阻断;把 AppScope/app.json5改成无法解析的 JSON5,应中止脚本而不是继续生成正常报告;再把 CAMERA 从声明中移除,新增权限提醒应消失。注意这些是建议执行的测试步骤,不是本文已经实际运行的记录。接着核对build-profile.json5的产品覆盖:目标default如果设置了versionCode,检查报告必须采用覆盖值;若存在多个产品,应分别运行或显式传入产品名。构建工具版本和工程结构后续变化时,先验证数据结构再升级脚本,不要仅凭旧博客的字段示例复制到生产。对于权限差异,建议在代码评审中把“新增权限名、使用的页面、触发时机、授权方式、用户告知文案”放在一起。脚本负责发现差异,工程负责人负责判断用途;涉及 user_grant的声明字段和动态授权方式,应回到对应能力文档逐项核对。单纯出现requestPermissions 并不能证明系统已经向用户授予该权限。最后是产物与证据链。ReleaseGate 可以把版本、目标 product、权限清单和检查时间写进 JSON,流水线再为实际构建产物建立摘要,供后续追溯;但是这一步在本文中属于扩展设计,未给出“已完成签名产物哈希核验”的承诺。只有把构建后检查、功能回归、隐私材料、截图与平台真实审核流程连起来,团队才有可信的发布闭环。这个工具的价值不在于制造一个“自动审核通过”的绿色勾,而在于把可证明的配置错误挡在提交动作之前,并把需要解释的权限差异清楚地摆出来。Node.js + JSON5` 可以承担配置检查,最终上架判断仍属于平台流程与人工责任。### 资料依据- 华为开发者文档《工程级 build-profile.json5 文件》:https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/ide-hmos-hvigor-build-profile-app- 华为开发者文档《端侧应用配置》:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-v5/payment-config-app-identity-info-V5- 华为开发者文档《开发准备:权限声明示例》:https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/development-preparation- JSON5 项目说明:https://github.com/json5/json5> 声明:报告编号、版本、权限差异与手机界面均为示例,并未调用 AppGallery Connect 实际提交或审核接口。
更多推荐

所有评论(0)