技术栈:AppGallery Connect(上架流程背景)/Node.js 20+/ESM/PNG IHDR/JSON 交付清单
Demo:LocaleShotMatrix|任务编号:LM-406

这次的麻烦发生在提交素材前的最后一轮核对。应用有中文、英文和法文三套介绍文案,美术同事在网盘里放了很多图,运营同事在表格里填了“都已准备”。真正按语言打开目录,才发现英文横版缺文件、法文竖版还停留在待设计状态。截图并没有丢失,问题是大家说的“齐全”指向了不同的交付集合。比起继续在群里逐张对图,我更希望让一个可复查的矩阵说明到底差哪一格。

先解释范围。华为 AppGallery 公开指引说明,开发者可以在发布流程中选择语言并本地化基础信息,包括应用介绍及截图等素材;但官方对具体应用类型、设备、国家地区及当前表单可能有不同要求。**本文的“每种语言都准备竖版和横版各一张”是示例产品团队制定的内部交付合同。**真正提交时仍要核对控制台当时适用的规格和审查要求。

先明确“格子”而不是追问“图片够不够”

我给 Demo 取名 LocaleShotMatrix,任务编号 LM-406。这是一款旅行记录应用的上架素材交付示例:语言是 zh-CN、en-US、fr-FR,团队内部素材方向是 portrait 和 landscape。相乘一共有六个格子。每格在 JSON 清单里记录相对路径;如果文件不在交付目录,或者格式与方向不匹配,就需要返工。这里的方向名只是视觉沟通约定,不直接代表应用商店表单的设备类型或官方字段。

构造样本刻意保留两处缺失:en-US/landscape.png 和 fr-FR/portrait.png。另外四个格子有真实可打开的示例 PNG。预期最终为 PASS 4 / HOLD 2。这一计数先由规则推导出来,再由脚本实际计算,而不是照着已经设计好的封面填写数字。检查时每个格子都应输出原因,报告使用英文状态码,方便 CI 聚合;面对运营同事时再映射成“可交付”和“需补素材”的中文提示。

为什么不直接遍历文件夹?因为文件存在不等于它是被当前发布版本采用的素材。目录里可能同时有旧版本、被弃用的图、没有审核过的草稿与待替换文案。以清单为主、文件系统为证据,可以让每个语言和方向恰好对应一个候选文件。也不用把“包含 PNG”误写成“可用于官方上架”,那是更宽的内容审核问题。

第一道门:路径不能越过交付根目录

清单会被 CI 读取,不能盲信字符串。有人把路径写成 ../../secrets.png,即使本地恰好能找到,也不能让它被算作合法素材。第一段代码先规范化相对路径,并约束最终解析结果仍在专用 screenshots 目录之内。绝对路径、空路径、非法扩展名都返回 HOLD 原因。Windows 与类 Unix 路径规则不同,真实工程要固定 CI 平台并扩大边界用例覆盖,本文脚本在当前 Node.js 环境执行。

import path from 'node:path';
import { existsSync } from 'node:fs';
export function resolveAsset(root, relative) {
  if (typeof relative !== 'string' || !relative || path.isAbsolute(relative)) {
    return { status: 'HOLD_PATH' };
  }
  const full = path.resolve(root, relative);
  if (!full.startsWith(path.resolve(root) + path.sep)) {
    return { status: 'HOLD_PATH' };
  }
  if (path.extname(full).toLowerCase() !== '.png') {
    return { status: 'HOLD_FORMAT' };
  }
  return existsSync(full) ? { status: 'PASS', full } : { status: 'HOLD_MISSING' };
}

这个最小函数只是预检,并不替代真正的文件访问权限设计。符号链接可能把看似正常的路径指向交付根目录之外,生产环境应在允许读取后使用 realpath 复核物理路径;若希望彻底隔离,最可靠的方法是在独立工作目录里生成素材镜像并限制执行账号权限。即使项目在 Windows 上运行,原始清单仍建议使用统一的正斜杠形式,并在读取环节做平台适配。

第二道门:文件有了,还要确认横竖方向

设计师可能把竖图改名叫 landscape.png,单看文件名就会漏掉错误。PNG 的前八个字节包含签名,随后的 IHDR 数据块保存图像宽高。项目脚本能够用标准库读取这些元数据,而不需要提前启动任何 HarmonyOS 页面。这里只检查签名和宽高,实际文件解码完整性、色彩空间、内容准确性和图片版权仍应由其他工具或人工流程负责。

import { readFile } from 'node:fs/promises';
export async function checkPngDirection(file, expected) {
  const buf = await readFile(file);
  const sig = '89504e470d0a1a0a';
  if (buf.length < 24 || buf.subarray(0, 8).toString('hex') !== sig) {
    return 'HOLD_FORMAT';
  }
  const width = buf.readUInt32BE(16);
  const height = buf.readUInt32BE(20);
  const direction = width > height ? 'landscape' : 'portrait';
  return width === height || direction !== expected ? 'HOLD_DIRECTION' : 'PASS';
}

