在WooCommerce店铺运营中,商品图片的质量直接影响点击率和转化率。同一个产品,配一张带背景、带文案的Mock展示图,往往比单纯的白底图效果好得多。但SKU一多,设计师手动P图就变成了瓶颈。其实绝大多数Mock图都有固定模板:背景图固定、商品图位置固定、文案字体固定,变的只有商品本身和文字内容。这种高度模式化的出图需求,完全可以交给Node.js自动完成。本文就来详细讲解如何用Node.js生成Mock图片,并通过WooCommerce REST API自动更新到商品。

一、技术选型与环境搭建
生成图片在Node.js生态里主要有两条路:一是node-canvas,二是sharp。两者定位不同。sharp基于libvips,擅长图片缩放、裁剪、格式转换,性能极强,但绘制能力弱,画文字、做复杂合成都比较别扭。node-canvas是Canvas 2D API的服务端实现,API和浏览器里的Canvas几乎一致,drawImage、fillText、globalCompositeOperation一应俱全,非常适合做模板化合成。因此Mock图片生成场景,首选node-canvas。
安装过程有个常见的坑:node-canvas依赖Cairo等原生库,在Windows或某些精简版Linux上直接npm install canvas可能报错。CentOS/Ubuntu需要先安装cairo-devel、pango-devel、libjpeg-turbo-devel等系统依赖;macOS用brew装pkg-config cairo pango libpng jpeg giflib librsvg即可。如果实在装不上,可以考虑@napi-rs/canvas,它是预编译的二进制包,不需要本地编译环境,API与node-canvas基本兼容,缺点是极少数冷门特性不支持。
WooCommerce通信方面不需要额外SDK,直接用axios请求REST API即可,端点默认是https://你的域名/wp-json/wc/v3,鉴权推荐用Consumer Key和Consumer Secret的基础认证方式,在HTTPS环境下传输是安全的。下面是项目的基本依赖:
npm install canvas axios dotenv // 项目结构建议 // mock-generator/ // templates/ 存放背景模板图 // products/ 存放商品透明底PNG // fonts/ 存放字体文件 // output/ 生成的成品图 // .env 存放WooCommerce密钥 // app.js 主逻辑
二、用Canvas合成Mock图片的核心实现
合成逻辑本质上是分层的:最底层是背景模板图,中间层是商品透明底PNG,最上层是文案文字。只要把每一层的坐标、尺寸确定下来,就能批量套模板。第一步是注册字体,中文字体必须显式注册,否则fillText画出来的中文会变成方块或直接被忽略。
第二步是处理商品图的等比缩放与居中。商品图尺寸千差万别,直接拉伸会变形,正确做法是先计算缩放比例,再计算居中偏移,保证商品图完整落在预留区域内。第三步是阴影效果,把商品图用globalAlpha降低透明度后先画一遍偏移的黑色剪影,再叠加原图,就能得到简单的悬浮阴影,视觉上立体很多。
const { createCanvas, loadImage, registerFont } = require('canvas');
const path = require('path');
// 注册中文字体(必须在fillText之前完成)
registerFont(path.join(__dirname, 'fonts/SourceHanSans.ttf'), {
family: 'HanSans'
});
async function generateMock(productImage, title, subtitle, outputPath) {
// 模板尺寸,WooCommerce推荐至少1000x1000
const canvas = createCanvas(1000, 1000);
const ctx = canvas.getContext('2d');
// 第一层:背景模板
const bg = await loadImage(path.join(__dirname, 'templates/bg-1.jpg'));
ctx.drawImage(bg, 0, 0, 1000, 1000);
// 第二层:商品图等比缩放并居中到预留区域(x:200,y:250,w:600,h:500)
const img = await loadImage(productImage);
const box = { x: 200, y: 250, w: 600, h: 500 };
const scale = Math.min(box.w / img.width, box.h / img.height);
const dw = img.width * scale;
const dh = img.height * scale;
const dx = box.x + (box.w - dw) / 2;
const dy = box.y + (box.h - dh) / 2;
// 简单阴影:先画一遍半透明黑底再叠原图
ctx.globalAlpha = 0.25;
ctx.drawImage(img, dx + 8, dy + 12, dw, dh);
ctx.globalAlpha = 1;
ctx.drawImage(img, dx, dy, dw, dh);
// 第三层:文案
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 56px HanSans';
ctx.textAlign = 'center';
ctx.fillText(title, 500, 830);
ctx.font = '30px HanSans';
ctx.fillStyle = '#ffd700';
ctx.fillText(subtitle, 500, 880);
// 导出为JPEG,体积更小
const fs = require('fs');
fs.writeFileSync(outputPath, canvas.toBuffer('image/jpeg', { quality: 0.92 }));
return outputPath;
}有几个细节值得注意。textBaseline默认是alphabetic,做多行文字排版时建议显式设置为middle或top,否则不同字号的行间距很难控制。另外商品图一定要用透明底PNG,如果原图是白底JPG,直接叠上去边缘会非常生硬,可以先用sharp做一次简单的抠图或交给上游流程处理。文案过长时要做截断或自动换行,简单做法是按字符宽度逐字测量,超过容器宽度就折行。
三、对接WooCommerce REST API自动上传
图片生成好之后,接下来要上传到WordPress媒体库并设置为商品特色图。WooCommerce的商品更新接口是PUT /wp-json/wc/v3/products/{id},其中images字段接受一个数组,每个元素包含src和alt。上传图片有两条路径:一是先把图片转成Base64通过WordPress的媒体接口上传,二是把图片放到一个可公开访问的URL上,直接把URL交给WooCommerce,WooCommerce服务端会自己拉取并存入媒体库。后者实现更简单,前者不依赖额外的文件服务。
const axios = require('axios');
require('dotenv').config();
const auth = {
username: process.env.WC_KEY,
password: process.env.WC_SECRET
};
const base = process.env.WC_URL + '/wp-json/wc/v3';
// 方式一:Base64直传WordPress媒体库
async function uploadImage(filePath, fileName) {
const fs = require('fs');
const buf = fs.readFileSync(filePath);
const res = await axios.post(process.env.WC_URL + '/wp-json/wp/v2/media', buf, {
headers: {
'Content-Disposition': 'attachment; filename="' + fileName + '"',
'Content-Type': 'image/jpeg'
},
auth
});
return res.data.id; // 返回媒体附件ID
}
// 更新商品特色图
async function setProductImage(productId, mediaId) {
await axios.put(base + '/products/' + productId, {
images: [{ id: mediaId }]
}, { auth });
}
// 带重试的执行器,网络抖动时自动重试
async function withRetry(fn, times = 3) {
for (let i = 0; i < times; i++) {
try { return await fn(); }
catch (e) {
if (i === times - 1) throw e;
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
}
}
}
// 主流程:取商品列表,逐个生成Mock图并更新
async function run() {
const list = await axios.get(base + '/products', {
params: { per_page: 20, status: 'publish' },
auth
});
for (const p of list.data) {
const out = await generateMock(p.local_image, p.name, '限时特惠', `output/mock-${p.id}.jpg`);
const mediaId = await withRetry(() => uploadImage(out, `mock-${p.id}.jpg`));
await withRetry(() => setProductImage(p.id, mediaId));
console.log('商品', p.id, '更新完成');
}
}
run();这里有几个实战中的注意点。第一,WooCommerce后台的Consumer Key需要在高级设置里开启REST API后生成,权限选Read/Write。第二,如果店铺启用了安全插件或Basic Auth被拦截,可以改用oAuth1.0签名方式。第三,批量更新时不要用Promise.all并发轰炸服务器,WooCommerce背后是PHP加MySQL,大量并发写操作容易把站点打挂,串行或小并发加上重试机制才是稳妥做法。第四,每次更新images数组会整体替换特色图列表,如果商品有多个图库图片,先读取原数组再追加,避免误删旧图。
四、常见问题与优化方向
实际落地时经常会遇到中文字体渲染乱码的问题,原因多半是字体文件没注册成功或者family名字与font属性里写的不一致,排查时可以用ctx.measureText验证文字宽度是否正常。另一个高频问题是WooCommerce拉取远程图片失败,通常是图片URL带了防盗链或者证书不合法,此时改走Base64上传即可绕开。还有商家反馈图片体积过大导致前台加载慢,可以在导出时把quality降到0.85左右,或者额外用sharp生成一份WebP版本,通过picture标签做兼容。
如果SKU规模到了几千级别,建议把一次性脚本改造成任务队列:用bull或简单的p-limit控制并发,把失败任务落库重跑,同时给每张图加缓存判断,商品信息没变化就跳过重新生成,能节省大量CPU时间。进阶玩法还可以接入AI抠图服务自动去除商品白底,让整个流程从原始素材到成品图完全无人值守。整体来看,这套方案的投入产出比很高,一两个小时就能搭起原型,值得每个WooCommerce卖家试试。
Node.jsWooCommerce图片生成修改时间:2026-09-05 07:44:42