AppGallery Connect+Node.js:上架截图版本漂移离线证据清单与发布门禁【鸿蒙心迹】
一个应用准备提交新版本时,最容易拖慢团队的有时不是功能,而是素材追不回来源。手机页已经换了最新界面,平板说明图还是上一轮截的;运营在群里收到一批 PNG,开发却不知道这些图到底对应哪个构建。这个问题单凭文件名很难解决,因为 home-final-2.png 并没有可验证的版本含义。
这次我把工作范围压到一件事:在打开 AppGallery Connect 上传页面之前,用本地 Node.js 对截图来源清单做一轮预审。ReleaseBinder 示例任务为 RB-707,目标版本 2.7.0,共有八条截图登记,七条标记为目标版本,一条 TAB-03 仍登记为 2.6.4,因此内部门禁输出 HOLD。这是一套团队自定义的质量检查,不是华为审核平台自动给出的结果,也不意味着官方规定所有截图必须携带一致的版本字段。

一、先弄清截图版本漂移到底指什么
这里说的“漂移”,不是 PNG 像素里的系统版本号,也不是用户手机的 HarmonyOS 版本,而是制图记录标注的应用构建版本和准备上架的目标构建不一致。截图可能仍然准确,也可能展示已删除入口、过时文案或未经确认的开关,具体影响必须由业务和审核人员复核。机器只负责指出需要复查的证据,不能替代人工看图。
我不建议脚本直接去扫描每张截图上的所有文字。一方面容易把隐私信息、日期和测试账号误判为版本,另一方面那会变成 OCR 问题,偏离本轮目标。最小可行办法是让素材导出动作同时写一条结构化登记记录:截图 ID、文件相对路径、目标平台、制图来源构建版本和操作者核对标志。清单保存到 Git 或流水线工件里,让后续人能找到是哪一次生成的图。
华为开发者网站把 AppGallery Connect 列为应用分发与发布环节的入口;具体是否需要哪种截图、分辨率或资质,要以当前应用类型对应的官方控制台要求为准。本文并不杜撰截图数量、比例、文案限制,更不会声称“这个脚本通过就能上架”。检查发生在上传之前,仅对团队自定字段负责。
二、固定最小数据契约,避免报表自己制造歧义
ReleaseBinder 项目结构很短:release/build.json 记当前构建;release/captures.json 记八条素材记录;scripts/check-evidence.mjs 运行预审;reports/evidence-report.json 保存结果。演示里的 TAB-03 是平板页面图,登记版本仍是 2.6.4,其余七条为 2.7.0。不把这八张业务截图作为真的图片文件交付,本轮生成的三张是文章配图,二者数量和用途必须分开。
第一段代码说明输入形状。它是可以由脚本消费的标准 JavaScript 数据对象;八条明细并不代表已经从 AppGallery Connect 实际读取任何记录。
const build = { app: 'ReleaseBinder', version: '2.7.0', buildCode: 270 };
const captures = [
{ id: 'PH-01', platform: 'phone', version: '2.7.0' },
{ id: 'PH-02', platform: 'phone', version: '2.7.0' },
{ id: 'PH-03', platform: 'phone', version: '2.7.0' },
{ id: 'PH-04', platform: 'phone', version: '2.7.0' },
{ id: 'TAB-01', platform: 'tablet', version: '2.7.0' },
{ id: 'TAB-02', platform: 'tablet', version: '2.7.0' },
{ id: 'TAB-03', platform: 'tablet', version: '2.6.4' },
{ id: 'TAB-04', platform: 'tablet', version: '2.7.0' }
];
选这种结构而不是直接把版本塞进图片名,是因为图片名会变,ID 和登记记录可以单独维护。正式工程里还应该记录截图生成时间、构建流水线编号和负责复核的人。尤其在团队协作场景下,一张截图被重新压缩或改名,不代表画面内容变了;反过来,文件名没变也不代表内容没有变。
三、判断要保守:发现问题就先 HOLD
真正的检查逻辑不需要调用任何尚未核实的华为审核 API。它是普通 Node.js 数组过滤与字段校验,能够在开发机或 CI 环境执行,和运行在 HarmonyOS 手机上的 ArkTS 页面也没有直接的系统调用关系。这里优先检查清单是否为空、ID 是否重复、版本字段是否存在,再比较构建版本;否则“全部无记录”也会被错误地算成全部一致。
function audit(build, captures) {
if (!build.version || !Array.isArray(captures) || captures.length === 0) {
throw new Error('INVALID_MANIFEST');
}
const seen = new Set();
for (const row of captures) {
if (!row.id || !row.version || seen.has(row.id)) {
throw new Error('INVALID_CAPTURE_ROW');
}
seen.add(row.id);
}
const stale = captures.filter(row => row.version !== build.version);
return {
taskId: 'RB-707', target: build.version, captures: captures.length,
matched: captures.length - stale.length, stale: stale.length,
staleIds: stale.map(row => row.id), gate: stale.length > 0 ? 'HOLD' : 'READY'
};
}
HOLD 与 READY 是团队内部门禁的名字,不是 AppGallery Connect 的审核状态枚举。如果 captures.json 损坏、版本为空或有两个相同 ID,函数会抛出异常,外层命令应该退出非零状态,而不是悄悄生成 READY。脚本失败和截图版本漂移也不是一类问题:前者意味着无法判断,后者意味着已经拿到明确的待复核条目。
模型计算结果为八条中的七条一致、一条不一致,staleIds 中只有 TAB-03。为什么不自动把版本号改为 2.7.0?因为那会毁掉证据价值。发现旧图后应重新核对画面,必要时重新用正确的构建截图;若确认旧图仍适用,也必须由负责人员写明理由,而不是让算法替人证明正确。
四、输出报表,留存可复核的失败原因
团队常常把“发现问题”当作最后一步,但下一个同事接手时,最重要的其实是复现路径。建议报表同时提供任务号、目标版本、数量、待复核 ID 和生成时间;原始记录不在报表里被修改。磁盘写入失败时,控制台应当明确打印失败原因,不能在内存算出 HOLD 就告诉运营报告已经成功保存。
import { readFile, writeFile, mkdir } from 'node:fs/promises';
const build = JSON.parse(await readFile('release/build.json', 'utf8'));
const captures = JSON.parse(await readFile('release/captures.json', 'utf8'));
const report = audit(build, captures); // audit 为上一段应用自定义函数
await mkdir('reports', { recursive: true });
await writeFile('reports/evidence-report.json',
JSON.stringify(report, null, 2) + '\n', 'utf8');
console.info(`[ReleaseBinder] RB-707 target=${report.target} captures=${report.captures}`);
console.info(`[ReleaseBinder] RB-707 matched=${report.matched} stale=${report.stale}`);
console.info(`[ReleaseBinder] RB-707 gate=${report.gate} staleId=${report.staleIds.join(',')}`);
if (report.gate !== 'READY') process.exitCode = 2;
上面第三段是命令主体摘录,运行时需要把第二段的 audit 放进同一模块,或明确从自建模块导入,不能把三个分开的文章代码块误认为复制即得完整工程。writeFile 是覆盖写入;更严格的 CI 还可先写临时文件再原子替换,避免过程中被其他任务读取半份报告。对同一输出路径的并发写入必须串行化,防止不同批次相互覆盖。

