diff --git a/docs/superpowers/specs/2026-04-07-minimax-image-generation-design.md b/docs/superpowers/specs/2026-04-07-minimax-image-generation-design.md index bf8a2f1..03006e5 100644 --- a/docs/superpowers/specs/2026-04-07-minimax-image-generation-design.md +++ b/docs/superpowers/specs/2026-04-07-minimax-image-generation-design.md @@ -51,7 +51,7 @@ Content-Type: application/json "data": { "job_id": "550e8400-e29b-41d4-a716-446655440000", "status": "PROCESSING", - "created_at": "2026-04-07T12:00:00Z" + "created_at": 1744118400000 } } ``` @@ -71,7 +71,7 @@ GET /api/v1/assets/mints/image/generation/:job_id "job_id": "550e8400-e29b-41d4-a716-446655440000", "status": "PROCESSING", "progress": 50, - "created_at": "2026-04-07T12:00:00Z" + "created_at": 1744118400000 } } ``` @@ -88,8 +88,8 @@ GET /api/v1/assets/mints/image/generation/:job_id "images": [ "https://api.minimaxi.com/v1/images/xxx.png" ], - "created_at": "2026-04-07T12:00:00Z", - "completed_at": "2026-04-07T12:01:30Z" + "created_at": 1744118400000, + "completed_at": 1744118490000 } } ``` @@ -104,7 +104,7 @@ GET /api/v1/assets/mints/image/generation/:job_id "status": "FAILED", "progress": 0, "error_msg": "MiniMax API 调用失败: timeout", - "created_at": "2026-04-07T12:00:00Z" + "created_at": 1744118400000 } } ``` @@ -114,18 +114,29 @@ GET /api/v1/assets/mints/image/generation/:job_id ### 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 string `json:"status"` // PENDING, PROCESSING, COMPLETED, FAILED - Progress int `json:"progress"` // 0-100 - Images []string `json:"images,omitempty"` - ErrorMsg string `json:"error_msg,omitempty"` - Request *ImageGenerationRequest `json:"request,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + 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"` // 毫秒时间戳 } ``` @@ -138,23 +149,24 @@ type ImageGenerationRequest struct { Prompt string `json:"prompt" binding:"required"` AspectRatio string `json:"aspect_ratio"` SubjectReference []SubjectReference `json:"subject_reference"` - N int `json:"n"` + N int `json:"n"` // 1-4 } type SubjectReference struct { Type string `json:"type"` - ImageFile string `json:"image_file"` + 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"` + 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"` } ``` @@ -166,7 +178,7 @@ type ImageJobResponse struct { backend/ ├── services/assetService/ │ └── service/ -│ └── minimax_service.go # MiniMax API 转发服务 +│ └── minimax_service.go # MiniMax API 转发服务 + 任务管理 └── gateway/ ├── controller/ │ └── asset_controller.go # 新增 ImageGeneration, GetImageJob @@ -178,16 +190,43 @@ backend/ ### 核心逻辑 -1. **创建任务**: 生成 job_id,存储任务到内存,返回 202 -2. **异步处理**: goroutine 调用 MiniMax API,图片压缩,更新 job 状态 +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 -- 轮询 `GET /generation/:job_id` 直到 `status === 'COMPLETED'` 或 `'FAILED'` -- 进度可通过 `progress` 字段模拟更新 +- 每 3 秒轮询 `GET /generation/:job_id` +- 超时时间: 120 秒后显示"生成超时,请重试" +- 完成后跳转到结果页 + +## HTTP 状态码 + +| 场景 | HTTP 状态码 | +|------|-------------| +| 成功 (创建/查询) | 200 / 202 | +| 参数校验失败 | 400 | +| 未认证 | 401 | +| 无权访问 job | 403 | +| Job 不存在 | 404 | +| MiniMax API 失败 | 500 | ## 错误处理 @@ -195,6 +234,7 @@ backend/ |------|----------| | MiniMax API 超时 | 标记 job 为 FAILED,error_msg 包含原因 | | 图片压缩失败 | 使用原图,继续处理 | +| SSRF 校验失败 | 返回 400,"无效的图片URL" | | Job 不存在 | 返回 404 | | 无权访问 job | 返回 403 |