HarmonyOS 7 module.json5+Node.js:权限用途跨模块对账【鸿蒙心迹】
临近版本提交时,一份工程配置里出现requestPermissions,通常容易被当成“权限已经配好了”。实际还差很多。声明了某项权限,只能说明应用提出了一个访问要求;用户是否授权、申请理由是否与使用场景一致、多个HAP模块的配置是否彼此冲突,以及发布平台是否认可,属于不同层面的证据。将它们压成一个绿色勾选框,对研发、测试和审核准备都没有帮助。
本文换一种角度,不写“怎样快速通过审核”的承诺,而是搭建一个本地静态质量门禁。演示工程叫PermitEvidenceLab,任务编号PERM-1010-15。Node.js脚本在开发机上读取两个演示模块的module.json5、对应的多语言资源与能力名称清单,产出一份可复核的权限用途差异报告。结果是:扫描2个模块、6条权限声明、4条演示约定的用户授权类权限、2条系统授权类权限,发现3处需要开发者修复的问题,最终本地状态EVIDENCE_HOLD。真实应用商店审核仍是NOT_RUN。
这和此前围绕语言占位符或HAR文件校验的工作不是一回事。这里并不核对翻译语法或构建字节,而是把权限名、提示理由、使用时机和实际模块中的Ability关联起来。做这种工具,首先要承认:脚本不能代替实际权限授权,也不能替代平台审核。它只能把开发者能够提前发现的明显矛盾翻到桌面上。

