Picset/文章中心/AI玩法
AI玩法

用 Node.js 给商品图片建立 SHA-256 指纹清单

封面由 AI 生成,仅作主题示意。

Picset 内容研究组 · 发布/核验于 2026年9月29日 · 约 3 分钟阅读
用 Node.js 给商品图片建立 SHA-256 指纹清单
简要回答

封面由 AI 生成,仅作主题示意。

封面:给图片留一份指纹

封面由 AI 生成,仅作主题示意。

  图片文件叫“最终版.png”,后来同名文件又被覆盖了,上传记录却仍然写着旧名称。只按文件名核对资产,就可能分不清上次实际用的是哪份内容。这里实现一个本地文件清单函数:为文件读取字节数与SHA-256,并把调用方提供的商品信息一起记录。

它不判断画面是否美观、颜色是否准确,也不签发真实性凭证。文件指纹用于排查文件内容变化;同一个商品重新压缩、修改元数据或换编码,都可能改变它。判断两张图是否“视觉相同”是另一项任务,不属于本文实现。

清单里把计算字段与业务字段分开

Picset 是可产生商品图片素材的一条工具路径,页面提供详情图制作。[1] 素材也可以来自摄影、设计软件或供应商。本函数只处理已保存到本地的文件,不调用产品接口,不假设Picset支持某种API,也不把工具名称当作图片已经获准上架的证明。

文件路径、字节数与哈希由程序获得;商品编码、包装版本、用途和审核状态由业务流程提供。代码可以记录一行“待确认”,却不能通过文件指纹推导“已审核”。把这两个来源分开,后续才知道该去修文件还是修业务登记。

图片文件、清单与指纹比对的概念关系

AI 技术概念图,不是实际哈希值、程序输出或安全认证。

下面限定文件在指定素材目录内,允许子目录;通过realpath再检查一次符号链接解析后的路径,拒绝指向目录外的文件。读入前限制体积,示例默认20 MiB,这是项目参数,不是电商平台规范,也不是完整的安全隔离。

实现一个不修改原图的函数

保存为 asset-manifest.cjs。示例用Node.js标准库,SHA-256接口依据官方crypto文档。[2]

const fs = require('node:fs/promises');
const path = require('node:path');
const { createHash } = require('node:crypto');

function within(root, file) {
  const rel = path.relative(root, file);
  return rel !== '' && !path.isAbsolute(rel) &&
    rel !== '..' && !rel.startsWith('..' + path.sep);
}

async function assetRecord(rootDir, relative, metadata = {},
  maxBytes = 20 * 1024 * 1024) {
  if (typeof relative !== 'string' || !relative ||
      path.isAbsolute(relative)) throw new TypeError('需相对路径');
  if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0)
    throw new TypeError('体积上限无效');
  const root = await fs.realpath(rootDir);
  const candidate = path.resolve(root, relative);
  if (!within(root, candidate)) throw new Error('路径超出素材目录');
  const actual = await fs.realpath(candidate);
  if (!within(root, actual)) throw new Error('链接指向目录外');
  const info = await fs.stat(actual);
  if (!info.isFile()) throw new Error('目标不是文件');
  if (info.size > maxBytes) throw new Error('文件过大');
  const bytes = await fs.readFile(actual);
  if (bytes.length > maxBytes) throw new Error('读取后文件过大');
  return {
    metadata,
    file: relative.split(path.sep).join('/'),
    bytes: bytes.length,
    sha256: createHash('sha256').update(bytes).digest('hex')
  };
}

module.exports = { assetRecord };

调用时传入素材根目录、文件相对路径和业务字段,例如 assetRecord('./assets', '01.png', { sku: 'DEMO-01', status: '待确认' })。其中编码是示例,不对应真实商品。函数返回Promise;在调用脚本中使用await,或者then与catch处理结果。需要批量清单时,先逐文件执行并记录失败项,别因为一个文件不存在就默默跳过它。

返回对象里保留的是调用时读到的文件字节。以后再对同一文件生成记录,可以比较哈希,并检查业务版本是否也变化。文件改名但字节没变,哈希可以保持相同;两份业务记录的商品编码是否允许共用图片,仍应由业务规则决定。不能因为哈希相同,就自动合并不同商品。

测试时不要只检查正常文件

本批交付用本机Node实际验证了正常文件、重复读取、同字节改名、字节变化、中文与子目录路径、目录目标、体积上限和目录逃逸等分支,具体结果随包保存。测试不是对任意环境的安全保证,也不证明所有图片可解码。

函数没有检查扩展名、文件签名或图片宽高。即使把文本文件改名成PNG,它也能产生字节指纹,不能因此将其认定为图片。若需要图像类型预检或解码,应接另一道处理,不把几种不同检查合成一个模糊的“素材合格”状态。

还有并发修改边界。检查路径、读取文件之间,文件系统状态可能变化;这个示例适合受控素材目录的日常记录,不用于处理不可信用户能够同时改动的文件树。大目录也不宜无限并发读取,批量版本需要另设并发与错误记录策略。

将清单放进图片交付流程时,先确定它帮助回答哪个问题:某份发布记录对应的文件是否改变,还是商品资料已经换版?前者可以由指纹协助定位,后者需要商品版本字段。把指纹当作辅助记录,才不会让技术字段承担它无法提供的内容判断。

参考资料

[1] Picset:产品介绍(页面提示国际站升级为 Picell AI)

[2] Node.js v24.19.0:crypto.createHash 与 Hash 文档