提交应用版本之前,市场素材目录里往往既有手机竖屏截图,也有测试人员在平板或旧模拟器上截下来的画面。文件名都叫“首页”“搜索”“详情”,人工逐个打开时不一定意识到:有张图虽然长宽比也是 9:16,实际像素只有 720×1280。它在文章配图里看着没问题,却不等于符合指定商店素材位的像素要求。

本文把这个检查提前到本地项目中。StoreShotGuard 是一支独立运行的 Node.js 校验脚本,配合一张仅用于结果展示的 ArkUI 演示页。它不调用 AppGallery Connect 的上传接口,也不能代替审核。示例报告号 SS-092,检查的是 phone-portrait/zh-CN 这一组四张假设存在的 PNG 文件,预计发现一张尺寸不符的 legacy.png。本文的文件名、大小和诊断结果是演示数据,不代表某次真实上架记录。

一、规格首先按“素材位”理解

1. 比例正确,也可能分辨率错误

华为 AppGallery Connect 官方的 Asset Specifications 页面在 2026 年 1 月 20 日更新,其 HarmonyOS 应用手机和平板截图表格列出了竖版组 9:16、1080×1920、每组 3~5 张,以及 PNG/JPG/JPEG 5 MB、WebP 200 KB 的格式和大小要求;横版组另列 16:9、1920×1080。不同设备品类、应用与元服务素材位的约束不能混写,规则应与实际选择的发布品类一起保存。

本例只实现竖版 PNG 的精确尺寸与字节上限检查,maxBytes 采用 5,000,000 字节做本地保守实现。官方表格写的是“5 MB”,并未在这段示例中承诺采用 1024 进制;上线前仍需以当前控制台、官方文档和实际素材位为准。工具故意不支持 JPG/JPEG/WebP 解析,不是说平台禁止这些格式,而是为了避免一个示例脚本冒充全格式验证器。

规则和图片应该同时带有设备组、语言组、素材位三层标识。把 1080×1920 强行应用到平板横图或 Vision 设备,会让检查器自己制造错误。即便本次只有 zh-CN,目录名也要明确;以后扩展 en-US 时,不能用当前的检查通过结果替代另一语言的实际上传材料。

2. 只读 PNG 头,不解码整张图

PNG 文件头的八字节签名和 IHDR 块可以提供宽高信息。用于静态素材审计,读取前 24 个字节就足以初步判断格式与尺寸,没必要为了几个整数加载完整图片并消耗内存。但是仅检查头部并不能保证图片完全可解码、未损坏或内容真实;它是构建流水线的第一道门,而不是最终验收。

下面是核心读取函数。readSync 返回实际读到的字节数,必须检查;文件句柄无论成功与否都在 finally 关闭。使用 readUInt32BE 是 PNG 头本身的大端约定,不涉及 HarmonyOS 的图像 API。

import { openSync, readSync, closeSync, statSync } from 'node:fs';

function readPngHeader(filePath) {
  const header = Buffer.alloc(24);
  const fd = openSync(filePath, 'r');
  try {
    const got = readSync(fd, header, 0, header.length, 0);
    const signature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
    if (got !== 24 || !header.subarray(0, 8).equals(signature)) {
      throw new Error('BAD_PNG_SIGNATURE');
    }
    if (header.toString('ascii', 12, 16) !== 'IHDR') {
      throw new Error('MISSING_IHDR');
    }
    return {
      width: header.readUInt32BE(16),
      height: header.readUInt32BE(20),
      bytes: statSync(filePath).size
    };
  } finally {
    closeSync(fd);
  }
}

签名和尺寸字段检查必须一起做:只看扩展名会把重命名成 .png 的 JPEG 当作正确素材;只看 9:16 会放过 720×1280。这段代码没有验证后续 IDAT、CRC 或颜色空间,正式发布前还应使用成熟解码器做一次完整打开验证,并由人工检查内容、隐私信息和品牌素材正确性。

二、检查应能够说清是哪一张、错在哪里

1. 路径限制比错误提示更基础

本地批次清单应只接受指定根目录下的相对文件名。若来自外部表单、脚本参数甚至共享表格,就可能被填入 ../ 逃逸路径。审核工具本来只想读素材,却无意中读了工作区其他文件,这种失误没有必要发生。

下面的代码采用固定演示清单 home.png、search.png、detail.png、legacy.png。文件解析严格限制在 store-assets/phone-portrait/zh-CN 目录内,并且把错误保存为逐项结果,不因某一张打不开就中断整份报告。

import { resolve, relative, isAbsolute, sep } from 'node:path';
import { writeFileSync } from 'node:fs';

const root = resolve('store-assets/phone-portrait/zh-CN');
const files = ['home.png', 'search.png', 'detail.png', 'legacy.png'];
const policy = { width: 1080, height: 1920, maxBytes: 5000000 };

function auditOne(name) {
  try {
    const abs = resolve(root, name);
    const rel = relative(root, abs);
    if (isAbsolute(rel) || rel === '..' || rel.startsWith('..' + sep)) {
      throw new Error('OUTSIDE_ASSET_ROOT');
    }
    if (!name.toLowerCase().endsWith('.png')) throw new Error('NOT_PNG');
    const info = readPngHeader(abs);
    const ok = info.width === policy.width &&
      info.height === policy.height && info.bytes <= policy.maxBytes;
    return { name, ...info, status: ok ? 'PASS' : 'BLOCK_SPEC' };
  } catch (err) {
    return { name, status: 'BLOCK_READ', reason: String(err) };
  }
}