一、从一个很普通的“权限已配置”误判说起
假设项目里有两个HAP:入口模块entry和功能模块featureMap。入口负责拍照上传场景,功能模块提供门店位置和附近活动浏览。配置文件中有摄像头、麦克风、网络、位置、近似位置和蓝牙使用相关声明。某次本地编译能通过,并不说明所有申请理由都能被用户看懂。尤其是用户授权类权限,在不同模块、不同页面使用时,不应把一句笼统的“用于功能”复制到各处。
华为官方《在配置文件中声明权限》文档(2026-09-08更新)明确说明,应在module.json5的requestPermissions中逐项声明;对于user_grant或manual_settings权限,reason与usedScene承担相应配置要求,usedScene涉及Ability列表和when字段。官方还对理由文案的清晰度、长度与多语言表达提出了指导。这里的Node脚本只检查我们在演示夹具中能核实的部分,不推断所有系统权限的分类,也不发明新的上架审核规则。
usedScene.abilities如果指向不存在的Ability,页面可能仍有业务按钮,配置却已不具备可解释性。reason指向$string:location_reason,而资源文件里找不到相应键,编译或打包阶段可能会出现其他错误;在发布准备环节,它至少是值得阻断的证据缺口。脚本要把这些情况单独记录,不用同一种“权限失败”掩盖原因。
本轮的四个用户授权权限并非系统授权状态的实测。它们来自演示允许清单,具体名字是ohos.permission.CAMERA、ohos.permission.MICROPHONE、ohos.permission.LOCATION和ohos.permission.APPROXIMATELY_LOCATION。两个在本例中当作系统授权样本的是ohos.permission.INTERNET和ohos.permission.USE_BLUETOOTH。这是工具对已知样本的分类,不是一个覆盖所有HarmonyOS权限的万能字典;真实项目应对照目标API版本的官方权限类型表更新。
二、字段对账需要一份稳定的“事实表”
为了避免临时用正则表达式拆配置文本,第一步应该按JSON5语法真正解析module.json5。HarmonyOS工程允许JSON5格式里的注释、尾逗号等写法,直接调用JSON.parse可能把合法配置当成错误。这里使用开发机上的第三方json5包。它是Node脚本依赖,不会因为在项目中出现就变成HarmonyOS设备端SDK;安装与锁定版本也应纳入开发工具链管理。
再把入口与功能模块整理成同一种记录结构:moduleName、permissionName、reasonRef、usedAbilities、when。不要在原始配置上做原地修改,生成只读快照更利于对照和复盘。我们还要保存源文件路径、采集时间、源码哈希或构建提交号,避免版本更新后拿旧报告解释新包。本文示意的两个模块名是entry和featureMap,不是用户真实提交到平台的模块信息。
这段Node.js代码解决的就是“解析、提取、保留文件路径”的最小问题。module.json5中其他字段不会自动视为权限。遇到缺失的requestPermissions应返回空数组,遇到错误类型则直接报告配置不合法,不应静默丢弃不认识的节点。
// scripts/collect-permissions.mjs:开发机 Node.js 工具,不在设备端运行
import fs from 'node:fs/promises';
import JSON5 from 'json5';
export async function collectModule(file, moduleName) {
const doc = JSON5.parse(await fs.readFile(file, 'utf8'));
const declared = doc.module?.requestPermissions ?? [];
if (!Array.isArray(declared)) {
throw new TypeError(`${moduleName}: requestPermissions must be an array`);
}
return declared.map((entry, index) => ({
module: moduleName,
source: file,
index,
name: entry.name,
reason: entry.reason ?? '',
abilities: entry.usedScene?.abilities ?? [],
when: entry.usedScene?.when ?? ''
}));
}
配置快照若成功解析,不等于里面所有字段都合法。例如某个abilities被写成字符串而不是数组,后面的集合比较可能把它当作字符序列处理。生产脚本应增加严格类型检查、错误位置定位以及不可信文件尺寸限制。示例为了展示主线,只把最重要的字段带出来;不能把它当作完整JSON5校验器。
同一权限在两个模块同时声明,也不是自动算成错误。官方文档说明,多个HAP中不必在每个模块重复声明相同权限;应用级作用域与实际使用场景需要分开理解。静态门禁不该机械地把重复模块出现的权限全部标红,而应检查它们的用途描述是否有一致的业务含义,并在必要时合并整理。这个判断需要产品和隐私负责人参与,不能由数组去重代替。
三、检查顺序:名字、理由、场景、证据
工具最怕一次性做十几种“自动结论”,最后不知道一个红点从哪里来的。我会把预检顺序固定为四层。第一层只核查权限名是否在本地已确认的允许清单中;未知权限标为REVIEW_REQUIRED,不要武断地写成非法。第二层只对已确认属于用户授权类的演示权限检查理由引用。第三层验证usedScene.abilities是否能在当前能力清单中找到,并核查when取值。第四层把这些静态配置与团队另行维护的用途说明文档关联,用于人工复核。
演示中3处缺陷是:MICROPHONE没有usedScene;LOCATION的$string:location_reason在资源映射里解析不到;APPROXIMATELY_LOCATION引用了不存在的MapAbility2。摄像头理由能被找到、使用场景指向已声明的EntryAbility;网络和蓝牙作为示例系统授权权限,不因为没有弹窗理由就自动判错。这样,六条声明里三条无当前规则拦截,三条被挂起。脚本不会声称三条通过的权限已经得到用户授权。
下面的校验函数对演示范围实施明确的规则,尤其避免把系统权限一律套用用户授权权限的理由要求。knownAbilities来自配置文件汇总的受控Ability集合;reasons来自资源文件读取后的字符串映射,不是硬编码“看起来像合理文案”就算通过。
const USER_GRANT_SAMPLE = new Set([
'ohos.permission.CAMERA',
'ohos.permission.MICROPHONE',
'ohos.permission.LOCATION',
'ohos.permission.APPROXIMATELY_LOCATION'
]);
function checkOne(record, reasons, knownAbilities) {
const defects = [];
if (!USER_GRANT_SAMPLE.has(record.name)) return defects;
const key = record.reason.startsWith('$string:')
? record.reason.slice('$string:'.length) : '';
if (!key || !(key in reasons) || !String(reasons[key]).trim()) {
defects.push('REASON_UNRESOLVED');
}
if (!Array.isArray(record.abilities) || record.abilities.length === 0 ||
!['inuse', 'always'].includes(record.when)) {
defects.push('USEDSCENE_MISSING');
} else if (record.abilities.some(a => !knownAbilities.has(a))) {
defects.push('ABILITY_UNKNOWN');
}
return defects;
}
这段代码只是一个明确边界的本地规则函数。它不能验证“麦克风为什么要被调用”是否真实,也不能检测某个三方库是否在运行时访问了更多系统能力。更不能据此自动生成隐私政策或用户授权弹窗的最终文案。真正的权限用途应由产品行为、实际接口调用与应用对用户作出的告知共同支撑。
还有一个工程取舍:是否允许警告继续构建?对于只是文案风格可改进的提示,可以先记录为告警;对于本例这类关键字段缺失,建议在发布准备门禁里阻断。这里的EVIDENCE_HOLD是团队自定义的本地策略状态,不是AppGallery Connect API返回的审核码。UI必须把“本地阻断”和“平台审核未运行”分成两行,不能画成“商店审核失败”的真实截图。

