电商图片生成的难点通常不在于“能否生成一张好看的图”,而在于能否稳定地产出一组可上架、可复查、可替换的素材。一个商品页面往往需要主图、细节图、场景图、尺寸说明图等多个位置;它们既要维持视觉一致性,又不能改变商品的颜色、结构、包装文字和配件关系。
把这件事直接交给前端按钮和一次模型调用,常会遇到几个工程问题:请求超时后不知道任务是否仍在执行;重试导致重复扣费或重复生成;运营修改了提示词却无法定位图片由哪个版本产生;生成结果混入错误文字、缺失配件或改变商品比例,仍被自动投放到正式素材库。
因此,较可靠的目标不是“自动发布图片”,而是建立一条可追溯的生成流水线:输入被版本化,生成被异步化,结果被校验,最终发布必须经过明确状态转换。本文以模型 API 为可替换依赖,说明如何落地这条链路。若团队通过聚合或中转服务接入模型,例如 HaerAPI(https://www.haerapi.com),也应先依据其当前文档确认认证方式、模型标识、异步语义及数据处理边界,再将其封装在适配器后。
先定义边界:生成的是候选素材
生成服务应只负责制造“候选版本”,不能直接修改商品主图。建议把资产分为三个区域:
source:商品白底图、品牌素材、经过审核的参考图,只读保存。candidate:模型生成的候选图,可删除、可替换,必须关联任务记录。published:已审核、可被店铺或内容系统引用的正式资源。
这三个区域对应风险隔离。即使模型输出异常,影响也被限制在 candidate;只有人工或满足明确规则的审核程序才能把资源提升到 published。
每一个生成任务还应保存不可变快照,而不是仅保存“商品 ID”。最小快照包括:商品 SKU、输入素材的对象存储版本或哈希、场景模板版本、提示词版本、负向约束、请求参数和操作者。后续出现争议时,团队才能回答“这张图为何出现”,而不是依赖日志猜测。
原理:用状态机消化异步与不确定性
图像生成天然存在不确定性:远端请求可能超时,服务端可能已接收任务但客户端没有响应,回调可能重复到达,输出也可能不符合商品事实。状态机用于将这些不确定性变成受控转换。
一个实用的状态集合如下:
DRAFT -> QUEUED -> RUNNING -> GENERATED -> REVIEWING -> PUBLISHED
| | |
v v v
FAILED REJECTED ARCHIVED
其中:
DRAFT用于编辑素材和提示词,不应触发外部调用。QUEUED表示已经生成不可变请求快照,等待工作进程消费。RUNNING表示已向模型供应方提交请求,并记录外部请求标识。GENERATED只代表文件已经获取成功,不代表内容可用。REVIEWING表示候选素材已进入验收队列。PUBLISHED才允许下游页面引用。
状态变化必须使用条件更新。例如,回调处理器仅可把同一任务从 RUNNING 更新为 GENERATED;若任务已经是 FAILED 或 ARCHIVED,则记录事件但拒绝覆盖。这样可以抵抗重复回调和延迟消息。
幂等键是另一项关键设计。客户端创建任务时提供 Idempotency-Key,服务端在“商家、SKU、模板版本、幂等键”维度建立唯一约束。同一请求因网络重试到达多次,也只能得到同一任务,而不会制造一批相似候选图。
数据模型与任务接口
下面用 PostgreSQL 表达核心数据。字段可按现有系统调整,但不要省略输入快照、外部请求标识和版本字段。
create table image_generation_jobs (
id uuid primary key,
sku text not null,
status text not null check (status in (
'DRAFT', 'QUEUED', 'RUNNING', 'GENERATED',
'REVIEWING', 'PUBLISHED', 'FAILED', 'REJECTED', 'ARCHIVED'
)),
idempotency_key text not null,
template_version text not null,
input_snapshot jsonb not null,
provider_name text not null,
provider_request_id text,
output_manifest jsonb,
failure_code text,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (sku, template_version, idempotency_key)
);
input_snapshot 不应只保存一段自然语言。推荐将业务事实和渲染指令分开:
{
"product": {
"sku": "MUG-450-BLK",
"title": "450ml 保温杯",
"must_keep": ["黑色杯身", "银色杯盖", "无手柄"],
"must_not_show": ["品牌标识", "人物", "额外饮品"]
},
"assets": [
{
"uri": "s3://source/MUG-450-BLK/front.png", "sha256": "<source-hash>"}
],
"scene": {
"template": "kitchen-counter-v3",
"ratio": "1:1",
"purpose": "detail"
}
}
结构化数据的价值在于:提示词只是它的渲染结果,审核规则和后续迁移仍可读取事实字段,而不必解析自然语言。
可执行步骤
1. 建立模板与约束词的版本库
把场景模板放在 Git 或受控配置库中。模板变更必须提升版本号,例如从 kitchen-counter-v3 升到 v4,不要原地修改。以下是一个模板文件示例:
id: kitchen-counter-v3
purpose: detail
prompt: |
Create a commercial product image for {
{product.title}}.
Preserve these product facts exactly: {
{product.must_keep}}.
Use a clean kitchen counter setting with soft natural lighting.
Product occupies about 65 percent of the frame.
negative_prompt: |
Do not add logos, readable text, people, handles, duplicate products,
changed colors, or accessories not present in the source image.
output:
aspect_ratio: "1:1"
count: 2
这里的“约 65%”是构图意图,不是可由模型严格保证的测量结果。若业务对占比、文字、尺寸图有硬性要求,应优先用设计工具或确定性图像合成完成,不要把准确性押在生成模型上。
2. 创建任务并投递消息
HTTP 层只负责校验输入、创建记录和投递队列;不要在 Web 请求内等待图像生成结束。以下 Node.js 示例使用伪队列接口,重点是事务边界和幂等处理:
import crypto from "node:crypto";
export async function createJob(req, res, db, queue) {
const idempotencyKey = req.header("Idempotency-Key");
if (!idempotencyKey) {
return res.status(400).json({
error: "Idempotency-Key is required" });
}
const snapshot = buildValidatedSnapshot(req.body);
const jobId = crypto.randomUUID();
const job = await db.transaction(async (tx) => {
const existing = await tx.oneOrNone(
`select * from image_generation_jobs
where sku = $1 and template_version = $2 and idempotency_key = $3`,
[snapshot.product.sku, snapshot.scene.template, idempotencyKey]
);
if (existing) return existing;
const created = await tx.one(
`insert into image_generation_jobs
(id, sku, status, idempotency_key, template_version, input_snapshot, provider_name)
values ($1, $2, 'QUEUED', $3, $4, $5, $6)
returning *`,
[jobId, snapshot.product.sku, idempotencyKey, snapshot.scene.template,
snapshot, process.env.IMAGE_PROVIDER_NAME]
);
await tx.none(
`insert into outbox_events (id, topic, payload)
values ($1, 'image-generation.requested', $2)`,
[crypto.randomUUID(), {
jobId: created.id }]
);
return created;
});
return res.status(202).json({
id: job.id, status: job.status });
}
生产环境中建议采用 outbox 模式:任务记录和待发送事件在同一数据库事务中写入,再由独立投递器转发到消息队列。这样能避免“数据库已创建任务但队列消息丢失”的双写问题。
3. 用供应商适配器隔离调用差异
不要让业务代码依赖某家服务的 URL、字段名或模型标识。定义内部契约,所有外部差异放进适配器。密钥只从环境变量读取:
export async function submitImageGeneration(command) {
const response = await fetch(`${
process.env.IMAGE_API_BASE_URL}/images/generations`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${
process.env.IMAGE_API_KEY}`
},
body: JSON.stringify({
model: process.env.IMAGE_MODEL_ID,
prompt: command.prompt,
negative_prompt: command.negativePrompt,
size: command.size,
reference_images: command.referenceImages
}),
signal: AbortSignal.timeout(30_000)
});
if (!response.ok) {
throw new Error(`provider request failed: ${
response.status}`);
}
return normalizeProviderResponse(await response.json());
}
示例路径和字段仅用于说明适配器形态,并不意味着所有供应方都使用该协议。接入 HaerAPI 或其他服务时,应以当前官方接口文档为准,特别核对是否支持参考图、同步或异步返回、结果下载有效期和内容安全处理方式。
工作进程在提交前用条件更新抢占任务;提交成功后保存外部请求 ID;下载结果后写入 candidate 区域,并将文件哈希、MIME 类型、宽高和来源 URI 写入 output_manifest。下载外部 URL 时还应限制域名、重定向次数、文件类型和文件大小,避免服务端请求伪造风险。
4. 把审核做成明确动作
审核界面至少应展示原始商品素材、候选图、任务快照和差异说明。审核员只做三个明确操作:通过、驳回、归档。驳回必须选择原因,例如“颜色不一致”“出现无关物品”“文字不可用”“构图不符合模板”。这些原因可以反哺模板改进,但不要自动把一次驳回简单拼接进提示词,以免约束持续膨胀且无法解释。
发布动作应复制或登记不可变对象版本,而不是直接引用可能被覆盖的候选 URL。回滚则将商品素材指针切回上一份已发布版本,并记录操作者、时间和原因。
常见问题
超时后能否直接重试?
不能立即假定请求失败。先按业务任务 ID 查询本地状态;若已保存外部请求 ID,应优先查询供应方任务状态。只有确认远端未接收,或适配器定义了可安全重试的错误类型时,才重新提交。任何重试都要沿用同一个业务幂等键。
能否用图像识别自动验收?
可以作为筛选层,例如检查文件是否损坏、比例是否正确、是否存在明显文本或是否与参考图差异过大。但“商品事实一致”往往需要结合类目知识和人工判断。自动评分低于阈值可以直接进入人工复核;自动评分高不应在缺少风险评估时等同于可发布。
为什么不把提示词直接存成一列文本?
文本只能说明当时发送了什么,难以表达哪些字段是商品事实、哪些字段是场景偏好。保留结构化快照后,团队可重渲染提示词、批量查找使用过某个素材的任务,也能在模板升级时进行差异分析。
是否应该保留全部候选图?
取决于数据处理制度、对象存储成本、供应方条款和业务追溯要求。常见做法是为未发布候选图设置生命周期策略,但在删除前保留任务元数据、文件哈希和审核结论。涉及人物、客户上传图片或受监管行业素材时,应由合规与安全团队确定保留期限和访问权限。
总结
批量生成电商套图的核心不是堆叠提示词,而是把模型输出放进可控生产流程:以结构化商品事实约束输入,以幂等键和状态机处理异步失败,以候选区隔离风险,以审核和版本指针控制发布与回滚。
当模型、供应方或模板需要替换时,这套设计还能保留稳定的业务契约。先让每一张图片可定位、可解释、可撤销,再逐步提高自动化比例,通常比追求一次调用就完全正确更符合生产系统的要求。