本文检查的是项目内部待提交 PNG 文件,不调用 AppGallery Connect 审核接口,更不能替代平台审核。所有 IDE、HiLog、手机后台图均为独立绘制的技术示意;示例输出使用构造文件说明规则,并非真实应用上架结果。

一、一个看起来小的尺寸差异,为什么适合做成脚本

在应用发布流程里,功能、签名和截图往往分属不同人员。视觉稿定稿后,项目组可能会再压一次图,或把某张图改成了更宽的版本;图片看起来仍是竖屏,但它已经不满足当前选择的上架素材规格。开发者手工翻文件夹可以发现显眼错误,却容易遗漏混入素材、文件改名和大小超限。与其等待上传时再来回沟通,不如在准备提交包之前运行一个完全本地的检查器,让尺寸、长宽比、数量和体积形成可读的报告。

华为 AppGallery Connect 的官方《Asset Specifications》页面在 2026-01-20 的版本中列出了 HarmonyOS 手机与平板应用截图的相应约束。例如页面表格包含竖版 9:16、1080×1920、3~5 张,图片可使用 PNG、JPG/JPEG,并列出对应的文件大小上限。这里专门选择一组竖版 PNG做练习,按表格的 PNG/JPG/JPEG 5 MB 项做静态门禁。这个选择不是说全部应用、设备品类、审核阶段只有这一组规则;方屏、横屏、穿戴和视觉设备的规格各异,平台政策也可能调整。发版时应重新对照官方当前页面,而不是把本文里的常量当成永久政策。

Demo 名称叫 ReleaseShotGate,编号 RG-203。为了让演示同时包含正常与异常分支,准备四个候选文件:cn-phone-01.png、cn-phone-02.png、cn-phone-03.png 均为 1080×1920 的待提交示意资源;cn-phone-04.png 故意设为 1200×1920。前三张符合本次限定的分辨率规则,第四张比例也不再是 9:16,因此预期结果显示 READY 3 / BLOCK 1。这里的 READY 只表示通过自定义本地门禁,不代表在平台上架审核已通过。

二、先从最少依赖入手,只读 PNG 文件头

截图自动校验不一定需要完整解码图片。标准 PNG 的文件签名占八字节,宽高位于 IHDR 中的固定位置,读取前二十四字节便可以做初步尺寸判断,文件真实长度由 stat 提供。脚本只使用 Node.js 内置 fs 与 path,避免为了一个本地校验器引入大型图形库。该方法无法证明图片在视觉上正确、内容没有泄露隐私或者编码数据没有损坏;这些必须交给预览检查、完整 PNG 解码及人工内容审查。为了减少误报,非 PNG 直接给出“当前脚本不支持”,而不是把 JPG 误读成坏图。

// audit.js,以下三个代码块按顺序放在同一文件
const fs = require('node:fs');
const path = require('node:path');
const PNG_SIGNATURE = Buffer.from([137,80,78,71,13,10,26,10]);
const POLICY = { width: 1080, height: 1920, minCount: 3,
                 maxCount: 5, maxBytes: 5 * 1024 * 1024 };

function inspectPng(filename) {
  const fd = fs.openSync(filename, 'r');
  try {
    const h = Buffer.alloc(24);
    if (fs.readSync(fd, h, 0, h.length, 0) !== 24) {
      throw new Error('SHORT_HEADER');
    }
    if (!h.subarray(0, 8).equals(PNG_SIGNATURE) ||
        h.toString('ascii', 12, 16) !== 'IHDR') {
      throw new Error('NOT_PNG_IHDR');
    }
    return { width: h.readUInt32BE(16), height: h.readUInt32BE(20),
             bytes: fs.fstatSync(fd).size };
  } finally { fs.closeSync(fd); }
}

读取文件头还有一个好处:不需要创建 PixelMap,不会把发布资产检查任务带进 Image Kit 的内存分配流程。但并不意味着前二十四字节足以作安全验证,脚本只面向团队信任目录的静态检查。生产环境如果文件来自不可信第三方,仍应限制输入路径、做完整文件校验与资源上限,并使用受维护的图像解析方案。脚本对 IHDR 长度和 CRC 没有做全面检查,因此报告使用“header dimensions”而非“已证明文件可解码”。这一条边界要写在工具首页,避免报告被外部误用。

三、把失败原因拆开:宽高、比例、大小、数量

很多脚本遇到不合格图片就输出一个布尔值,实际协作却需要知道原因。对 cn-phone-04.png,应明确报告 DIMENSIONS,而不是告诉设计同事“图片格式不正确”。同时比对宽高和长宽比,看似重复,实则有利于读者理解:1080×1920 是本次选定的目标分辨率,9:16 是方向和比例逻辑;一张 900×1600 图片比例正确,却未达到这里选定的目标像素尺寸。判断比例时用整数交叉乘法 w*16 === h*9,可以避免浮点误差。体积则读取字节数,不要把计算机的 MiB 与政策文件表述的 MB 无声混用;本工具明确采用 5 * 1024 * 1024 字节这一项目内部门禁,需要在发布前按官方口径复核。