四、报告里必须保留原始输入和反例
一个好的诊断结果不是告诉开发者“有三个错误”就结束。它至少要包含:模块、权限名、错误类别、哪个字段缺失、应去哪里修改,以及该问题对应哪一项本地规则。在这份演示报告里,MICROPHONE的问题是entry/module.json5中的usedScene没有记录;LOCATION的问题是featureMap资源键缺失;APPROXIMATELY_LOCATION的问题是MapAbility2不在已知Ability清单。三者的修复人可能不同,日志里必须能区分。
在排查时,不能把所有reason原样写入公开日志。如果理由字符串恰好拼进了测试账号、内部项目或其它敏感内容,应只输出资源键、路径和摘要,必要时对值做脱敏。合规工具自身也要遵守最小收集原则;不能为了“证明没有敏感字段”反而把敏感信息复制到新的报告文件里。
报告统计采用不夸张的方式:modules=2、declared=6、userGrant=4、systemGrant=2、blockers=3、passedByLocalRule=3、state=EVIDENCE_HOLD、storeReview=NOT_RUN。注意passedByLocalRule不是平台审核通过,也不是用户已授权。将这一列写成“通过审核3项”是不准确的,连一个小型教学Demo都不该这么写。
下面的聚合函数解决“数字究竟怎么来的”问题。它把每条权限最多记为一条阻断项,错误详情可以有多个标签,因此总数不会因为同一条权限同时缺理由和缺使用场景而被重复夸大。真实发布工具应同时统计阻断权限数量和阻断细目数量,让负责人知道要修复多少条声明、多少处字段。
function buildReport(records, reasons, knownAbilities) {
const issues = [];
for (const record of records) {
const defects = checkOne(record, reasons, knownAbilities);
if (defects.length > 0) {
issues.push({ module: record.module, permission: record.name, defects });
}
}
const count = records.length;
return {
taskId: 'PERM-1010-15', modules: 2, declared: count,
userGrant: records.filter(r => USER_GRANT_SAMPLE.has(r.name)).length,
systemGrant: records.filter(r => !USER_GRANT_SAMPLE.has(r.name)).length,
blockers: issues.length,
passedByLocalRule: count - issues.length,
state: issues.length ? 'EVIDENCE_HOLD' : 'EVIDENCE_READY',
storeReview: 'NOT_RUN', issues
};
}
在真实场景里,这段脚本还需要明确待复核权限和受限权限不能归入“系统授权”兜底分支。此处systemGrant之所以按补集统计,是因为六项输入都提前固定在小型夹具中;如果读入未知权限名称,应该分类为UNKNOWN_CLASS并触发人工检查,而不是直接计入通过。可以把这条作为写单元测试时必须补齐的第一项。
五、两张页面承担不同的证据职责
PermissionAuditPage是面向工程负责人的主报表,首屏看模块总数、权限总数、阻断总数及当前状态。它没有假装直接读取系统上架审核后台,而是读取本地生成的JSON摘要。演示画面上应明确写着EVIDENCE_HOLD和storeReview=NOT_RUN,顶部状态栏只是UI示意的一部分,不代表运行图由真机录制。

PermissionDetailPage则展示逐条问题,不再重复主报表的所有指标。每条记录显示权限名、源模块、失败字段和建议操作。例如MICROPHONE要补齐使用场景、LOCATION要补资源键、APPROXIMATELY_LOCATION要校正Ability名称。页面底部放一条本地静态测试时间线,方便核对是否读到了同一版夹具;同时提醒开发者完成用户授权交互、场景行为测试与正式平台提交前的人工审查。

