一、整体架构
假设用户上传一张照片,输入提示词“把照片变成动漫风格”,要求生成 4 张图片。
flowchart TDA["① 前端提交请求:原图、提示词、生成参数"]B["② 后端上传原图到 MinIO,获取 object_key"]C["③ 创建生成任务并写入数据库,状态为 PENDING"]D["④ 异步调用 AI 模型,状态更新为 PROCESSING"]E["⑤ 生成图片并上传 MinIO,每张图片生成一个 object_key"]F["⑥ 保存图片记录,更新任务状态为 SUCCESS 或 FAILED"]
A --> BB --> CC --> DD --> EE --> F
M[("MinIO 对象存储")]DB[("MySQL / PostgreSQL")]
B -. 保存原图 .-> MC -. 保存任务 .-> DBE -. 保存生成图片 .-> MF -. 保存结果和任务状态 .-> DB二、数据库具体怎么设计?
我会设计三张核心表,分别管理图片资源、生成任务和生成结果。
1. 图片资源表 image_asset
负责管理 MinIO 中的图片。
| 字段 | 作用 |
|---|---|
id | 图片资源 ID |
user_id | 所属用户 |
bucket_name | MinIO 存储桶名称 |
object_key | 图片在 MinIO 中的唯一标识 |
mime_type | 图片格式 |
file_size | 图片大小 |
width、height | 图片尺寸 |
created_at | 创建时间 |
例如,数据库保存:
id: 10001bucket_name: ai-imagesobject_key: users/2001/original/abc.png真正的图片文件在 MinIO 里,数据库只记录如何找到它。
2. 生成任务表 image_generation_task
负责管理一次图生图任务。
| 字段 | 作用 |
|---|---|
id | 任务 ID |
task_no | 业务任务编号 |
user_id | 发起任务的用户 |
source_image_id | 原图 ID,关联图片资源表 |
prompt | 用户提示词 |
model_name | 使用的模型 |
model_params | 模型参数,使用 JSON 存储 |
status | 任务状态 |
error_code、error_message | 失败信息 |
created_at、finished_at | 创建和结束时间 |
例如:
task_no: TASK_5001source_image_id: 10001prompt: 把照片变成动漫风格model_name: image-model-v1status: PROCESSING这里用 source_image_id 关联图片资源表,而不是直接存一大串图片数据或依赖临时 URL。
3. 生成结果表 image_generation_result
负责记录每次任务生成的图片。
| 字段 | 作用 |
|---|---|
id | 结果记录 ID |
task_id | 对应的任务 ID |
image_id | 生成图片 ID,关联图片资源表 |
result_index | 第几张结果图 |
created_at | 创建时间 |
如果任务 TASK_5001 生成 4 张图片,就写入 4 条结果记录,每条关联一个 image_id。
这样就形成了:
生成任务表
TASK_5001 · 原图 ID = 10001
生成结果表
4 条记录,分别关联 4 个 image_id
图片资源表 → MinIO
根据各自的 object_key 找到实际图片文件
三、任务执行过程中需要考虑什么?
这是面试官很容易继续追问的部分。
第一,为什么要异步处理?
图片生成耗时可能比较长。如果一直让 HTTP 请求等待,容易超时。因此可以创建任务后立即返回 task_no,通过消息队列交给后台 Worker 处理,前端再通过轮询或 WebSocket 查询任务进度。
第二,模型调用失败怎么办?
任务状态可以设计为:
PENDING → PROCESSING → SUCCESS / FAILED如果调用超时,不能直接认定模型没有执行成功。应先查询模型任务状态;如果模型支持幂等请求,还可以使用稳定的请求标识重试,避免重复生成。
第三,上传 MinIO 和写数据库不在同一个事务中怎么办?
MinIO 和 MySQL 通常不能通过普通数据库事务实现原子提交。因此要考虑失败补偿:
- 图片上传成功、数据库写入失败:清理孤立文件,或者由定时任务扫描回收。
- 图片生成成功、结果记录失败:保留任务信息并重试保存结果,避免重复调用模型。
- 任务失败:记录错误原因和重试次数,按策略重试或标记失败。
第四,如果涉及扣费怎么办?
单独设计计费流水,并通过幂等键避免重复扣费。不要仅凭任务接口超时就再次扣款,也不要简单地把扣费操作和图片生成状态混在一张表里。
四、完整面试口播
下面这段可以直接作为面试回答来练习。
如果让我设计图生图业务的数据库,我会从图片存储、任务管理和结果管理三个方面考虑。
首先,图片本身会放在 MinIO 这样的对象存储中,数据库不直接存储图片二进制,而是保存图片 ID、Bucket、Object Key、文件类型和大小等元数据。
数据库方面,我会设计三张核心表。
第一张是图片资源表,负责管理原图和生成图在 MinIO 中的存储位置。
第二张是生成任务表,记录用户 ID、原图 ID、提示词、模型名称、模型参数、任务状态和错误信息等。模型参数可以使用 JSON 存储,以适应不同模型的参数差异。
第三张是生成结果表,记录任务 ID 和生成图片 ID 的关联。因为一次图生图任务可能生成多张图片,所以任务和结果是一对多的关系。
整个流程是:用户上传原图,后端将图片保存到 MinIO,并在数据库记录图片信息;然后创建生成任务,将任务交给后台 Worker 异步处理;Worker 调用 AI 模型,拿到生成结果后将图片上传到 MinIO,再保存图片资源和结果记录,最后更新任务状态。
另外,我还会考虑异常处理。如果模型调用超时,需要先判断模型是否已经执行成功,再决定是否重试;如果 MinIO 上传成功但数据库写入失败,需要进行补偿或清理,避免产生孤立文件。如果涉及用户扣费,还需要独立的计费流水和幂等机制,避免重复扣费。
这样既能管理图片资源,也能支持多图生成、任务查询、失败重试和后续扩展。
你只需要记住这条主线:
用户上传图片 → MinIO 保存文件 → 图片表记录元数据 → 任务表记录生成请求 → Worker 调用模型 → MinIO 保存结果 → 结果表关联图片 → 更新任务状态。
相关文章
- 幂等键(Idempotency Key):涉及扣费时如何避免重复扣款。
- 场景题:扣费请求发生网络超时,如何设计?:异步任务中的超时、补偿与对账。