AppGallery Connect×module.json5:权限声明与提审材料差异门禁【鸿蒙心迹】
应用准备提交审核时最难处理的往往不是“有没有填这一栏”而是不同位置说的是不是同一件事。代码里有相机入口模块配置里声明了相机权限资源文件写了申请理由应用详情页还有隐私说明。它们分别由开发、产品与运营维护彼此并不会因为一次构建成功就自动保持一致。这次设计的ReviewLedger并不申请真正的市场审核也不会给项目打一个虚假的“可上架”结论。它做的是提交前的本地静态巡检在 Demo 工程里核对module.json5的权限项、字符串资源引用是否存在并列出需要人工确认的隐私文本。任务号固定为AG-206示例汇总为静态通过 3 项、人工复核 1 项。这个数字是演示清单的输出不是审核平台返回码。## 一、从一张分散的表开始收口我会先把四个检查点写在一张小表里不让它们藏在对话截图和临时备注里。第一项是相机权限理由第二项是麦克风权限理由第三项是权限声明有没有重复名称。这三项可以通过配置与资源文件自动判断“有没有”。第四项是隐私文本内容与实际功能是否一致它必须留给人工验证静态脚本不知道按钮后面究竟有没有真的录像、上传或第三方 SDK 调用。为什么不把第四项也写成true因为“字符串存在”与“说明准确”不是一回事。即使模块资源中有“用于拍摄照片”六个字若实际应用把相机画面提交到服务器这句文案是否充分还要结合真实数据流、隐私政策以及发布地区要求判断。静态门禁能发现缺项却无法证明材料合法、真实、完整。官方 AppGallery Connect 的隐私说明资料明确涉及敏感权限申请原因对于限制类权限还可能要求额外用途说明和使用场景视频。不同权限类型与应用发行策略存在区别因此本文不把“所有权限都必须上传视频”写成规则。具体流程应以提交时官方后台和最新政策为准。本地脚本只保留核对清单不把政策判断硬编码成永久结论。## 二、先让配置和资源之间能够对账ReviewLedger的最小示例包含一个entry/src/main/module.json5其中只展示当前示例涉及的字段。两个申请理由均使用字符串资源资源名分别为camera_reason和mic_reason。它们只是示例业务需求实际项目不要为了让巡检通过而声明并未使用的权限。json5{ module: { requestPermissions: [ { name: ohos.permission.CAMERA, reason: $string:camera_reason, usedScene: { abilities: [EntryAbility], when: inuse } }, { name: ohos.permission.MICROPHONE, reason: $string:mic_reason, usedScene: { abilities: [EntryAbility], when: inuse } } ] }}配套的entry/src/main/resources/base/element/string.json需存在对应条目例如{name:camera_reason,value:现场拍摄时获取画面}和{name:mic_reason,value:用户主动录制旁白时采集声音}。示例把字符串资源当作静态检查目标不代表这两句文案已被审核认可。实际场景要按系统权限、开发者协议与应用真实功能调整。这一步里最容易混淆的路径有两个module.json5是模块声明string.json是本地化资源$string:是对资源的引用不是已经自动展开的用户可见解释。工具应分别读取并交叉核对不能只搜索CAMERA关键词就得出“权限说明完善”。另外usedScene要以当前 HarmonyOS 官方配置格式为准别照抄旧 Java 时代config.json里的reqPermissions结构。## 三、写一段能真正停住坏材料的脚本下面用 Node.js 加json5解析项目配置。JSON5 允许注释等语法直接JSON.parse(module.json5)往往并不可靠这里明确引入真实存在的开源json5包开发环境需安装npm install --save-dev json5。示例脚本可保存为tools/permission-audit.mjs从项目根目录执行node tools/permission-audit.mjs。它对路径和资源结构有约定不扫描打包后的 HAP。jsimport { readFileSync } from node:fs;import JSON5 from json5;const config JSON5.parse(readFileSync( entry/src/main/module.json5, utf8));const source JSON.parse(readFileSync( entry/src/main/resources/base/element/string.json, utf8));const permissions config.module?.requestPermissions ?? [];const strings new Map((source.string ?? []).map( item [item.name, item.value]));function reasonExists(permissionName) { const row permissions.find(p p.name permissionName); if (!row || typeof row.reason ! string || !row.reason.startsWith($string:)) return false; const key row.reason.slice($string:.length); return typeof strings.get(key) string strings.get(key).trim().length 0;}const checks [ [CHECK_CAMERA_REASON, reasonExists(ohos.permission.CAMERA)], [CHECK_MIC_REASON, reasonExists(ohos.permission.MICROPHONE)], [DUPLICATE_NAME, new Set(permissions.map(p p.name)).size permissions.length]];for (const [key, ok] of checks) { console.log([DEMO] AG-206 ${key} ${ok ? PASS : FAIL});}console.log([DEMO] AG-206 PRIVACY_TEXT MANUAL_REVIEW);if (checks.some(item !item[1])) process.exitCode 1;示例约定三项自动检查全部通过控制台应打印CHECK_CAMERA_REASON PASS、CHECK_MIC_REASON PASS、DUPLICATE_NAME PASS最后单独保留PRIVACY_TEXT MANUAL_REVIEW。只有静态失败才设置非零退出码人工复核不会被偷偷转成通过。这个区分有意义CI 可以拦下格式错误却不能替运营或合规负责人签字。脚本故意不做毫无依据的权限白名单裁决。不同能力对权限声明、调用场景和受限权限申请有不同要求“字符串已声明”并不等于权限有效也不等于用户已经授权。正式项目应把所有模块的配置合并考虑并加入多语言资源、构建产物、release profile、实际调用链与服务端数据流检查。尤其是多模块场景只检查entry可能漏掉其他模块的声明。开发演示图中的ReviewLedger工程目录、PermissionPage.ets、module.json5、string.json对应的都是本文提到的检查对象。图中日志里的三次PASS和一次MANUAL_REVIEW是根据示例数据设计的 UI 演示并非 DevEco Studio 实机运行记录。少量红圈标注强调两个重要位置资源理由的校验与不能自动签字的人工项。## 四、静态通过之后还有哪些不能自动判断“相机理由存在”只是语法层面的通过。为了给产品和运营一个能够落地的交接方式我建议每个检查点附上责任人、证据入口和复核日期。应用版本更新后如果把拍摄页换成扫一扫入口旧的权限说明是否仍然适用不能让工具仅凭字符串仍然存在就一直显示绿色。隐私文案是最典型的人工项是否明示了信息采集目的、使用方式和范围是否有第三方 SDK 实际读取数据是否存在用户拒绝授权后的替代流程权限弹窗是否出现在用户主动触发的合适场景。单看配置文件无法回答这些问题。也不能因为审核平台上有隐私政策链接就断言链接在不同网络环境中都可访问或者文案与当前版本完全一致。我更看重“失败说明可以行动”。例如缺少camera_reason脚本不应只报false而应提示去string.json核对资源键出现重复权限名称则列出重复的name有了多语言资源后还要指出是哪个语言目录缺失。为了控制篇幅正文脚本只输出核心状态正式落地应加入这些定位字段并确保日志不泄露账号、证书或签名材料。## 五、把结果做成可读的巡检页App 侧可以读取构建阶段生成的巡检 JSON再显示对应任务状态不要在运行时直接读取源码目录里的module.json5因为那是开发工作区文件并不是面向用户的运行时数据源。以下PermissionPage使用固定演示数据建立页面结构真实项目应通过构建产物传递报告并为报告增加时间和版本标识。tsinterface AuditItem { label: string; status: string;}EntryComponentstruct PermissionPage { State private items: AuditItem[] [ { label: 相机权限申请原因, status: 通过 }, { label: 麦克风权限申请原因, status: 通过 }, { label: 权限重复申请检测, status: 通过 }, { label: 隐私文本内容核对, status: 待复核 } ]; build() { Column({ space: 12 }) { Text(ReviewLedger · 提审材料巡检).fontSize(22) Text(任务 AG-206仅本地静态巡检) Text(静态通过 3 项 / 人工复核 1 项) ForEach(this.items, (item: AuditItem) { Row() { Text(item.label).layoutWeight(1) Text(item.status).fontColor(item.status 通过 ? Color.Green : Color.Orange) }.width(100%).padding(12) }, (item: AuditItem) item.label) Button(重新巡检) Text(这不是应用市场审核结果).fontSize(12) }.width(100%).padding(16) }}运行页演示图对应AG-206的四项清单绿色三项、橙色一项并明确写着“这不是应用市场审核结果”。这里的“重新巡检”只用于说明报告刷新入口上面的页面代码未连接真正的 Node.js 构建命令更不会从手机直接调用开发电脑上的脚本。把这个边界写清楚才不会让读者误会按钮已经实现完整自动审核。交付前可安排三类验证。第一类修改资源键、制造重复项检查本地脚本是否正确失败第二类调整隐私说明、权限申请时机和功能入口由人员走完整交互路径第三类核对打包后的实际权限、发布资料及审核后台要求。三类证据互相补充不能用某一项“已通过”覆盖其他两项。当前结论module.json5与资源文件之间存在可以自动巡检的确定性关联AppGallery Connect 的提审材料却还有大量依赖真实业务事实与官方审核要求的判断。把“配置存在”“运行有效”“材料真实”“平台审核”分成四层记录比做一个全绿的大按钮更有价值。本文提供的是静态门禁最小实现和演示页面并未执行实际审核也不承诺某种权限声明一定获批。资料核对华为官方应用提审入口https://developer.huawei.com/consumer/cn/app/submitAppGallery Connect《Configuring a Privacy Description》更新于2025-11-05https://developer.huawei.com/consumer/es/doc/app/agc-help-release-app-privacy-desc-0000002313477969HarmonyOS模块权限配置示例https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/graphics-accelerate-gamebuddyservice-prepare。