const rows = files.map(auditOne);
const issues = rows.filter(row => row.status !== 'PASS').length;
const report = { id: 'SS-092', group: 'phone-portrait/zh-CN',
  checked: rows.length, issues,
  state: issues === 0 ? 'READY_FOR_REVIEW' : 'REVIEW_REQUIRED', rows };
writeFileSync('store-shot-report.json', JSON.stringify(report, null, 2), 'utf8');
if (issues > 0) process.exitCode = 1;

这是一个可嵌入 CI 的基本思路:脚本用非零退出码阻断错误素材进入下一道工序,但退出码为零不等于通过应用市场审核。真实项目中还应校验清单数量 3~5 张、禁止符号链接逃逸、检测图片完整性、验证同一组素材顺序和内容一致性。尤其当根目录可能被其他进程修改时,resolve() 只能处理字面路径,不能代替对真实路径与文件属性的严格访问控制。

三、把诊断结果做成审阅者一眼能读懂的卡片

演示假设前三张素材为 1080×1920,第四张 legacy.png 为 720×1280。所有四张都可以是 9:16,所以只校验比例的工具会误报“全部正常”。按 policy 的绝对尺寸判断,home.png、search.png、detail.png 为 PASS,legacy.png 为 BLOCK_SPEC,报告状态为 REVIEW_REQUIRED,即检查 4 项、有 1 项待修复。图片大小暂用演示值,不作为真实文件读数。

为了在文章中解释如何让产品、测试和研发共享一个结果,我们可以把这份报告 JSON 的示例副本写到 ArkUI 页面常量里。它不是 Node.js 在手机里直接运行,也不是平台提供的自动同步能力。页面只负责把已经审计过的状态展示清楚,避免用一个绿色大按钮遮蔽关键问题。

@Entry
@Component
struct ShotAuditPage {
  @State reportId: string = 'SS-092';
  @State issueCount: number = 1;
  @State reportState: string = 'REVIEW_REQUIRED';

  build() {
    Column({ space: 15 }) {
      Text('StoreShotGuard').fontSize(26).fontWeight(FontWeight.Bold)
      Text('报告 ' + this.reportId)
      Text('phone-portrait/zh-CN')
      Text('检查 4 项 · 待修复 ' + this.issueCount + ' 项')
      Text(this.reportState).fontColor(Color.Red)
      Text('home.png / search.png / detail.png · PASS')
      Text('legacy.png · BLOCK_SPEC')
      Text('实际 720×1280;要求 1080×1920')
      Button('返回素材列表')
    }.padding(20).width('100%')
  }
}

正文和技术图一致地使用 SS-092,不把演示页面的颜色、气泡、日志当成审核结论。若开发者将 JSON 接入应用内演示,需要显式写好资源打包、反序列化与失败回退,这不在本篇的三段代码范围内。对发布流水线而言,真正关键的产物是机器可读的 store-shot-report.json,而不是这个可视化页面。

四、验证路径应从异常文件开始

最容易发现校验器是否有价值的方式,是故意在测试夹具中准备四种情况:合法头且尺寸正确、合法头但尺寸不足、扩展名为 PNG 但实际签名错误、短于 24 字节的空壳文件。再新增一个“像素合规但文件超过 5,000,000 字节”的案例。BLOCK_SPEC 与 BLOCK_READ 应分清;前者是规则不满足,后者连可信元信息都读不出。

对于待提交的截图,还应该单独检查屏幕状态栏是否带有个人信息、是否混入调试浮层、页面是否和当前版本功能一致,及相邻截图是否有误导性重复。程序能确认像素,确认不了内容合法性、商标权限和审核政策适配。所谓“证据关联”是把 legacy.png、素材组与错误原因留在报告里,让设计同学知道具体改哪张,不是伪造任何已经得到市场平台认可的凭证。

配图中展示的底部日志 SS-092 checked=4 issues=1 和 legacy.png BLOCK_SPEC 720x1280,是示例报告的可视化,并非真实流水线执行输出。手机画面保留 REVIEW_REQUIRED 而非“已提交成功”,避免误导读者。修复 legacy.png 并重新扫描之后,只有全部检查项满足当前本地规则时才会得到 READY_FOR_REVIEW,名称中的 Review 仍提醒人工核对必不可少。

五、把工具放在正确的门槛上

这个脚本最适合放在素材入库和版本提交前,它不依赖 DevEco Studio 的设备连接,也不与 HarmonyOS 运行时 API 耦合。Node.js 负责本地字节审计,ArkUI 仅是供协作展示的演示载体。这样的职责分开,既保留了技术文章与 HarmonyOS 发布流程的关联,也避免把服务器侧 JavaScript 假装成 ArkTS 系统接口。

后续如果覆盖更多素材位,应先按官方文档建立独立规则表,再针对 PNG/JPEG/WebP 分别使用经过验证的解析实现。不要复制粘贴一个 1080×1920 常量到所有设备。控制台规则、上架政策与实际素材提交资格可能发生变化;本文引用的是官方文档注明更新日期时可核对到的规格,提交前仍需重新核查。最后一步应是人工打开图片、核对版本功能和权限内容,而非将 exitCode = 0 当作“官方已审核通过”。

参考资料:

  • 华为 AppGallery Connect《Asset Specifications》(2026-01-20):https://developer.huawei.com/consumer/en/doc/agc-help-app-visual-asset-spec-0000002277607976
  • 华为开发者《华为应用市场》发布流程:https://developer.huawei.com/consumer/cn/appgallery

(本文所有截图、日志和检查数据是合成演示,Node.js 脚本未对实际商店账号进行任何读取或提交。)

Logo

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

更多推荐