function auditOne(filename) {
  const reasons = [];
  if (path.extname(filename).toLowerCase() !== '.png') {
    return { name: path.basename(filename), ok: false,
             reasons: ['ONLY_PNG_IN_THIS_DEMO'] };
  }
  try {
    const info = inspectPng(filename);
    if (info.width !== POLICY.width || info.height !== POLICY.height)
      reasons.push('DIMENSIONS');
    if (info.width * 16 !== info.height * 9)
      reasons.push('ASPECT_9_16');
    if (info.bytes > POLICY.maxBytes) reasons.push('FILE_SIZE');
    return { name: path.basename(filename), ...info,
             ok: reasons.length === 0, reasons };
  } catch (e) {
    return { name: path.basename(filename), ok: false,
             reasons: ['UNREADABLE_HEADER'] };
  }
}

异常捕获没有把文件打开失败、格式错误、过短头和系统错误分成四种人类语言描述,是为了保持教学案例容易复制。真正接 CI 时应该保留安全处理后的错误码和文件名,必要时关联合规工单,而不是把操作系统原始路径、用户目录和完整异常栈直接公开在日志里。另一方面,系统层面可能有其他截图资产要求,如应用内容真实、可读性、语言版本匹配和不同设备截图组等,它们不能被本脚本“尺寸合格”四个字覆盖。

四、最终报告:让发布经理看到的是可以处理的待办

脚本从指定目录查找 .png 文件,逐个运行 auditOne 并汇总 READY/BLOCK。数量门禁只对本组已通过单图校验的截图计数;这属于我们的流程设计,平台是否接受仍需以实际提交规则判断。为了把例子说清,假设候选 01~03 各自的文件头正确且不超过配置的体积,04 为 1200×1920,预计终端打印 RG-203 READY 3 BLOCK 1。如果候选集中出现 JPG,报告会提示本 Demo 暂不支持,不能简单算作政策不允许 JPG,因为官方明确列出了 JPG/JPEG。最后通过 process.exitCode 让 CI 对未通过的批次返回非零状态。

function run(folder) {
  const files = fs.readdirSync(folder)
    .filter((name) => /\.(png|jpg|jpeg)$/i.test(name))
    .sort().map((name) => path.join(folder, name));
  const results = files.map(auditOne);
  const ready = results.filter((r) => r.ok).length;
  const block = results.length - ready;
  const countOk = ready >= POLICY.minCount && ready <= POLICY.maxCount;
  for (const row of results) {
    console.log(`${row.name} ${row.ok ? 'PASS' : row.reasons.join('+')}`);
  }
  console.log(`RG-203 READY ${ready} BLOCK ${block} COUNT_OK=${countOk}`);
  if (block > 0 || !countOk) process.exitCode = 1;
  return { ready, block, countOk };
}
if (require.main === module) run(process.argv[2] || './shots');

上面三个代码块拼在一个 audit.js 后,运行 node audit.js ./shots 即可得到一份本地报告。配套素材包还提供一份 audit.js,方便直接复核逻辑,但这不是 DevEco 构建脚本,也不负责把截图上传 AppGallery Connect。你可以把它放进应用发布流水线,先跑本地检查,再由具有发布权限的人完成官方控制台提交。不要让脚本持有上架账号凭据:尺寸检查完全不需要密钥、访问令牌或网络权限。

五、怎样调试,才能避免“这次检查过了,下次又错”

建议把输出 JSON 与待审版本号绑定,至少记住:生成时间、项目内部任务 ID、文件名、宽高、字节数、规则版本和拒绝原因。第一步故意构造一张 1200×1920 的 PNG,检查是否只因为尺寸与比例被拦;第二步把一张 1080×1920 的文件改成不完整数据,确认工具不会把未读到的头信息当作合格;第三步放入仅有两张合格文件的目录,确认数量门禁会失败。第四步验证文件修改后的再次运行不会复用旧结果。本练习并不宣称上述步骤已在华为真机或应用市场控制台执行,它们是实际交付前应做的验证清单。

图里的手机界面是一张报告看板的概念稿:RG-203、前三张 PASS、第四张 BLOCK,状态汇总写着 READY 3 / BLOCK 1。它不代表官方市场 App 的审核界面,也不暗示截图规格检查能够在手机上替代发布平台。之所以画这个界面,是想让非技术人员不用读终端也能明白阻塞项在哪里。当前脚本不会自动生成该看板,实际工程若有需要可以再把 JSON 报告交给自己的管理后台渲染。

六、边界是这类脚本最重要的一行说明

本文的“通过”只说明本地 PNG 的头部尺寸、文件体积以及本组数量达到预先设定的工程规则。它没有验证文字是否符合政策、截图是否反映真实产品、是否含敏感个人数据,也没有验证真实文件的 PNG CRC、JPG 兼容性与不同应用类别的素材规格;更没有访问或调用 AppGallery Connect 的任何审核接口。官方素材说明可能因为地区、设备类型、应用形态或页面更新而改变,上架前必须重查对应类别的最新表格。对横屏、方屏、视觉设备或需要视频的项目,应该先建立新的规则配置,而不是硬把宽高换一个数便宣称适用。

最实用的验收结果不是自动化程序显示了一个大大的绿色圆圈,而是每个人都知道“哪个文件不合格、为什么不合格、由谁修改、何时再检查”。将图片制作和提交审核拆成两个责任环节,前者用脚本提前清除可机械验证的错误,后者保留官方控制台和人工审查的最终决定权。这样即使工具只用了几十行 Node.js,也有资格成为发布工程里一个可信的轻量门禁,而不是一张漂亮却不负责的截图。

官方资料与复核说明

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