383 lines
11 KiB
Markdown
383 lines
11 KiB
Markdown
# MiniMax 图生图 API 集成方案
|
||
|
||
## 一、需求概述
|
||
|
||
前端传递参数 → 后端调用 MiniMax 图生图 API → 后端返回结果给前端
|
||
|
||
## 二、现有项目架构
|
||
|
||
| 层级 | 技术栈 | 说明 |
|
||
|------|--------|------|
|
||
| 前端 | uni-app (Vue 3) | 使用 `uni.request` 发起请求 |
|
||
| 后端 | Go + Gin | API Gateway 模式,端口 8080 |
|
||
| 配置 | `.env` 文件 | API keys 等敏感配置 |
|
||
|
||
## 三、MiniMax API 信息
|
||
|
||
**接口地址**: `POST https://api.minimaxi.com/v1/image_generation`
|
||
|
||
**请求头**:
|
||
```json
|
||
{
|
||
"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
|
||
}
|
||
```
|
||
|
||
## 四、后端设计方案
|
||
|
||
### 4.1 配置文件新增 (.env)
|
||
|
||
```bash
|
||
# MiniMax API 配置
|
||
MINIMAX_API_KEY=your_api_key_here
|
||
MINIMAX_API_URL=https://api.minimaxi.com/v1/image_generation
|
||
```
|
||
|
||
### 4.2 新增 DTO (gateway/dto/image_dto.go)
|
||
|
||
```go
|
||
// 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"`
|
||
}
|
||
|
||
type SubjectReference struct {
|
||
Type string `json:"type"`
|
||
ImageFile string `json:"image_file"`
|
||
}
|
||
|
||
// MiniMax 图生图响应
|
||
type ImageGenerationResponse struct {
|
||
Model string `json:"model"`
|
||
Prompt string `json:"prompt"`
|
||
AspectRatio string `json:"aspect_ratio"`
|
||
Images []Image `json:"images"`
|
||
}
|
||
|
||
type Image struct {
|
||
URL string `json:"url"`
|
||
}
|
||
```
|
||
|
||
### 4.3 新增 Controller (gateway/controller/image_controller.go)
|
||
|
||
```go
|
||
// ImageGeneration 图生图 - 调用 MiniMax API
|
||
// @Summary 图生图
|
||
// @Description 前端传递参数,后端调用 MiniMax 图生图 API
|
||
// @Tags assets
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param request body dto.ImageGenerationRequest true "图生图请求"
|
||
// @Success 200 {object} response.Response
|
||
// @Router /api/v1/assets/mints/image/generation [post]
|
||
func (ctrl *AssetController) ImageGeneration(c *gin.Context) {
|
||
var req dto.ImageGenerationRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
response.Error(c, 400, "Invalid request parameters: "+err.Error())
|
||
return
|
||
}
|
||
|
||
// 从 context 获取 userID 和 starID(由认证中间件设置)
|
||
userID := c.GetInt64("userID")
|
||
starID := c.GetInt64("starID")
|
||
|
||
// 调用资产服务的 MiniMax 转发服务
|
||
result, err := ctrl.assetService.CallMiniMaxImageAPI(c.Request.Context(), userID, starID, &req)
|
||
if err != nil {
|
||
response.Error(c, 500, "Image generation failed: "+err.Error())
|
||
return
|
||
}
|
||
|
||
response.Success(c, result)
|
||
}
|
||
```
|
||
|
||
### 4.4 新增 Service (assetService/service/minimax_service.go)
|
||
|
||
```go
|
||
package service
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"encoding/base64"
|
||
"encoding/json"
|
||
"fmt"
|
||
"image"
|
||
"image/gif"
|
||
"image/jpeg"
|
||
"image/png"
|
||
"net/http"
|
||
"time"
|
||
|
||
"github.com/nfnt/resize"
|
||
"github.com/topfans/backend/services/assetService/config"
|
||
dto "github.com/topfans/backend/gateway/dto"
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
// MinimaxService MiniMax API 转发服务接口
|
||
type MinimaxService interface {
|
||
// CallMiniMaxImageAPI 调用 MiniMax 图生图 API
|
||
CallMiniMaxImageAPI(ctx context.Context, userID, starID int64, req *dto.ImageGenerationRequest) (*dto.ImageGenerationResponse, error)
|
||
}
|
||
|
||
// minimaxService MiniMax API 转发服务实现
|
||
type minimaxService struct {
|
||
config *config.AssetConfig
|
||
}
|
||
|
||
// NewMinimaxService 创建 MiniMax 服务实例
|
||
func NewMinimaxService(cfg *config.AssetConfig) MinimaxService {
|
||
return &minimaxService{config: cfg}
|
||
}
|
||
|
||
// CallMiniMaxImageAPI 调用 MiniMax 图生图 API
|
||
func (s *minimaxService) CallMiniMaxImageAPI(ctx context.Context, userID, starID int64, req *dto.ImageGenerationRequest) (*dto.ImageGenerationResponse, error) {
|
||
// 1. 压缩 subject_reference 中的图片
|
||
processedRefs := make([]dto.SubjectReference, len(req.SubjectReference))
|
||
for i, ref := range req.SubjectReference {
|
||
compressedURL, err := s.compressImageIfNeeded(ref.ImageFile)
|
||
if err != nil {
|
||
// 压缩失败时使用原图
|
||
compressedURL = ref.ImageFile
|
||
zap.S().Warnf("Image compression failed, using original: %v", err)
|
||
}
|
||
processedRefs[i] = dto.SubjectReference{
|
||
Type: ref.Type,
|
||
ImageFile: compressedURL,
|
||
}
|
||
}
|
||
|
||
// 2. 构建请求体
|
||
payload := map[string]interface{}{
|
||
"model": req.Model,
|
||
"prompt": req.Prompt,
|
||
"aspect_ratio": req.AspectRatio,
|
||
"subject_reference": processedRefs,
|
||
"n": req.N,
|
||
}
|
||
|
||
// 3. 发送 HTTP POST 请求到 MiniMax
|
||
apiURL := s.config.GetMiniMaxAPIURL()
|
||
apiKey := s.config.GetMiniMaxAPIKey()
|
||
|
||
client := &http.Client{Timeout: 120 * time.Second}
|
||
jsonData, _ := json.Marshal(payload)
|
||
|
||
httpReq, err := http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewBuffer(jsonData))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||
}
|
||
httpReq.Header.Set("Authorization", "Bearer "+apiKey)
|
||
httpReq.Header.Set("Content-Type", "application/json")
|
||
|
||
resp, err := client.Do(httpReq)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to call MiniMax API: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
// 4. 解析响应
|
||
var result dto.ImageGenerationResponse
|
||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||
return nil, fmt.Errorf("failed to parse response: %w", err)
|
||
}
|
||
|
||
return &result, nil
|
||
}
|
||
|
||
// compressImageIfNeeded 下载图片、压缩后返回 base64 编码
|
||
func (s *minimaxService) compressImageIfNeeded(imageURL string) (string, error) {
|
||
// 下载图片
|
||
resp, err := http.Get(imageURL)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
// 读取图片数据
|
||
imgData, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
// 解码图片(自动识别格式)
|
||
img, format, err := image.Decode(bytes.NewReader(imgData))
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
// 计算压缩后的尺寸(最大边 1024px,保持宽高比)
|
||
bounds := img.Bounds()
|
||
maxDim := uint(1024)
|
||
newWidth := uint(bounds.Dx())
|
||
newHeight := uint(bounds.Dy())
|
||
|
||
if newWidth > maxDim || newHeight > maxDim {
|
||
if newWidth > newHeight {
|
||
ratio := float64(maxDim) / float64(newWidth)
|
||
newWidth = maxDim
|
||
newHeight = uint(float64(newHeight) * ratio)
|
||
} else {
|
||
ratio := float64(maxDim) / float64(newHeight)
|
||
newHeight = maxDim
|
||
newWidth = uint(float64(newWidth) * ratio)
|
||
}
|
||
}
|
||
|
||
// 如果图片尺寸没变,不压缩直接返回原图
|
||
if newWidth == uint(bounds.Dx()) && newHeight == uint(bounds.Dy()) {
|
||
return "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(imgData), nil
|
||
}
|
||
|
||
// 缩放图片(Lanczos 算法,质量好)
|
||
resized := resize.Thumbnail(newWidth, newHeight, img, resize.Lanczos)
|
||
|
||
// 重新编码为 JPEG(质量 85%,体积小)
|
||
var buf bytes.Buffer
|
||
switch format {
|
||
case "png":
|
||
err = png.Encode(&buf, resized)
|
||
case "gif":
|
||
err = gif.Encode(&buf, resized, nil)
|
||
default:
|
||
err = jpeg.Encode(&buf, resized, &jpeg.Options{Quality: 85})
|
||
}
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
// 返回 base64 编码(data URI 格式)
|
||
encoded := base64.StdEncoding.EncodeToString(buf.Bytes())
|
||
mimeType := "image/jpeg"
|
||
if format == "png" {
|
||
mimeType = "image/png"
|
||
} else if format == "gif" {
|
||
mimeType = "image/gif"
|
||
}
|
||
return "data:" + mimeType + ";base64," + encoded, nil
|
||
}
|
||
```
|
||
|
||
### 4.5 路由注册 (gateway/router/router.go)
|
||
|
||
在 assets 路由组内添加:
|
||
|
||
```go
|
||
// 资产相关路由(需要认证)
|
||
assets := v1.Group("/assets")
|
||
assets.Use(middleware.AuthMiddleware())
|
||
{
|
||
// ... 现有路由 ...
|
||
assets.POST("/mints/image/generation", assetCtrl.ImageGeneration) // 图生图(MiniMax API)
|
||
}
|
||
```
|
||
|
||
## 五、前端调用方案
|
||
|
||
**架构说明**:前端只与后端 API 通信,不直接调用 MiniMax。后端作为代理调用 MiniMax。
|
||
|
||
### 5.1 前端 API (frontend/utils/api.js) - 已存在
|
||
|
||
```javascript
|
||
// 图生图 API - 前端传递必要参数,后端调用 MiniMax
|
||
export function imageGenerationApi(params) {
|
||
return request({
|
||
url: '/api/v1/assets/mints/image/generation',
|
||
method: 'POST',
|
||
data: params
|
||
})
|
||
}
|
||
```
|
||
|
||
### 5.2 前端调用示例
|
||
|
||
```javascript
|
||
// 前端只需要传递这些参数,具体的 MiniMax API 调用由后端完成
|
||
imageGenerationApi({
|
||
prompt: '描述文字',
|
||
aspect_ratio: '16:9',
|
||
subject_reference: [
|
||
{
|
||
type: 'character',
|
||
image_file: '用户选择的图片URL'
|
||
}
|
||
],
|
||
n: 2
|
||
}).then(res => {
|
||
// res.data.images 是 MiniMax 返回的图片 URL 列表
|
||
console.log('生成的图片:', res.data.images)
|
||
})
|
||
```
|
||
|
||
### 5.3 数据流向
|
||
|
||
```
|
||
前端参数 {prompt, aspect_ratio, subject_reference, n}
|
||
↓ POST /api/v1/assets/mints/image/generation
|
||
后端接收参数 → 调用 assetService 的 MiniMax 转发服务 → 调用 MiniMax API
|
||
↓
|
||
后端获取响应 → 直接透传 images 数组 → 返回给前端
|
||
↓
|
||
前端收到 {code: 200, data: {images: [...]}}
|
||
```
|
||
|
||
## 六、文件修改清单
|
||
|
||
| 操作 | 文件路径 | 说明 |
|
||
|------|----------|------|
|
||
| 修改 | `backend/.env` | 添加 MiniMax 配置 |
|
||
| 新增 | `backend/gateway/dto/image_dto.go` | 请求/响应 DTO |
|
||
| 新增 | `backend/services/assetService/service/minimax_service.go` | MiniMax API 转发 + 图片压缩 |
|
||
| 新增 | `backend/services/assetService/config/minimax_config.go` | MiniMax 配置读取 |
|
||
| 修改 | `backend/gateway/controller/asset_controller.go` | 新增 ImageGeneration 处理器 |
|
||
| 修改 | `backend/gateway/router/router.go` | 注册 `/mints/image/generation` 路由 |
|
||
| 修改 | `frontend/utils/api.js` | 已存在,无需修改 |
|
||
|
||
### 依赖安装
|
||
```bash
|
||
cd backend/services/assetService && go get github.com/nfnt/resize
|
||
```
|
||
|
||
## 七、验证方案
|
||
|
||
1. **后端启动**: 启动 `backend/gateway/main.go` 和 `backend/services/assetService/main.go`
|
||
2. **接口测试**: 使用 curl 或 Postman 测试接口
|
||
```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":[]}'
|
||
```
|
||
3. **前端测试**: 在页面中调用 `imageGenerationApi()` 并展示返回的图片
|
||
|
||
## 八、已确认事项
|
||
|
||
- 图片直接返回 MiniMax 的 URL,不经过 OSS
|
||
- subject_reference.image_file 由前端自定义传递给后端(用户选择的自定义图片)
|
||
- 转发服务位于 `assetService` 中,不在 Gateway
|