topfans/docs/MiniMax 图生图 API 集成方案.md
2026-04-08 01:30:58 +08:00

11 KiB
Raw Permalink Blame History

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

请求头:

{
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
}

请求体:

{
  "model": "image-01",
  "prompt": "描述文本",
  "aspect_ratio": "16:9",
  "subject_reference": [
    {
      "type": "character",
      "image_file": "https://..."
    }
  ],
  "n": 2
}

四、后端设计方案

4.1 配置文件新增 (.env)

# 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)

// 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)

// 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)

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 路由组内添加:

// 资产相关路由(需要认证)
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) - 已存在

// 图生图 API - 前端传递必要参数,后端调用 MiniMax
export function imageGenerationApi(params) {
  return request({
    url: '/api/v1/assets/mints/image/generation',
    method: 'POST',
    data: params
  })
}

5.2 前端调用示例

// 前端只需要传递这些参数,具体的 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 已存在,无需修改

依赖安装

cd backend/services/assetService && go get github.com/nfnt/resize

七、验证方案

  1. 后端启动: 启动 backend/gateway/main.gobackend/services/assetService/main.go
  2. 接口测试: 使用 curl 或 Postman 测试接口
    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