这两张页面的差异很重要:概览页适合快速判断版本能否进入下一阶段;详情页适合定位责任与操作。把整张错误列表塞进首页,会让真正应该先看的状态和证据缺口被挤到屏幕下方。应用可以有漂亮的错误UI,但UI并不自动为脚本的准确性背书,报告输入、计算规则和输出字段都必须能被独立复核。
六、修复要改变真实配置,而不是把报告改成绿色
对第一条MICROPHONE缺使用场景,应确认业务是否真的需要在此模块请求麦克风权限。如果实际上没有对应功能,删掉不必要的声明可能比补一段模糊理由更好;如果确实用于录音,补齐与真实Ability和前台时机一致的usedScene,再提供用户能够理解的用途文字。隐私审查不是修饰句子的任务,而是检查应用是否在做所说的事。
对第二条LOCATION资源键缺失,应该在对应资源目录里增加有意义的理由,而非给脚本塞一个硬编码默认值,让发布前显示“已通过”。默认语言资源和其它语言资源要覆盖目标市场真实使用的界面;本篇检查资源键存在只是开始,不重复第13轮关于占位符漂移的深入校验。多语言显示效果仍需要真实设备和目标语言环境验证。
对第三条APPROXIMATELY_LOCATION使用不存在的MapAbility2,需要先追到当前工程的能力入口。它可能是旧版重构留下的名字,也可能是曾经存在但已经删除的功能模块。脚本不应该为了让校验变绿自动把它改成某个猜测的Ability。产品层应确认权限用途、工程层应确认能力声明,之后再重新生成报告。如果权限要求与实际业务已不一致,最好先调整应用设计。
修复后重新运行工具时,除了输出EVIDENCE_READY,还应保留上一次EVIDENCE_HOLD的时间戳和缺陷清单。否则后续有人怀疑为什么这一版突然少了某项权限时,没人能从报告回答。历史版本记录可以保存在CI产物中,附源码提交号、Node工具版本和输入文件摘要;这比只把截图发到群里更有追踪价值。
七、哪些检查不能交给本地脚本
很多人关心,既然静态权限清单已经对上了,能否在CI里直接打勾“可上架”?不能。首先,脚本无法知道用户看到的真实授权时机、拒绝后的降级路径和撤销权限后的资源释放情况。其次,三方库可能使用的权限、实际数据处理流程和提交到平台的隐私资料,需要另外的行为审查与证据。再次,受限权限的申请、证书和Profile、AppGallery Connect上的版本资料,都有各自的规则与平台环节,不能被一份自定义JSON替代。
华为官方关于权限声明的文档指出,理由必须清楚告知实际功能场景,user_grant与manual_settings涉及理由与使用场景要求。应用发布过程还可能要求对敏感隐私权限作具体说明,这些并不等于脚本能够完成审批。开发者在发布前应再查目标版本文档和平台实际提示,任何与脚本不符的地方以官方当期规范和实际项目为准。
这里还要区分“静态检查未命中”和“实际权限不会造成风险”。前者只表示本地规则没有发现问题;后者需要运行时行为、最小必要性、数据存储和用户透明度等完整审查。拿到前者就宣布后者成立,是一种很常见的工程短路。文章用storeReview=NOT_RUN提醒读者,工具的责任到哪里为止。
还有一个容易被忽略的条件:发布准备门禁本身也要支持例外,但例外必须被记录。有些业务可能在特定设备、区域或版本阶段尚未启用某项能力;应记录功能开关、目标市场和例外到期时间,而不是让一条if (debug) return true永远绕过检查。所有例外都应可审计,并由有责任的人批准。本文没有实现审批工作流,也没有发明平台批准角色。
八、把“工程可解释”作为交付目标
本地夹具的预期输出可以人工复算:两模块、六条声明、三处故障,计数为三条符合当前静态规则、三条待修复。验证时还要故意加入“资源键存在但文本为空”“usedScene.abilities不是数组”“权限名陌生”“同一权限跨模块出现”四种反例,检查报告是否拒绝给出错误的绿色结论。真正落地前,还应处理json5包版本固定、目录穿越保护、批量文件错误隔离、报告机密字段和CI退出码。
这一套工具的价值不在于让审核必然通过,而是把问题尽量留在开发机上。与其等到提交版本以后再倒查一段空白授权理由,不如在每次版本冻结之前就看到一份带源文件、规则编号和修复建议的报告。结果是否最终被平台认可,应由真实提交反馈说话。本轮只完成设计与本地夹具示意,不对官方审核结果作任何推断。
本篇结论只有一句:权限配置不仅要存在,还要能够解释其调用目的、引用来源和使用场景;一旦解释链断了,先阻断自己的发布准备流程,再谈把版本送到平台。 EVIDENCE_HOLD是我们定义的内部质量门,不能冒充任何系统或AppGallery Connect返回的状态。
官方依据与验证边界
- 华为开发者文档:在配置文件中声明权限,2026-09-08更新,核对
requestPermissions、reason、usedScene字段及分类约束。 - AppGallery Connect:配置隐私说明,核对提交版本时隐私权限说明与平台流程的边界。
- 脚本使用的是开发机Node.js、开源JSON5解析器和自定义比对规则;并未接入AppGallery Connect审核API,也未运行真实设备权限交互。所有权限清单仅用于演示,不代表用户项目或华为当期完整权限全集。
更多推荐




所有评论(0)