有意没有使用“宽必须多少像素”的断言。分辨率下限、截图数量与文件大小的要求,需要在计划发布时按对应页面资料另外配置,并且存在变更可能。本文只做方向一致性和清单覆盖性判断。上面的函数也只示例读取头部,没有解析全部数据块;若交付对象来自不可信来源,应使用成熟图像解码器补做文件完整性验证,防止签名正确但内容损坏的文件流入发布流程。

第三道门:把六个组合全部枚举出来

有两种常见的漏检:只读取清单已有的项,会把本来缺失的组合完全跳过;读取全部 PNG 文件,会把不在本期计划中的旧素材算进来。我选择以已确定的语言表和方向表生成笛卡尔积,再逐格查清单。这样“没有配置”本身就是一个结果,不必靠人工在报告中发现少了某一行。项目维护人员可以在配置文件里改语言集合,不必把审核规则写死在图片生成脚本里。

const locales = ['zh-CN', 'en-US', 'fr-FR'];
const orientations = ['portrait', 'landscape'];
const matrix = [];
for (const locale of locales) {
  for (const orientation of orientations) {
    const relative = manifest[locale]?.[orientation];
    matrix.push({ locale, orientation,
      status: relative ? 'CHECK_FILE' : 'HOLD_MISSING', relative });
  }
}
console.log(`LM-406 cells=${matrix.length}`);

第三段只负责生成待检查的矩阵,实际随包 Node.js 工具还会接上路径检查和 PNG 方向检查,并写出每一格的最终状态。将“生成格子”和“检查文件”拆开,是为了单元测试更加直接:增加一个语言应该多两个格子;删除一份清单路径仍保留原本格子,只是状态变为 HOLD。这能防止一类非常隐蔽的错误——因为少了输入,检查器反而宣称全部通过。

调试时我重点看哪些数字

我的运行步骤是:从项目根目录执行 node --check tools/check-locales.mjs;确认语法通过后再运行 node tools/check-locales.mjs;打开生成的 reports/matrix-report.json,核对六个固定组合的排序和结果。随后将 en-US/landscape.png 补进目录重跑,应当让 HOLD 从二变成一;再补齐法文竖图,才预期全部通过。这个反向验证比只拿初始结果截图更重要,因为它能证明规则确实对缺失文件产生了响应。

为了不让假通过掩盖问题,CI 模式约定存在 HOLD 时退出码为 2,方便上游停止提交素材;本地普通运行仍生成报告供运营阅读。这两个行为都属于项目自行定义。日志应该打印语言、方向、候选路径和原因,但不要将真实账号、后台访问令牌或未公开应用信息写进示例。整个流程不需要访问 AppGallery Connect 的管理接口。

内部完整性与表单合规要分开核对

这个 Demo 解决的是团队交接,而不是“过审算法”。它可以发现有名单却没有文件、竖横方向不对应、交付配置遗漏;无法判断截图是否夸大功能、是否包含禁止内容、商店实际使用何种回退语言或上传页面是否采用相同素材结构。某些应用类型、版本或设备形态在控制台可能共享截图资产,不能机械要求它们按我的六格矩阵分别上传。发布负责人应根据实际支持的语言和页面提示,将这份内部清单调整成适用的合同。

我还会在交付前做一次“把文件挪走”的破坏性测试:暂时重命名某个原本通过的 PNG,再运行检查器,报告必须准确指出对应语言与方向缺失。把文件放回后重新运行,状态应能恢复。这个操作发生在本地示例目录,不涉及商店后台,也不应该对生产资产做破坏性改动。测试价值在于检验脚本不是拿硬编码的统计结果冒充扫描结果。

此外,若换了一版应用界面,旧截图内容虽然仍能成功读取,也不代表它反映当前功能。值得在矩阵里追加“应用版本号、设计稿版本和验收人”三个字段,让机器负责覆盖性,人负责真实性;再配合图片哈希及人工抽检,而不是把所有问题塞进一个检查器。这样代码的价值才会稳定:它只阻断自己有能力确认的缺口,对不确定的官方政策和内容风险明确提示人工核查。与其将一个 PASS 徽章贴在运营日报里,我更愿意交一份说明缺口位置、检查范围和无法判断事项的报告。

官方资料:华为 AppGallery 应用分发与本地化流程;华为 AppGallery Connect 公开产品页;华为应用本地化资料示例(游戏场景,勿套用于所有应用)。参考资料用于确认“本地化信息与素材管理”是实际流程;本篇的六格规则、路径组织及 CI 阻断均为项目自定义示例。

Logo

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

更多推荐