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;}@Entry@Componentstruct 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/submit;AppGallery Connect《Configuring a Privacy Description》(更新于2025-11-05):https://developer.huawei.com/consumer/es/doc/app/agc-help-release-app-privacy-desc-0000002313477969;HarmonyOS模块权限配置示例:https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/graphics-accelerate-gamebuddyservice-prepare。
更多推荐




所有评论(0)