Node.js+JSON5:权限声明与功能说明差异清单【鸿蒙心迹】
到了准备提交应用的阶段,开发者通常会打开 module.json5 看一眼权限,再回到产品文档检查对应功能的用途说明。两份内容可能分别都写得很认真,却仍然出现错位:配置里多了一个权限,说明文案没有提;或者功能早已撤掉,原来的权限仍在清单中。这种问题并不需要等到审核环节才发现,工程本身就可以提前做一次静态对账。本文设计的是一个本地工程工具 PermLedger,它比较 entry/src/main/module.json5 的 requestPermissions 与团队维护的 release/permission-copy.json,输出供开发者核查的差异报告。它不会模拟应用市场的审核决策,也不会声称“一条规则就能保证上架”。演示任务号 PL-091,检查三项声明,其中两项有对应功能说明,一项缺失,因此业务状态显示 BLOCKED。
## 一、配置文件和业务说明各自有盲区module.json5 是应用模块配置的一部分,其中的 requestPermissions 用来声明权限名等信息。权限能否使用还涉及授权方式、使用场景、权限级别以及设备环境;读取一份 JSON5 文件并不能证明运行时已经得到授权。另一方面,产品说明写着“扫码”“云同步”,也不等于工程已经正确声明相应权限。工具因此不尝试取代系统权限管理。它只回答一个更窄的问题:准备交付的这个模块中,声明过的权限是否能在团队维护的功能说明里找到对应项? 这比根据几条关键词推断“应用是否违规”要稳妥。工具输出的是“需要人工核查的差异”,不是“平台审核拒绝原因”。在 PermLedger 的示例里,配置声明了 ohos.permission.CAMERA、ohos.permission.INTERNET、ohos.permission.MICROPHONE。说明文件分别列出“扫码录入”对应相机、“云端同步”对应网络;麦克风对应的说明缺失。这里的三个字符串是用于演示静态比对的权限名称,是否确有必要保留麦克风权限还得由产品功能和官方权限文档来判断。另一个不能跳过的现实是 JSON5 与普通 JSON 不同。DevEco 工程配置允许注释、尾逗号等写法,用 JSON.parse() 硬读会造成无意义的解析失败。因此我让脚本显式依赖第三方 json5 解析包,版本由工程锁文件管理,而不是假装 Node.js 内置了 JSON5 解析器。## 二、读取声明:先保证输入可信第一步只完成读取和归一化。使用 Node.js 标准库 node:fs 读取两个文件,再用 json5 解析工程配置、JSON.parse() 解析团队自己维护的严格 JSON。工具从 module.requestPermissions 中提取 name,剔除空值与重复项。json5 需要先在工具工程安装,例如运行 npm install --save-dev json5;这是一项三方依赖,不是鸿蒙平台专有 API。下面是 tools/audit-permissions.mjs 的第一段核心逻辑,解决不同配置格式和重复声明导致的计数偏差。为便于文章聚焦,使用固定的工程相对路径;在真实流水线中应从命令行接收模块目录,并在启动时校验路径是否位于工作区内。javascriptimport fs from 'node:fs';import JSON5 from 'json5';const moduleText = fs.readFileSync('entry/src/main/module.json5', 'utf8');const copyText = fs.readFileSync('release/permission-copy.json', 'utf8');const config = JSON5.parse(moduleText);const copy = JSON.parse(copyText);const records = config.module?.requestPermissions ?? [];const declared = [...new Set(records.map((item) => item.name) .filter((name) => typeof name === 'string' && name.length > 0))];const explained = Array.isArray(copy.explanations) ? copy.explanations : [];脚本故意没有把 reason 字段当成一段自动通过检查的业务说明。配置引用的字符串资源可能只是给系统弹窗展示的简短原因,而团队的功能说明需要覆盖用户能在产品中看到的入口和使用目的。两者应该核对,但不能简单用“存在 reason”替代内容审查。输入读取失败、JSON5 语法错误和字段形状不对时,应当让工具以错误退出,并向开发人员报告路径和解析原因,而不是生成一份“没有权限、全部通过”的空报告。用户打开一个错误的空报告,比脚本明确失败更危险。这里也不应偷偷吞掉不可识别的字段结构;不同工程模板必须先做格式适配。## 三、差异不是结论,而是下一步动作第二步根据声明集合找出说明缺失项。permission-copy.json 里的每个 explanations 元素包含 permission、feature、text。工具要求 text 非空,同时保留 feature 供人工回查。单纯出现同名字符串还不够,相关功能在发布版本中是否存在,无法靠脚本完全判定。下面这段代码执行集合比对,并生成 release/audit/permission-audit.json。演示中的 PL-091 是本地任务号,不是应用市场工单或审核编号;BLOCKED 也仅代表团队定义的提交前门禁尚未满足。javascriptconst hasExplanation = (name) => explained.some((item) => item.permission === name && typeof item.text === 'string' && item.text.trim().length > 0 && typeof item.feature === 'string');const missing = declared.filter((name) => !hasExplanation(name));const report = { auditId: 'PL-091', declared: declared.length, explained: declared.length - missing.length, missing: missing.length, missingPermissions: missing, status: missing.length === 0 ? 'READY_FOR_REVIEW' : 'BLOCKED'};fs.mkdirSync('release/audit', { recursive: true });fs.writeFileSync('release/audit/permission-audit.json', JSON.stringify(report, null, 2), 'utf8');console.log(`[PermLedger] audit=PL-091 declared=${report.declared} explained=${report.explained}`);console.log(`[PermLedger] missing=${report.missing} status=${report.status}`);if (missing.length > 0) process.exitCode = 1;这段代码只覆盖“声明存在但说明缺失”的方向。反过来,文案中写了某个权限而模块没有声明,也值得另加一项反向比对;不过有些功能依赖系统选择器而非应用主动申请读取权限,不能把每个功能名粗暴转换成一个必须声明的权限。示例收缩范围,是为了减少未经验证的政策推断。工具采用非零退出码是为了接入本地检查或持续集成。它不是平台规则。项目有多个模块、不同渠道构建或不同产品风味时,应对实际交付模块集合逐一检查,还要确认打包使用的配置与本地扫描的是同一份。否则脚本对“源文件通过”的判断可能和最终构建产物不一致。
## 四、把机器报告译成开发者能处理的页面输出 JSON 适合持续集成,不适合让非脚本维护者逐字段核对。为了展示结果怎样与 ArkUI 页面衔接,PermLedger 另有一个 AuditPage.ets。演示 UI 使用一份和命令行输出完全一致的静态报告:声明数 3、已说明 2、差异 1、状态 BLOCKED,缺失的是 ohos.permission.MICROPHONE。这里强调一个工程边界:下面的页面示例并没有实现“从电脑上的 release/audit 文件自动读取到手机”。想将预检报告随包展示,需要建立明确的构建期复制、资源打包或经授权的数据下发流程。文章的 UI 只是帮助读者理解报告结构,而不是编造了一个跨设备自动读取 API。arktsinterface AuditReport { auditId: string; declared: number; explained: number; missing: number; status: string;}@Entry@Componentstruct AuditPage { @State report: AuditReport = { auditId: 'PL-091', declared: 3, explained: 2, missing: 1, status: 'BLOCKED' }; build() { Column({ space: 14 }) { Text('权限说明预检').fontSize(25).fontWeight(FontWeight.Bold) Text(`任务 ${this.report.auditId}`) Text(`声明 ${this.report.declared} / 已说明 ${this.report.explained}`) Text(`差异 ${this.report.missing} · ${this.report.status}`) .fontColor(Color.Red) Text('待核对:ohos.permission.MICROPHONE') Button('查看差异') }.padding(20).width('100%') }}点击“查看差异”在产品化版本中应打开明确的权限用途说明和对应功能入口;本文的代码仅构造可视化展示,不包含按钮导航或报告同步。UI 演示图上另有“重新核对”文字,指向的是回到构建脚本重跑检查的业务动作,不应理解为点击手机按钮就能直接读取开发电脑文件。## 五、上线前具体应核对什么演示屏幕把 ohos.permission.MICROPHONE 标成差异,不等于建议随便补一句文案来消除红色。合理的处理顺序是先问:发布版本是否真的提供录音能力?若功能已经删掉,应审查是否可以移除权限声明;若仍需要它,应补充准确、与真实触发时机相符的说明,再验证申请方式和拒绝授权后的体验。
对于正常保留的声明,还应复查 reason 字符串资源是否存在、是否与界面里向用户展示的目的相符,以及 usedScene 等字段是否按对应权限要求配置。不同权限可能具有不同授权规则;静态集合比较不能覆盖系统授权弹窗、受限权限资质、第三方 SDK 权限引入、隐私政策内容与平台审查要求。我通常会把本地报告的状态设计得比“成功/失败”更保守。READY_FOR_REVIEW 意味着本轮静态检查没有发现当前定义的差异,后续仍需要人工复核;BLOCKED 表示需要处理差异,但不推断平台最终一定拒绝。这样日志不会给团队一个错误的“已经合规”暗示。如果报告需要成为交付记录,最好附上构建提交号和被扫描文件的哈希。权限声明修改之后必须重跑脚本,不能拿上一轮的截图作证据。对于多人协作项目,脚本应优先打印具体缺失的权限名,并在持续集成中保留当次输出,而不是把错误信息藏在一个图形徽章里。## 六、工具停在何处才算合理一个小型 Node.js 脚本确实可以把两份配置之间的文字差异拦在上架之前,但它的价值是帮助工程团队发现需要确认的问题,而不是替代平台政策判断。对于 HarmonyOS 而言,权限声明的结构、授权方式和开放范围必须回到官方文档逐项核对;应用市场最终审核标准也可能调整,不能用本文示例中的数字作承诺。这套方法适用于权限清单、三方 SDK 接入说明、页面能力声明等有明确“源文件—说明文件”映射关系的场景。如果后续把它扩展为真正的发布门禁,还需要覆盖多模块、构建产物验证、反向差异和人工签署记录。先把检查边界讲清楚,再往上叠功能,工具才不会变成另一份看似权威却并不准确的台账。资料核对: 华为开发者文档《对所有应用开放》 https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-v5/permissions-for-all-V5 ;《开发准备》关于 module.json5/requestPermissions 的示例 https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/development-preparation ;DevEco Studio 签名与 ACL 权限说明 https://developer.huawei.com/consumer/cn/doc/doccenter-deveco-studio/ide-signing-auto 。
更多推荐




所有评论(0)