五、示意页面应该展示异常,而不是包装成绩
界面演示名叫 ReleaseBinder 证据预审,预设任务号 RB-707、目标 2.7.0、截图 8、同版本 7、过期 1、门禁 HOLD。报表关注条目是 TAB-03,登记来源版本 2.6.4。演示日志依次写 15:45:10 target=2.7.0 captures=8、15:45:11 matched=7 stale=1、15:45:11 gate=HOLD staleId=TAB-03。它们都是给文章与配图对照用的预设数据,并非真实 HiLog 或上传记录。
把 Node.js 脚本的源代码放在 DevEco 风格的开发环境截图里,只是说明仓库可以同时存 ArkTS 工程和本地发布辅助脚本。不能因此推断 Node.js 脚本直接运行在 HarmonyOS 手机端;也不能把模拟器里显示的 HOLD 说成应用市场审核失败。它表达的是“开发团队选择暂停本次素材流转”,只有负责人核对后才能进入下一步。
真实交付时还需要人工逐项打开手机、平板、多形态下的应用页面,核实截图描述是否忠实、是否覆盖当前功能以及是否泄露测试账号、定位或第三方受保护信息。脚本只看登记元数据,不读取 PNG 像素,不具备识别页面真假和内容合规的能力。这一点比让界面出现醒目的绿色通过标记更重要。

六、把失败拆开,避免误把内部检查当审核结论
异常可以分三层。输入层:清单缺失、重复 ID、版本字段为空,脚本标为不可判定并退出。内容层:版本记录不一致,产生 HOLD 和待复核条目,不自动修订。平台层:上传后实际 AppGallery Connect 的审核反馈,是另一个系统的事实,只能以后续官方控制台信息为准。这三层分清了,CI 才不会因为报表生成就错误宣布应用发布成功。
文件路径也要约束在工作空间内,但该问题此前已单独讨论过目录逃逸,本轮不重复展开。这里的新增工程重点是截图证据和应用构建版本之间的映射、预审状态的可追溯性,以及把问题交还给人工复核。未来若要把这个流程接到 PR,建议先输出机器可读的 JSON,再生成供运营查看的摘要;不要让开发与运营分别维护两套数据,以免二次漂移。
七、交付前我会实际确认哪些条件
对这套模型最直接的验收是:八条数据输出七条匹配、一条过期;把 TAB-03 的版本改回 2.7.0,门禁才会由 HOLD 转成 READY;删掉必要字段或制造重复 ID 时,应输出错误而非假通过。还要测试报表目录不可写、并发两次执行、文件格式损坏等环境条件。这里可以验证普通 Node.js 函数与序列化,不等同于 AppGallery Connect 平台集成测试。
我更看重它给发布团队提供了一条可核对的证据链,而不是多了一个脚本。截图能从任务号找到来源,构建能与截图逐项比对,失败会留下具体 ID。至于华为应用市场最终的上架要求,应以当前官方文档、开发者协议以及控制台即时提示为准。示意里的 READY 只说明本地规则放行,不意味着任何真实应用通过审核。
官方依据:华为开发者《华为应用市场/应用测试与分发》(可访问页面):https://developer.huawei.com/consumer/cn/appgallery ;华为官方《上架申请》(2026-02-04):https://developer.huawei.com/consumer/cn/doc/Media-Guides/app-release-0000001101428382 ;Node.js官方文件系统文档:https://nodejs.org/api/fs.html 。上架流程资料用于确认平台背景,不代表截图版本等于官方强制规定。
更多推荐



所有评论(0)