应用发版前,包体之外还有一堆会被反复传来传去的素材:图标、竖版截图、横版截图、营销文案。开发环境里的页面已经改完,运营手里的图片却可能还停留在上个版本。图片能打开,并不表示宽高、文件大小和数量一定符合提交表单的要求。比人工盯着资源管理器逐个点开更可靠的方法,是把可以机械检查的项目变成一个离线命令。本篇只做一件事:为 AssetGate 建立一份本地上架图片门禁。演示任务 AG-909 有一个图标和三张竖版 PNG 截图。设定图标为 1024×1024;01-home.png 为 1080×1920;02-task.png 被错误导出为 1080×1880;03-result.png 为 1080×1920。理想的静态统计是通过三项、待修复一项。这个结果说明文件尺寸匹配,不代表 AppGallery Connect 已经接受,更不是正式审核结论。## 一、先把官方要求和本地选择分开华为 AppGallery Connect 的 Asset Specifications 文档列出了 HarmonyOS 手机和平板素材要求。文档更新于 2026 年 1 月 20 日;其中应用图标允许 216×216 或 1024×1024,PNG 上限 3 MB;竖版截图为 1080×1920,数量通常为 3~5 张,PNG/JPG/JPEG 上限 5 MB。横版另有 1920×1080 的规格。这里要注意“支持的选项”不等于“所有选项必须一起交”:本示例仅检查团队本次选用的竖版 PNG 素材集合。这些阈值并不是 Node.js 的固定知识,而是外部平台约束。应将它们维护在易核对的策略对象中,并在真正提交前重新确认官方最新页面、设备分类以及活动要求。如果产品还要支持平板、穿戴或其他营销资产,不能机械套用这份只处理手机竖屏截图的脚本。对 JPEG、WebP 等格式,也应按各自编码格式增加解析器,而不是“看扩展名像图片”就判定尺寸正确。为便于审核,第一段代码把本地路径、采样分组和门禁阈值摆到台面上。所有路径都从约定的 assets 目录读取;脚本不会触碰开发者中心账号,也不上传任何隐私信息。js// audit-assets.mjs:本轮只检查 PNG 图标与竖版 PNG 截图const TASK_ID = 'AG-909';const limits = { icon: { sizes: [216, 1024], maxBytes: 3 * 1024 * 1024 }, portrait: { width: 1080, height: 1920, minCount: 3, maxCount: 5, maxBytes: 5 * 1024 * 1024 }};const files = [ { name: 'app_icon.png', role: 'icon' }, { name: '01-home.png', role: 'portrait' }, { name: '02-task.png', role: 'portrait' }, { name: '03-result.png', role: 'portrait' }];````TASK_ID` 用来关联一个检查批次,不能拿它充当应用市场任务编号;这只是自定义的演示标识。`role` 是本地约定,供扫描器使用,并不对应某个官方 JSON 接口。素材项统一交给脚本枚举,比让开发者手动把文件名硬贴进提交页面更易发现漏项。生产场景还应把客户端版本号、素材更新时间和负责人的复核结论纳入发布清单,静态检查只是一道门,而不是替代流程审批。## 二、不解码整张图,也能核对 PNG 尺寸PNG 文件有规范的签名与 IHDR 头部,宽度和高度以大端整数保存。这里使用 Node.js 内置 `fs`,无需引入读取整个图像像素的三方库。文件足够短、签名不符、体积超限时应报错;脚本不会假设扩展名正确就放行。只校验头部不等于完整的文件完整性检查,损坏数据块仍可能通过,因此后续还需要人工预览或额外的解码检测。jsimport fs from ‘node:fs’;import path from ‘node:path’;const ROOT = path.resolve(‘assets’);const PNG_SIGNATURE = Buffer.from([137,80,78,71,13,10,26,10]);function inspectPng(fileName) { const absolute = path.join(ROOT, fileName); const info = fs.statSync(absolute); const fd = fs.openSync(absolute, ‘r’); const header = Buffer.alloc(24); try { const actual = fs.readSync(fd, header, 0, header.length, 0); if (actual < 24 || !header.subarray(0, 8).equals(PNG_SIGNATURE)) { throw new Error(‘INVALID_PNG_HEADER’); } if (header.toString(‘ascii’, 12, 16) !== ‘IHDR’) { throw new Error(‘NO_IHDR’); } return { width: header.readUInt32BE(16), height: header.readUInt32BE(20), bytes: info.size }; } finally { fs.closeSync(fd); }}这里的 `finally` 有实际意义:即使图片头损坏,文件描述符也必须关闭。目录扫描器还需要防止路径穿越——代码中 `files` 是固定白名单,不接受用户在 UI 随便输入 `../` 路径;如果改成外部输入,就应再加路径规范化与根目录约束。可以把这一段看成素材预检工具的最小内核,而非通用 PNG 格式验证器。图片分辨率不能只凭文件字节数推断。两张 PNG 可能同为几百 KB,但宽高相差很大;压缩率、图片内容、颜色类型都会影响字节量。因此“体积通过”和“尺寸通过”要作为不同条件检查。对于用户上传的大量图片,读取几十字节头部的成本比完整解码小得多,但安全性也相应有限。## 三、先报清楚失败项,再决定是否阻断第三段代码完成数量检查、逐文件检查和汇总。通过项与待修复项分别入列;缺失文件、头部异常都转成可解释的问题,不让一次异常吞掉整批扫描结果。最终用非零退出码提醒流水线失败,但真实 CI 是否停止构建、停止上传,还需要对应平台的任务配置来接收这个退出码。jslet pass = 0;let fix = 0;const portraits = files.filter((item) => item.role === ‘portrait’);if (portraits.length < limits.portrait.minCount || portraits.length > limits.portrait.maxCount) { fix += 1; console.log([DEMO] ${TASK_ID} screenshot_count FIX);}for (const item of files) { try { const p = inspectPng(item.name); const good = item.role === ‘icon’ ? p.width === p.height && limits.icon.sizes.includes(p.width) && p.bytes <= limits.icon.maxBytes : p.width === limits.portrait.width && p.height === limits.portrait.height && p.bytes <= limits.portrait.maxBytes; if (good) pass += 1; else fix += 1; console.log([DEMO] ${TASK_ID} ${item.name} ${good ? 'PASS' : 'FIX'} ${p.width}x${p.height}); } catch (e) { fix += 1; console.log([DEMO] ${TASK_ID} ${item.name} FIX ${String(e)}); }}console.log([DEMO] ${TASK_ID} pass=${pass} fix=${fix});if (fix > 0) process.exitCode = 1;```用预先准备的四张本地 PNG 样例运行脚本,预期终端显示 app_icon.png PASS 1024x1024、01-home.png PASS 1080x1920、02-task.png FIX 1080x1880、03-result.png PASS 1080x1920,总计 pass=3 fix=1。本轮可对这组样例执行实际 Node 命令;但样例不是待发布应用的真实素材。命令退出码为 1,是检查失败的正常表示,不应误解成脚本崩溃。图片所展示的开发面板和检查结果仍是依照同一数据制作的示意图。## 四、发布团队真正需要的是能定位问题的报告只有一个红色“未通过”标志没有多少价值,负责处理的人还需要知道是哪张图、差在哪里、由谁补。02-task.png 的宽度是对的,高度比目标少四十像素。修复建议应写“重新导出为 1080×1920”,而不是“质量不好,请优化”。另外三项只是静态检查通过;就算数量与尺寸正确,也不代表画面内容、版权授权、文字表述或隐私声明都已经满足审核要求。还有个发布节奏问题:不要让设计同学把已经检查过的图片覆盖为新版本却不重新跑工具。更稳妥的是,报告保存文件哈希与检查时刻;平台上传时按哈希核对是否仍是同一组文件。这个拓展没有写进三段最小代码,因为它与本轮“尺寸和数量门禁”的核心问题不同,但在真实流水线里应优先补上。若走多市场多语言版本,还应把语言目录和截图内容对应关系列入核验范围。从技术角度,PNG 的 IHDR 检查只能证明文件头部宣称某个宽高,不能保证全文件可解码。要降低误报,应在上架前增加人工视觉抽查,并使用可信图片解码器再过一遍关键样本。运行进程还需为大量文件设置扫描目录、最大数量、最大字节数和超时边界,防止单个畸形文件拖住整条流水线。## 五、将“符合尺寸”与“审核通过”彻底分开对 AG-909,本地逻辑能给出的结论只有:图标检查通过一项、竖版截图通过两项、尺寸待修复一项。它不能给出真正审核通过率,也不能预测上架时间;更不能绕过开发者中心的隐私声明、资质证明、包体合法性和人工内容审查。团队可以拿它阻断明显错误的素材上传,却不应据此修改任何正式提交状态。还有必要区分“扫描对象”与“上架对象”:如果检查的是设计预览目录,真正上传时却从下载文件夹选取旧图,门禁再严格也失效。可把 assets 视为唯一交付目录,由发布流程从该目录复制,目录中的图片命名、哈希和复核记录同时打包归档。UI 上也应使用“本地预检”这样的字样,避免没有参与开发的同事把绿色 PASS 理解为市场审核通过。在检查范围的设计上,本篇只选择 PNG 是刻意的减法。官方允许 JPEG 等格式,但不同编码的宽高解析方式不一样。仓促加入一个只看扩展名的判断,反倒容易漏检文件头与真实内容不符的样本。需要多格式时,应该先为每种格式写出明确解析与异常处理,再加上样例文件验证;并为数量、边长、体积设置能从官方配置表更新的规则,不在代码里散落魔法数字。官方素材规格:https://developer.huawei.com/consumer/en/doc/agc-help-app-visual-asset-spec-0000002277607976。官方分发流程:https://developer.huawei.com/consumer/cn/appgallery/devstart/。两者都应以提交当日最新页面为准。本篇没有登录开发者中心,没有上传真实应用,也没有模拟审核员反馈。示例代码只使用 Node.js 标准库,能够在本地 fixture 上进行独立复现;文中的 HarmonyOS 手机界面仅用于展示检查报告的阅读方式,不意味着这是系统内置的上架审核界面。

Logo

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

更多推荐