- 修复时间戳不一致问题,统一使用毫秒时间戳 - 添加 CompletedAt 字段 - 添加 JobStatus 枚举类型 - 添加 HTTP 状态码表格 - 添加 SSRF 防护说明 - 添加轮询间隔(3秒)和超时(120秒)建议 - 添加任务清理机制(24h过期) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
273 lines
7.1 KiB
Markdown
273 lines
7.1 KiB
Markdown
# MiniMax 图生图 API 集成设计方案
|
||
|
||
## 概述
|
||
|
||
前端传递参数 → 后端异步调用 MiniMax 图生图 API → 前端轮询任务状态 → 返回结果
|
||
|
||
## 现有架构
|
||
|
||
| 层级 | 技术栈 | 说明 |
|
||
|------|--------|------|
|
||
| 前端 | uni-app (Vue 3) | 使用 `uni.request` 发起请求,已有 loading 页面 |
|
||
| 后端 | Go + Gin | API Gateway 模式,Dubbo RPC 调用微服务 |
|
||
| 微服务 | Go | `assetService` 等独立服务 |
|
||
| 配置 | `.env` 文件 | API keys 等敏感配置 |
|
||
|
||
## API 设计
|
||
|
||
### 1. 创建图生图任务
|
||
|
||
```
|
||
POST /api/v1/assets/mints/image/generation
|
||
```
|
||
|
||
**请求头:**
|
||
```
|
||
Authorization: Bearer <token>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
**请求体:**
|
||
```json
|
||
{
|
||
"model": "image-01",
|
||
"prompt": "描述文本",
|
||
"aspect_ratio": "16:9",
|
||
"subject_reference": [
|
||
{
|
||
"type": "character",
|
||
"image_file": "https://..."
|
||
}
|
||
],
|
||
"n": 2
|
||
}
|
||
```
|
||
|
||
**响应 (202 Accepted):**
|
||
```json
|
||
{
|
||
"code": 202,
|
||
"message": "任务已创建",
|
||
"data": {
|
||
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"status": "PROCESSING",
|
||
"created_at": 1744118400000
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 查询任务状态
|
||
|
||
```
|
||
GET /api/v1/assets/mints/image/generation/:job_id
|
||
```
|
||
|
||
**响应 (PROCESSING):**
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "处理中",
|
||
"data": {
|
||
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"status": "PROCESSING",
|
||
"progress": 50,
|
||
"created_at": 1744118400000
|
||
}
|
||
}
|
||
```
|
||
|
||
**响应 (COMPLETED):**
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"data": {
|
||
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"status": "COMPLETED",
|
||
"progress": 100,
|
||
"images": [
|
||
"https://api.minimaxi.com/v1/images/xxx.png"
|
||
],
|
||
"created_at": 1744118400000,
|
||
"completed_at": 1744118490000
|
||
}
|
||
}
|
||
```
|
||
|
||
**响应 (FAILED):**
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "失败",
|
||
"data": {
|
||
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"status": "FAILED",
|
||
"progress": 0,
|
||
"error_msg": "MiniMax API 调用失败: timeout",
|
||
"created_at": 1744118400000
|
||
}
|
||
}
|
||
```
|
||
|
||
## 数据模型
|
||
|
||
### Job 状态 (内存存储)
|
||
|
||
```go
|
||
// JobStatus 任务状态枚举
|
||
type JobStatus string
|
||
|
||
const (
|
||
StatusPending JobStatus = "PENDING"
|
||
StatusProcessing JobStatus = "PROCESSING"
|
||
StatusCompleted JobStatus = "COMPLETED"
|
||
StatusFailed JobStatus = "FAILED"
|
||
)
|
||
|
||
// ImageGenerationJob 图生图任务
|
||
type ImageGenerationJob struct {
|
||
JobID string `json:"job_id"`
|
||
UserID int64 `json:"user_id"`
|
||
StarID int64 `json:"star_id"`
|
||
Status JobStatus `json:"status"`
|
||
Progress int `json:"progress"` // 0-100
|
||
Images []string `json:"images,omitempty"`
|
||
ErrorMsg string `json:"error_msg,omitempty"`
|
||
Request *ImageGenerationRequest `json:"request,omitempty"`
|
||
CreatedAt int64 `json:"created_at"` //毫秒时间戳
|
||
UpdatedAt int64 `json:"updated_at"`
|
||
CompletedAt int64 `json:"completed_at,omitempty"` // 毫秒时间戳
|
||
}
|
||
```
|
||
|
||
### DTO
|
||
|
||
```go
|
||
// ImageGenerationRequest MiniMax 图生图请求
|
||
type ImageGenerationRequest struct {
|
||
Model string `json:"model" binding:"required"`
|
||
Prompt string `json:"prompt" binding:"required"`
|
||
AspectRatio string `json:"aspect_ratio"`
|
||
SubjectReference []SubjectReference `json:"subject_reference"`
|
||
N int `json:"n"` // 1-4
|
||
}
|
||
|
||
type SubjectReference struct {
|
||
Type string `json:"type"`
|
||
ImageFile string `json:"image_file"` // 必须为有效 URL,需 SSRF 校验
|
||
}
|
||
|
||
// ImageJobResponse 图生图任务响应
|
||
type ImageJobResponse struct {
|
||
JobID string `json:"job_id"`
|
||
Status string `json:"status"`
|
||
Progress int `json:"progress"`
|
||
Images []string `json:"images,omitempty"`
|
||
ErrorMsg string `json:"error_msg,omitempty"`
|
||
CreatedAt int64 `json:"created_at"`
|
||
UpdatedAt int64 `json:"updated_at"`
|
||
CompletedAt int64 `json:"completed_at,omitempty"`
|
||
}
|
||
```
|
||
|
||
## 后端实现
|
||
|
||
### 文件结构
|
||
|
||
```
|
||
backend/
|
||
├── services/assetService/
|
||
│ └── service/
|
||
│ └── minimax_service.go # MiniMax API 转发服务 + 任务管理
|
||
└── gateway/
|
||
├── controller/
|
||
│ └── asset_controller.go # 新增 ImageGeneration, GetImageJob
|
||
├── dto/
|
||
│ └── image_dto.go # 请求/响应 DTO
|
||
└── router/
|
||
└── router.go # 注册路由
|
||
```
|
||
|
||
### 核心逻辑
|
||
|
||
1. **创建任务**: 生成 job_id,存储任务到内存 map,返回 202
|
||
2. **异步处理**: goroutine 调用 MiniMax API,图片压缩(最大边1024px),更新 job 状态
|
||
3. **查询状态**: 从内存读取 job 状态返回
|
||
4. **任务清理**: 后台 goroutine 定期清理超期(>24h)的已完成任务
|
||
|
||
### 图片压缩
|
||
|
||
- 最大边压缩至 1024px,保持宽高比
|
||
- 格式转换: PNG/GIF → JPEG(质量85%)
|
||
- 返回 base64 data URI 格式
|
||
|
||
### SSRF 防护
|
||
|
||
`subject_reference[].image_file` 必须是有效 URL,下载前需校验:
|
||
- 不能是私有 IP (10.x, 172.16-31.x, 192.168.x)
|
||
- 不能是 localhost
|
||
- 不能是内网域名
|
||
- 校验失败则拒绝请求 (400)
|
||
|
||
## 前端改动
|
||
|
||
**generation-loading.vue**:
|
||
- 调用 `POST /generation` 获取 job_id
|
||
- 每 3 秒轮询 `GET /generation/:job_id`
|
||
- 超时时间: 120 秒后显示"生成超时,请重试"
|
||
- 完成后跳转到结果页
|
||
|
||
## HTTP 状态码
|
||
|
||
| 场景 | HTTP 状态码 |
|
||
|------|-------------|
|
||
| 成功 (创建/查询) | 200 / 202 |
|
||
| 参数校验失败 | 400 |
|
||
| 未认证 | 401 |
|
||
| 无权访问 job | 403 |
|
||
| Job 不存在 | 404 |
|
||
| MiniMax API 失败 | 500 |
|
||
|
||
## 错误处理
|
||
|
||
| 场景 | 处理方式 |
|
||
|------|----------|
|
||
| MiniMax API 超时 | 标记 job 为 FAILED,error_msg 包含原因 |
|
||
| 图片压缩失败 | 使用原图,继续处理 |
|
||
| SSRF 校验失败 | 返回 400,"无效的图片URL" |
|
||
| Job 不存在 | 返回 404 |
|
||
| 无权访问 job | 返回 403 |
|
||
|
||
## 文件修改清单
|
||
|
||
| 操作 | 文件路径 | 说明 |
|
||
|------|----------|------|
|
||
| 新增 | `backend/gateway/dto/image_dto.go` | 请求/响应 DTO |
|
||
| 新增 | `backend/services/assetService/service/minimax_service.go` | MiniMax API 转发 + 图片压缩 + 任务管理 |
|
||
| 修改 | `backend/gateway/controller/asset_controller.go` | 新增 ImageGeneration, GetImageJob |
|
||
| 修改 | `backend/gateway/router/router.go` | 注册路由 |
|
||
| 修改 | `frontend/pages/discover/generation-loading.vue` | 改为轮询模式 |
|
||
|
||
## 依赖
|
||
|
||
```bash
|
||
cd backend/services/assetService && go get github.com/nfnt/resize
|
||
```
|
||
|
||
## 验证方案
|
||
|
||
1. 启动后端服务
|
||
2. 获取 JWT token
|
||
3. 测试创建任务:
|
||
```bash
|
||
curl -X POST http://localhost:8080/api/v1/assets/mints/image/generation \
|
||
-H "Authorization: Bearer <token>" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"model":"image-01","prompt":"test","aspect_ratio":"16:9","n":1,"subject_reference":[]}'
|
||
```
|
||
4. 使用返回的 job_id 轮询状态:
|
||
```bash
|
||
curl http://localhost:8080/api/v1/assets/mints/image/generation/<job_id> \
|
||
-H "Authorization: Bearer <token>"
|
||
```
|