作者:架构源启-12年OTA公司资深程序员
技术栈:Spring Boot 3.5.9 + Spring AI 1.1.4 + GPT-IMAGE-2 + CogView
前置知识:可查看前面的文章


在这里插入图片描述

📖 前言

在上一篇文章中,我们深入学习了 Spring AI 的核心概念。今天,我们将进入一个更 exciting 的领域——AI 图片生成

想象一下:

  • 🎨 输入一段文字,AI 自动生成精美的图片
  • 🖼️ 上传一张照片,AI 帮你修改风格、去除背景
  • 🏨 酒店营销人员可以快速生成宣传海报
  • 📱 内容创作者可以批量生产配图

这些不再是梦想,而是可以通过 Spring AI 轻松实现的功能!

本文你将学到

✅ ImageModel 接口详解与使用
✅ 文生图(Text-to-Image)和图生图(Image-to-Image)实现
✅ Base64 vs URL 返回格式处理
✅ 实战:AI 头像生成器

为什么需要图片生成?

应用场景

  • 💼 企业营销:快速生成广告素材、产品海报
  • 📝 内容创作:文章配图、社交媒体图片
  • 🎮 游戏开发:角色设计、场景概念图
  • 🏨 酒店行业:房间效果图、活动宣传图
  • 👤 个人使用:头像生成、壁纸制作

准备好了吗?让我们开始吧!🚀


🎯 一、图片生成模型对比

1.1 主流模型概览(2026年最新版)

模型 开源 中文支持 艺术感 可控性 速度 成本(参考) 最佳场景
Midjourney V8 ⭐⭐⭐⭐⭐ $10–30 / 月 艺术创作 / 海报设计 / 高质感图像
GPT-Image 2 ⭐⭐⭐⭐ $0.02 / 张 复杂设计 / 文字图 / 多模态理解
SD 3 / SDXL ⭐⭐⭐ 极高 免费(本地) 定制化需求 / 私有化部署 / 批量生成
FLUX ⭐⭐⭐⭐ 极快 免费(本地) 高速批量生成 / 创意快速迭代
Seedream 5.0 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ 极快 ¥0.5–1 / 张 中文商业应用 / 营销素材生成
WAN 2.7 ⭐⭐⭐⭐⭐ ⭐⭐⭐ ¥0.2 / 张 低成本批量生成 / 中文配图
GLM-Image 中-强 ⭐⭐⭐ 免费(本地)/ ¥0.1(API) 中文汉字生成 / 科普插图 / 私有化部署
Gemini Flash Image 弱-中 ⭐⭐⭐ 极快 $0.067 / 张(免费额度高) 高速写实 / 多模态编辑 / 英文场景
CogView-4 ⭐⭐⭐⭐⭐ ⭐⭐⭐ ¥0.02 / 张 国内应用 / 快速原型

说明

  • ⭐ 数量代表能力强度(5星为最强)
  • 开源模型需要自备GPU资源
  • 成本仅供参考,实际以官方定价为准
  • GLM-Image 在中文汉字渲染方面表现优异

1.2 选择建议(2026年更新)

推荐使用策略

中文商业应用 → Seedream 5.0(中文最优、速度快)
    ↓
低成本批量 → WAN 2.7(¥0.2/张、性价比高)
    ↓
艺术创作 → Midjourney V8(顶级艺术感)
    ↓
复杂设计/文字图 → GPT-Image 2(理解力强)
    ↓
中文汉字/科普 → GLM-Image(汉字渲染优秀)
    ↓
高速写实/多模态 → Gemini Flash Image(极快、编辑能力强)
    ↓
私有化部署 → SD 3/SDXL、FLUX 或 GLM-Image(开源免费)
    ↓
国内快速原型 → CogView-4(便宜快速)

在这里插入图片描述

选型决策树(Java实现)

package com.shun.springai.service;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * AI图像生成模型路由服务
 * 根据需求自动选择最优模型
 */
@Slf4j
@Service
public class ImageModelRouter {
    
    /**
     * 支持的模型枚举
     */
    public enum ImageModel {
        MIDJOURNEY_V8("Midjourney V8", "艺术创作"),
        GPT_IMAGE_2("GPT-Image 2", "复杂设计"),
        SD_3("SD 3/SDXL", "开源定制"),
        FLUX("FLUX", "高速批量"),
        SEEDREAM_5("Seedream 5.0", "中文商业"),
        WAN_2_7("WAN 2.7", "低成本批量"),
        GLM_IMAGE("GLM-Image", "中文汉字"),
        GEMINI_FLASH("Gemini Flash Image", "高速写实"),
        COGVIEW_4("CogView-4", "国内通用");
        
        private final String name;
        private final String description;
        
        ImageModel(String name, String description) {
            this.name = name;
            this.description = description;
        }
        
        public String getName() {
            return name;
        }
        
        public String getDescription() {
            return description;
        }
    }
    
    /**
     * 图像生成需求
     */
    @Data
    public static class ImageGenerationRequest {
        private boolean hasChineseText;      // 是否包含中文文字
        private String category;             // 类别(educational/commercial/artistic等)
        private String language;             // 语言(chinese/english)
        private String quality;              // 质量要求(commercial/high/standard)
        private String budget;               // 预算(low/medium/high)
        private String volume;               // 批量(high/medium/low)
        private String style;                // 风格(artistic/realistic/cartoon等)
        private boolean needText;            // 是否需要文字
        private String complexity;           // 复杂度(high/medium/low)
        private String speed;                // 速度要求(ultra_fast/fast/normal)
        private String task;                 // 任务类型(editing/generation/enhancement)
        private boolean privacy;             // 是否要求私有化部署
    }
    
    /**
     * 根据需求选择最优模型
     * 
     * @param requirements 生成需求
     * @return 推荐的模型
     */
    public ImageModel selectModel(ImageGenerationRequest requirements) {
        
        // 1. 中文汉字或科普内容 - 优先GLM-Image
        if (requirements.isHasChineseText() || "educational".equals(requirements.getCategory())) {
            log.info("选择GLM-Image:中文汉字渲染最佳");
            return ImageModel.GLM_IMAGE;
        }
        
        // 2. 中文商业应用 - Seedream 5.0
        if ("chinese".equals(requirements.getLanguage()) && 
            "commercial".equals(requirements.getQuality())) {
            log.info("选择Seedream 5.0:中文商业首选");
            return ImageModel.SEEDREAM_5;
        }
        
        // 3. 低成本批量生成 - WAN 2.7
        if ("low".equals(requirements.getBudget()) && 
            "high".equals(requirements.getVolume())) {
            log.info("选择WAN 2.7:低成本批量");
            return ImageModel.WAN_2_7;
        }
        
        // 4. 艺术创作 - Midjourney V8
        if ("artistic".equals(requirements.getStyle())) {
            log.info("选择Midjourney V8:艺术创作");
            return ImageModel.MIDJOURNEY_V8;
        }
        
        // 5. 复杂设计或需要文字 - GPT-Image 2
        if (requirements.isNeedText() || "high".equals(requirements.getComplexity())) {
            log.info("选择GPT-Image 2:复杂设计");
            return ImageModel.GPT_IMAGE_2;
        }
        
        // 6. 高速写实或多模态编辑 - Gemini Flash Image
        if ("ultra_fast".equals(requirements.getSpeed()) || 
            "editing".equals(requirements.getTask())) {
            log.info("选择Gemini Flash Image:极速+编辑");
            return ImageModel.GEMINI_FLASH;
        }
        
        // 7. 私有化部署 - 根据具体需求选择
        if (requirements.isPrivacy()) {
            if ("fast".equals(requirements.getSpeed())) {
                log.info("选择FLUX:快速开源方案");
                return ImageModel.FLUX;
            } else if (requirements.isHasChineseText()) {
                log.info("选择GLM-Image:中文开源方案");
                return ImageModel.GLM_IMAGE;
            } else {
                log.info("选择SD 3:标准开源方案");
                return ImageModel.SD_3;
            }
        }
        
        // 8. 默认国内方案 - CogView-4
        log.info("选择CogView-4:通用国内方案");
        return ImageModel.COGVIEW_4;
    }
    
    /**
     * 获取模型的配置信息
     */
    public String getModelConfig(ImageModel model) {
        return switch (model) {
            case MIDJOURNEY_V8 -> "model=midjourney-v8, cost=$10-30/month";
            case GPT_IMAGE_2 -> "model=gpt-image-2, cost=$0.02/image";
            case SD_3 -> "model=sd-3, local=true, cost=free";
            case FLUX -> "model=flux, local=true, cost=free, speed=ultra-fast";
            case SEEDREAM_5 -> "model=seedream-5.0, cost=¥0.5-1/image";
            case WAN_2_7 -> "model=wan-2.7, cost=¥0.2/image";
            case GLM_IMAGE -> "model=glm-image, local=true, cost=free or ¥0.1/image";
            case GEMINI_FLASH -> "model=gemini-flash-image, cost=$0.067/image";
            case COGVIEW_4 -> "model=cogview-4, cost=¥0.02/image";
        };
    }
}

使用示例

@RestController
@RequestMapping("/image")
public class ImageGenerationController {
    
    @Autowired
    private ImageModelRouter modelRouter;
    
    @PostMapping("/generate-smart")
    public Map<String, Object> generateSmart(@RequestBody ImageGenerationRequest request) {
        
        // 1. 自动选择模型
        ImageModelRouter.ImageModel selectedModel = modelRouter.selectModel(request);
        
        // 2. 获取模型配置
        String config = modelRouter.getModelConfig(selectedModel);
        
        log.info("为用户选择的模型: {} - {}", selectedModel.getName(), config);
        
        // 3. 根据选择的模型调用相应的服务
        String imageUrl = generateWithSelectedModel(selectedModel, request);
        
        Map<String, Object> result = new HashMap<>();
        result.put("success", true);
        result.put("selectedModel", selectedModel.getName());
        result.put("modelDescription", selectedModel.getDescription());
        result.put("config", config);
        result.put("imageUrl", imageUrl);
        
        return result;
    }
    
    private String generateWithSelectedModel(ImageModelRouter.ImageModel model, 
                                            ImageGenerationRequest request) {
        // 根据模型调用不同的生成服务
        return switch (model) {
            case GLM_IMAGE -> glmImageService.generate(request);
            case SEEDREAM_5 -> seedreamService.generate(request);
            case WAN_2_7 -> wanService.generate(request);
            case GEMINI_FLASH -> geminiService.generate(request);
            case COGVIEW_4 -> cogviewService.generate(request);
            // ... 其他模型
            default -> throw new IllegalArgumentException("不支持的模型: " + model);
        };
    }
}

测试示例

// 测试1:中文汉字海报
ImageGenerationRequest req1 = new ImageGenerationRequest();
req1.setHasChineseText(true);
req1.setCategory("commercial");
ImageModel model1 = router.selectModel(req1);
// 输出: GLM-Image(中文汉字渲染最佳)

// 测试2:艺术创作
ImageGenerationRequest req2 = new ImageGenerationRequest();
req2.setStyle("artistic");
ImageModel model2 = router.selectModel(req2);
// 输出: Midjourney V8(艺术创作)

// 测试3:低成本批量
ImageGenerationRequest req3 = new ImageGenerationRequest();
req3.setBudget("low");
req3.setVolume("high");
ImageModel model3 = router.selectModel(req3);
// 输出: WAN 2.7(低成本批量)

// 测试4:私有化部署+中文
ImageGenerationRequest req4 = new ImageGenerationRequest();
req4.setPrivacy(true);
req4.setHasChineseText(true);
ImageModel model4 = router.selectModel(req4);
// 输出: GLM-Image(中文开源方案)

🔧 二、环境准备

2.1 添加依赖

pom.xml 中添加:

<dependencies>
    <!-- Spring AI OpenAI Starter(包含图片生成功能) -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-openai</artifactId>
    </dependency>
    
    <!-- 七牛云 SDK(可选,用于云存储) -->
    <dependency>
        <groupId>com.qiniu</groupId>
        <artifactId>qiniu-java-sdk</artifactId>
        <version>7.15.0</version>
    </dependency>
</dependencies>

2.2 配置文件

OpenAI 配置
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      base-url: https://api.openai.com
      
      # 图片生成配置
      image:
        options:
          model: dall-e-3          # 或 gpt-image
          size: 1024x1024          # 图片尺寸
          quality: standard        # standard 或 hd
          style: vivid             # vivid 或 natural
          response-format: url     # url 或 b64_json
        generations-path: /v1/images/generations
智谱 AI 配置
spring:
  ai:
    zhipuai:
      api-key: ${ZHIPU_API_KEY}
      image:
        options:
          model: cogview-3
          size: 1024x1024
Gemini 配置
spring:
  ai:
    gemini:
      api-key: ${GEMINI_API_KEY}
      image:
        options:
          model: gemini-2.5-flash-image

2.3 获取 API Key

智谱 AI API Key
  1. 访问 智谱开放平台
  2. 注册并完成实名认证
  3. 进入控制台 -> API Keys
  4. 创建 API Key

费用:CogView-3 约 ¥0.02/张

Gemini API Key
  1. 访问 Google AI Studio
  2. 使用 Google 账号登录(需要科学上网)
  3. 创建 API Key

费用:免费额度(无需信用卡):
Gemini 1.5 Flash:每天 1500 次请求
Gemini 1.5 Pro:每分钟 5 次请求
足够开发 / 测试用,含 Gemini Flash Image 图像生成


💻 三、基础图片生成

3.1 最简单的图片生成

@RestController
@RequestMapping("/image")
public class ImageGenerationController {
    
    private final ImageModel imageModel;
    
    public ImageGenerationController(ImageModel imageModel) {
        this.imageModel = imageModel;
    }
    
    @PostMapping("/generate")
    public Map<String, Object> generateImage(@RequestParam String prompt) {
        // 创建图片提示词
        ImagePrompt imagePrompt = new ImagePrompt(prompt);
        
        // 调用模型生成图片
        ImageResponse response = imageModel.call(imagePrompt);
        
        // 提取结果
        Map<String, Object> result = new HashMap<>();
        if (response != null && response.getResults() != null) {
            var output = response.getResult().getOutput();
            
            // 判断返回格式
            if (output.getUrl() != null && !output.getUrl().isEmpty()) {
                result.put("imageUrl", output.getUrl());
                result.put("imageType", "url");
            } else if (output.getB64Json() != null && !output.getB64Json().isEmpty()) {
                result.put("imageBase64", output.getB64Json());
                result.put("imageType", "base64");
            }
        }
        
        result.put("success", true);
        result.put("prompt", prompt);
        
        return result;
    }
}

测试

curl -X POST "http://localhost:8080/image/generate" \
  -d "prompt=烟雨江南古镇,白墙黛瓦流水,融合中国水墨晕染 + 赛博朋克霓虹光轨,水面反射蓝紫荧光,雾气氤氲,写意留白,电影级光影,16:9宽屏,超高清"

响应

{
  "success": true,
  "imageUrl": "xxxx",
  "imageType": "url",
  "prompt": "一烟雨江南古镇,白墙黛瓦流水,融合中国水墨晕染 + 赛博朋克霓虹光轨,水面反射蓝紫荧光,雾气氤氲,写意留白,电影级光影,16:9宽屏,超高清"
}

GTP-IMAGE-2的效果图
在这里插入图片描述

CogView-3-Flash的效果图
在这里插入图片描述

3.2 带选项的图片生成

@PostMapping("/generate-with-options")
public Map<String, Object> generateWithOptions(
        @RequestParam String prompt,
        @RequestParam(required = false, defaultValue = "1024x1024") String size,
        @RequestParam(required = false, defaultValue = "1") Integer n,
        @RequestParam(required = false, defaultValue = "standard") String quality) {
    
    // 解析尺寸
    String[] dimensions = size.split("x");
    int width = Integer.parseInt(dimensions[0]);
    int height = dimensions.length > 1 ? Integer.parseInt(dimensions[1]) : width;
    
    // 构建选项
    OpenAiImageOptions options = OpenAiImageOptions.builder()
            .model("dall-e-3")
            .width(width)
            .height(height)
            .N(n)
            .quality(quality)  // standard 或 hd
            .style("vivid")    // vivid 或 natural
            .responseFormat("url")  // url 或 b64_json
            .build();
    
    // 创建提示词
    ImagePrompt imagePrompt = new ImagePrompt(prompt, options);
    
    // 生成图片
    ImageResponse response = imageModel.call(imagePrompt);
    
    // 处理结果
    List<String> imageUrls = new ArrayList<>();
    if (response != null && response.getResults() != null) {
        for (var result : response.getResults()) {
            var output = result.getOutput();
            if (output.getUrl() != null) {
                imageUrls.add(output.getUrl());
            }
        }
    }
    
    Map<String, Object> result = new HashMap<>();
    result.put("success", true);
    result.put("imageUrls", imageUrls);
    result.put("count", imageUrls.size());
    result.put("prompt", prompt);
    
    return result;
}

测试

curl -X POST "http://localhost:8080/image/generate-with-options" \
  -d "prompt=古风汉服女子背影,多重曝光融合远山云雾、水墨山水、孤舟飞鸟,画面层叠交融,国风写意留白,浅灰青色调,柔光朦胧,中式美学,正方形构图" \
  -d "size=1792x1024" \
  -d "n=2" \
  -d "quality=hd"

在这里插入图片描述


🎨 四、高级功能实现

4.1 图片本地存储

生成的图片 URL 通常有时效性(如 24 小时后过期),我们需要将图片保存到本地。

创建存储服务
@Service
@Slf4j
public class ImageStorageService {
    
    private final String uploadDir = "uploaded-images";
    
    @PostConstruct
    public void init() {
        // 创建上传目录
        File dir = new File(uploadDir);
        if (!dir.exists()) {
            dir.mkdirs();
            log.info("创建图片存储目录: {}", dir.getAbsolutePath());
        }
    }
    
    /**
     * 从 URL 下载图片并保存到本地
     */
    public String saveImageFromUrl(String imageUrl) throws IOException {
        // 生成唯一文件名
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
        String uuid = UUID.randomUUID().toString().substring(0, 8);
        String fileName = String.format("image_%s_%s.png", timestamp, uuid);
        String filePath = uploadDir + "/" + fileName;
        
        // 下载图片
        URL url = new URL(imageUrl);
        try (InputStream in = url.openStream();
             FileOutputStream out = new FileOutputStream(filePath)) {
            
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
        }
        
        log.info("图片已保存: {}", filePath);
        return "/" + filePath;
    }
    
    /**
     * 从 Base64 保存图片
     */
    public String saveImageFromBase64(String base64Data) throws IOException {
        // 生成唯一文件名
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
        String uuid = UUID.randomUUID().toString().substring(0, 8);
        String fileName = String.format("image_%s_%s.png", timestamp, uuid);
        String filePath = uploadDir + "/" + fileName;
        
        // 解码 Base64
        byte[] imageBytes = Base64.getDecoder().decode(base64Data);
        
        // 保存文件
        try (FileOutputStream out = new FileOutputStream(filePath)) {
            out.write(imageBytes);
        }
        
        log.info("图片已保存: {}", filePath);
        return "/" + filePath;
    }
}
配置静态资源访问
@Configuration
public class ResourceConfig implements WebMvcConfigurer {
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 映射 /uploaded-images/** 到本地目录
        registry.addResourceHandler("/uploaded-images/**")
                .addResourceLocations("file:uploaded-images/");
    }
}
集成到控制器
@PostMapping("/generate-and-save")
public Map<String, Object> generateAndSave(@RequestParam String prompt) {
    try {
        // 1. 生成图片
        ImagePrompt imagePrompt = new ImagePrompt(prompt);
        ImageResponse response = imageModel.call(imagePrompt);
        
        var output = response.getResult().getOutput();
        String localPath;
        
        // 2. 根据返回格式保存
        if (output.getUrl() != null && !output.getUrl().isEmpty()) {
            // 从 URL 下载并保存
            localPath = storageService.saveImageFromUrl(output.getUrl());
        } else if (output.getB64Json() != null && !output.getB64Json().isEmpty()) {
            // 从 Base64 保存
            localPath = storageService.saveImageFromBase64(output.getB64Json());
        } else {
            throw new RuntimeException("无法获取图片数据");
        }
        
        // 3. 返回结果
        Map<String, Object> result = new HashMap<>();
        result.put("success", true);
        result.put("imageUrl", localPath);
        result.put("savedImagePath", localPath);
        result.put("message", "图片已生成并保存到本地");
        result.put("prompt", prompt);
        
        return result;
        
    } catch (Exception e) {
        log.error("图片生成失败", e);
        Map<String, Object> error = new HashMap<>();
        error.put("success", false);
        error.put("message", "图片生成失败: " + e.getMessage());
        return error;
    }
}

测试

curl -X POST "http://localhost:8080/image/generate-and-save" \
  -d "prompt=一只可爱的柯基犬在草地上奔跑"

响应

{
  "success": true,
  "imageUrl": "/uploaded-images/image_20260504_143022_a1b2c3d4.png",
  "savedImagePath": "/uploaded-images/image_20260504_143022_a1b2c3d4.png",
  "message": "图片已生成并保存到本地",
  "prompt": "一只可爱的柯基犬在草地上奔跑"
}

访问图片

http://localhost:8080/uploaded-images/image_20260504_143022_a1b2c3d4.png

4.2 七牛云 OSS 集成

对于生产环境,建议使用云存储服务。

添加配置
qiniu:
  access-key: ${QINIU_ACCESS_KEY}
  secret-key: ${QINIU_SECRET_KEY}
  bucket: your-bucket-name
  domain: https://your-domain.com
  region: z0  # 华东
创建七牛云服务
@Service
@Slf4j
public class QiniuStorageService {
    
    @Value("${qiniu.access-key}")
    private String accessKey;
    
    @Value("${qiniu.secret-key}")
    private String secretKey;
    
    @Value("${qiniu.bucket}")
    private String bucket;
    
    @Value("${qiniu.domain}")
    private String domain;
    
    @Value("${qiniu.region}")
    private String region;
    
    /**
     * 上传 Base64 图片到七牛云
     */
    public String uploadBase64ToQiniu(String base64Data) {
        try {
            // 解码 Base64
            byte[] imageBytes = Base64.getDecoder().decode(base64Data);
            
            // 生成文件名
            String fileName = generateFileName();
            
            // 配置
            Configuration cfg = new Configuration(Region.autoRegion());
            UploadManager uploadManager = new UploadManager(cfg);
            
            // 生成上传凭证
            Auth auth = Auth.create(accessKey, secretKey);
            String upToken = auth.uploadToken(bucket);
            
            // 上传
            Response response = uploadManager.put(imageBytes, fileName, upToken);
            
            // 解析结果
            DefaultPutRet putRet = new Gson().fromJson(
                response.bodyString(), 
                DefaultPutRet.class
            );
            
            String fileUrl = domain + "/" + putRet.key;
            log.info("图片已上传到七牛云: {}", fileUrl);
            
            return fileUrl;
            
        } catch (Exception e) {
            log.error("七牛云上传失败", e);
            throw new RuntimeException("图片上传失败", e);
        }
    }
    
    /**
     * 从 URL 下载并上传到七牛云
     */
    public String uploadUrlToQiniu(String imageUrl) {
        try {
            // 下载图片
            URL url = new URL(imageUrl);
            byte[] imageBytes;
            try (InputStream in = url.openStream()) {
                imageBytes = in.readAllBytes();
            }
            
            // 生成文件名
            String fileName = generateFileName();
            
            // 配置
            Configuration cfg = new Configuration(Region.autoRegion());
            UploadManager uploadManager = new UploadManager(cfg);
            
            // 生成上传凭证
            Auth auth = Auth.create(accessKey, secretKey);
            String upToken = auth.uploadToken(bucket);
            
            // 上传
            Response response = uploadManager.put(imageBytes, fileName, upToken);
            
            // 解析结果
            DefaultPutRet putRet = new Gson().fromJson(
                response.bodyString(), 
                DefaultPutRet.class
            );
            
            String fileUrl = domain + "/" + putRet.key;
            log.info("图片已上传到七牛云: {}", fileUrl);
            
            return fileUrl;
            
        } catch (Exception e) {
            log.error("七牛云上传失败", e);
            throw new RuntimeException("图片上传失败", e);
        }
    }
    
    private String generateFileName() {
        String timestamp = LocalDateTime.now()
            .format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
        String uuid = UUID.randomUUID().toString().substring(0, 8);
        return String.format("images/%s_%s.png", timestamp, uuid);
    }
}
集成到控制器
@PostMapping("/generate-and-upload-qiniu")
public Map<String, Object> generateAndUploadQiniu(@RequestParam String prompt) {
    try {
        // 1. 生成图片
        ImagePrompt imagePrompt = new ImagePrompt(prompt);
        ImageResponse response = imageModel.call(imagePrompt);
        
        var output = response.getResult().getOutput();
        String cloudUrl;
        
        // 2. 上传到七牛云
        if (output.getUrl() != null && !output.getUrl().isEmpty()) {
            cloudUrl = qiniuService.uploadUrlToQiniu(output.getUrl());
        } else if (output.getB64Json() != null && !output.getB64Json().isEmpty()) {
            cloudUrl = qiniuService.uploadBase64ToQiniu(output.getB64Json());
        } else {
            throw new RuntimeException("无法获取图片数据");
        }
        
        // 3. 返回结果
        Map<String, Object> result = new HashMap<>();
        result.put("success", true);
        result.put("imageUrl", cloudUrl);
        result.put("cloudStorage", "qiniu");
        result.put("message", "图片已生成并上传到七牛云");
        result.put("prompt", prompt);
        
        return result;
        
    } catch (Exception e) {
        log.error("图片生成或上传失败", e);
        Map<String, Object> error = new HashMap<>();
        error.put("success", false);
        error.put("message", "操作失败: " + e.getMessage());
        return error;
    }
}

4.3 智谱 CogView 集成

智谱 CogView 对中文支持更好,速度更快,成本更低。
CogView-3-Flash免费体验,可用来测试

创建智谱图像服务
@Service
@Slf4j
public class ZhipuImageService {
    
    private final ZhipuAiImageModel imageModel;
    private final ImageStorageService storageService;
    
    public ZhipuImageService(
            ZhipuAiImageModel imageModel,
            ImageStorageService storageService) {
        this.imageModel = imageModel;
        this.storageService = storageService;
    }
    
    /**
     * 生成图片并保存
     */
    public String generateAndSave(String prompt, String size) {
        try {
            // 解析尺寸
            String[] dimensions = size.split("x");
            int width = Integer.parseInt(dimensions[0]);
            int height = dimensions.length > 1 ? Integer.parseInt(dimensions[1]) : width;
            
            // 构建选项
            ZhipuAiImageOptions options = ZhipuAiImageOptions.builder()
                    .model("CogView-3-Flash")
                    .width(width)
                    .height(height)
                    .build();
            
            // 生成图片
            ImagePrompt imagePrompt = new ImagePrompt(prompt, options);
            ImageResponse response = imageModel.call(imagePrompt);
            
            // 提取 Base64 数据
            var output = response.getResult().getOutput();
            String base64Data = output.getB64Json();
            
            if (base64Data == null || base64Data.isEmpty()) {
                throw new RuntimeException("未获取到图片数据");
            }
            
            // 保存到本地
            String localPath = storageService.saveImageFromBase64(base64Data);
            
            log.info("智谱图片生成成功: {}", localPath);
            return localPath;
            
        } catch (Exception e) {
            log.error("智谱图片生成失败", e);
            throw new RuntimeException("图片生成失败", e);
        }
    }
}
控制器
@PostMapping("/zhipu/generate")
public Map<String, Object> generateWithZhipu(
        @RequestParam String prompt,
        @RequestParam(required = false, defaultValue = "1024x1024") String size) {
    
    try {
        String localPath = zhipuImageService.generateAndSave(prompt, size);
        
        Map<String, Object> result = new HashMap<>();
        result.put("success", true);
        result.put("imageUrl", localPath);
        result.put("model", "cogview-3");
        result.put("provider", "zhipu");
        result.put("message", "图片已生成并保存");
        
        return result;
        
    } catch (Exception e) {
        log.error("智谱图片生成失败", e);
        Map<String, Object> error = new HashMap<>();
        error.put("success", false);
        error.put("message", "生成失败: " + e.getMessage());
        return error;
    }
}

测试

curl -X POST "http://localhost:8080/image/zhipu/generate" \
  -d "prompt=未来机械少女,多重曝光叠加赛博都市霓虹、电路板纹路、光影线条,双重影像交织,高对比冷色调,科技感梦幻,潮流艺术风,2K" \
  -d "size=1024x1024"

在这里插入图片描述

优势

  • ✅ 中文理解更好
  • ✅ 生成速度更快(3-5秒)
  • ✅ 成本更低(免费)
  • ✅ 适合国内应用

4.4 硅基流动 Kwai-Kolors 图生图

硅基流动提供了高性能的 Kwai-Kolors/Kolors 模型,支持高质量的图生图功能,且对中文提示词理解极佳。

1. 环境配置

application.yml 中添加硅基流动的 API Key:

siliconflow:
  api:
    key: ${SILICONFLOW_API_KEY} # 从 https://cloud.siliconflow.cn/ 获取
2. 创建硅基流动服务类
package com.shun.springai.service;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.*;

@Slf4j
@Service
public class SiliconFlowService {

    @Value("${siliconflow.api.key}")
    private String apiKey;

    private static final String BASE_URL = "https://api.siliconflow.cn/v1/images/generations";
    private final RestTemplate restTemplate = new RestTemplate();

    @Data
    public static class SiliconFlowRequest {
        private String model;
        private String prompt;
        private String negative_prompt;
        private String image_size;
        private Integer batch_size;
        private Long seed;
        private Integer num_inference_steps;
        private Double guidance_scale;
        private String image; // base64 or url for img2img
    }

    @Data
    public static class SiliconFlowResponse {
        private List<ImageData> images;

        @Data
        public static class ImageData {
            private String url;
        }
    }

    /**
     * 图生图:基于参考图生成新图
     */
    public String imageToImage(String imageBase64, String prompt) {
        try {
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            headers.setBearerAuth(apiKey);

            SiliconFlowRequest request = new SiliconFlowRequest();
            request.setModel("Kwai-Kolors/Kolors");
            request.setPrompt(prompt);
            request.setImage(imageBase64); // 传入参考图的 Base64
            request.setImage_size("1024x1024"); // Kolors 推荐尺寸
            request.setBatch_size(1);
            request.setNum_inference_steps(20);
            request.setGuidance_scale(7.5);
            if (request.getNegative_prompt() == null) request.setNegative_prompt("");

            HttpEntity<SiliconFlowRequest> entity = new HttpEntity<>(request, headers);
            
            log.info("Calling SiliconFlow Kolors for image-to-image");
            SiliconFlowResponse response = restTemplate.postForObject(BASE_URL, entity, SiliconFlowResponse.class);

            if (response != null && response.getImages() != null && !response.getImages().isEmpty()) {
                return response.getImages().get(0).getUrl();
            }
            throw new RuntimeException("SiliconFlow returned no images");

        } catch (Exception e) {
            log.error("SiliconFlow image-to-image failed", e);
            throw new RuntimeException("图生图失败: " + e.getMessage(), e);
        }
    }
}
3. 控制器集成

ImageGenerationController 中增加接口:

@PostMapping(value = "/kolors/image-to-image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Map<String, Object> kolorsImageToImage(
        @RequestPart("image") MultipartFile image,
        @RequestParam("prompt") String prompt) {
    
    try {
        // 1. 将上传的图片转为 Base64
        byte[] imageBytes = image.getBytes();
        String base64Image = "data:" + image.getContentType() + ";base64," + 
                             Base64.getEncoder().encodeToString(imageBytes);
        
        // 2. 调用硅基流动服务
        String resultUrl = siliconFlowService.imageToImage(base64Image, prompt);
        
        // 3. (可选) 下载并保存到本地,因为 URL 有效期仅 1 小时
        String localPath = storageService.saveImageFromUrl(resultUrl);
        
        Map<String, Object> result = new HashMap<>();
        result.put("success", true);
        result.put("imageUrl", localPath);
        result.put("model", "Kwai-Kolors/Kolors");
        result.put("provider", "siliconflow");
        
        return result;
        
    } catch (Exception e) {
        log.error("Kolors 图生图失败", e);
        Map<String, Object> error = new HashMap<>();
        error.put("success", false);
        error.put("message", "失败: " + e.getMessage());
        return error;
    }
}

测试场景

curl -X POST "http://localhost:8080/image/kolors/image-to-image" \
  -F "image=@/path/to/your/photo.jpg" \
  -F "prompt=赛博朋克风格,霓虹灯光,未来城市背景,高细节"

原图
在这里插入图片描述
效果图
在这里插入图片描述

优势

  • 速度快:生成通常在 5-10 秒内完成
  • 中文强:对中文提示词的语义理解非常精准
  • 成本低:相比 Midjourney 或 DALL-E 3 更具性价比
  • 可控性:支持 guidance_scale 等参数微调生成效果

🏨 五、实战:AI 头像生成器

5.1 需求分析

我们要构建一个 AI 头像生成器,用户可以:

  1. 选择风格(商务、卡通、艺术等)
  2. 输入描述(性别、年龄、特征等)
  3. 生成多个候选头像
  4. 下载喜欢的头像

5.2 实现代码

DTO 类
@Data
public class AvatarGenerationRequest {
    
    @NotBlank(message = "风格不能为空")
    private String style;  // business, cartoon, artistic, anime
    
    @NotBlank(message = "描述不能为空")
    private String description;  // 男性,30岁,戴眼镜
    
    @Min(1)
    @Max(4)
    private Integer count = 1;  // 生成数量
    
    private String size = "512x512";
}

@Data
public class AvatarGenerationResponse {
    private boolean success;
    private List<String> imageUrls;
    private String message;
    private String model;
}
服务层
@Service
@Slf4j
public class AvatarGeneratorService {
    
    private final ImageModel imageModel;
    private final ImageStorageService storageService;
    
    public AvatarGenerationResponse generateAvatar(AvatarGenerationRequest request) {
        try {
            // 1. 构建提示词
            String prompt = buildAvatarPrompt(request);
            
            // 2. 配置选项
            OpenAiImageOptions options = OpenAiImageOptions.builder()
                    .model("dall-e-3")
                    .width(512)
                    .height(512)
                    .N(request.getCount())
                    .quality("standard")
                    .style("vivid")
                    .build();
            
            // 3. 生成图片
            ImagePrompt imagePrompt = new ImagePrompt(prompt, options);
            ImageResponse response = imageModel.call(imagePrompt);
            
            // 4. 保存图片
            List<String> imageUrls = new ArrayList<>();
            for (var result : response.getResults()) {
                var output = result.getOutput();
                
                String localPath;
                if (output.getUrl() != null) {
                    localPath = storageService.saveImageFromUrl(output.getUrl());
                } else {
                    localPath = storageService.saveImageFromBase64(output.getB64Json());
                }
                
                imageUrls.add(localPath);
            }
            
            // 5. 返回结果
            AvatarGenerationResponse avatarResponse = new AvatarGenerationResponse();
            avatarResponse.setSuccess(true);
            avatarResponse.setImageUrls(imageUrls);
            avatarResponse.setMessage("头像生成成功");
            avatarResponse.setModel("dall-e-3");
            
            return avatarResponse;
            
        } catch (Exception e) {
            log.error("头像生成失败", e);
            AvatarGenerationResponse errorResponse = new AvatarGenerationResponse();
            errorResponse.setSuccess(false);
            errorResponse.setMessage("生成失败: " + e.getMessage());
            return errorResponse;
        }
    }
    
    private String buildAvatarPrompt(AvatarGenerationRequest request) {
        StringBuilder prompt = new StringBuilder();
        
        // 基础描述
        prompt.append("Professional avatar portrait, ");
        prompt.append(request.getDescription()).append(", ");
        
        // 风格
        switch (request.getStyle().toLowerCase()) {
            case "business":
                prompt.append("business professional style, clean background, corporate headshot");
                break;
            case "cartoon":
                prompt.append("cartoon style, colorful, friendly expression, simple background");
                break;
            case "artistic":
                prompt.append("artistic painting style, creative, unique lighting");
                break;
            case "anime":
                prompt.append("anime style, Japanese animation, vibrant colors");
                break;
            default:
                prompt.append("professional style");
        }
        
        // 质量要求
        prompt.append(", high quality, detailed, centered composition");
        
        return prompt.toString();
    }
}
控制器
@RestController
@RequestMapping("/avatar")
@Validated
public class AvatarController {
    
    private final AvatarGeneratorService avatarService;
    
    @PostMapping("/generate")
    public AvatarGenerationResponse generateAvatar(
            @Valid @RequestBody AvatarGenerationRequest request) {
        return avatarService.generateAvatar(request);
    }
}

5.3 测试示例

商务头像
curl -X POST "http://localhost:8080/avatar/generate" \
  -H "Content-Type: application/json" \
  -d '{
    "style": "business",
    "description": "male, 35 years old, wearing glasses, short black hair",
    "count": 2,
    "size": "512x512"
  }'
卡通头像
curl -X POST "http://localhost:8080/avatar/generate" \
  -H "Content-Type: application/json" \
  -d '{
    "style": "cartoon",
    "description": "female, 25 years old, long brown hair, smiling",
    "count": 1
  }'

⚠️ 六、常见问题与解决方案

问题1:图片 URL 过期

现象:保存的 URL 过一段时间后无法访问

原因:OpenAI 生成的 URL 有效期通常为 24 小时

解决方案

  • ✅ 立即下载到本地存储
  • ✅ 或上传到云存储(七牛云、阿里云 OSS)
  • ✅ 不要直接返回临时 URL 给前端

问题2:Base64 数据过大

现象:内存溢出或响应超时

原因:高清图片的 Base64 编码可能超过 10MB

解决方案

// 1. 使用 URL 格式而非 Base64
.responseFormat("url")  // 而不是 "b64_json"

// 2. 降低图片尺寸
.size("512x512")  // 而不是 "1024x1024"

// 3. 异步处理
@Async
public CompletableFuture<String> generateAsync(String prompt) {
    // 异步生成
}

问题3:生成内容不符合预期

现象:图片质量差或与描述不符

解决方案

// 1. 优化提示词
String prompt = """
    Professional photography, 
    a cute cat sitting on windowsill, 
    sunlight streaming through window, 
    warm tones, high detail, 4k quality
    """;

// 2. 调整参数
.quality("hd")        // 使用高质量模式
.style("natural")     // 尝试不同风格

// 3. 多次生成,选择最佳
for (int i = 0; i < 3; i++) {
    // 生成并评估
}

问题4:速率限制

现象:报错 rate limit exceeded

原因:超过了 API 的调用频率限制

解决方案

@Component
public class RateLimiter {
    
    private final Semaphore semaphore = new Semaphore(5); // 最多5个并发
    
    public ImageResponse callWithLimit(ImageModel model, ImagePrompt prompt) 
            throws InterruptedException {
        semaphore.acquire();
        try {
            return model.call(prompt);
        } finally {
            semaphore.release();
        }
    }
}

问题5:中文提示词效果差

现象:使用中文描述生成的图片不准确

原因:部分模型对中文理解不如英文

解决方案

// 方案1:使用智谱 CogView(中文优化)
ZhipuAiImageOptions options = ZhipuAiImageOptions.builder()
    .model("cogview-3")
    .build();

// 方案2:翻译成英文
String englishPrompt = translateToEnglish(chinesePrompt);
ImagePrompt prompt = new ImagePrompt(englishPrompt);

// 方案3:中英文混合
String prompt = "一只可爱的猫咪, cute cat, professional photography";

📊 七、性能优化建议

7.1 缓存策略

@Service
public class CachedImageService {
    
    @Cacheable(value = "generated-images", key = "#prompt")
    public String generateCached(String prompt) {
        // 相同的提示词直接返回缓存
        return imageService.generate(prompt);
    }
}

7.2 异步处理

@Async
public CompletableFuture<String> generateAsync(String prompt) {
    String path = imageService.generateAndSave(prompt);
    return CompletableFuture.completedFuture(path);
}

// 使用
CompletableFuture<String> future = service.generateAsync(prompt);
future.thenAccept(path -> {
    // 处理完成后的逻辑
});

7.3 批量生成

public List<String> batchGenerate(List<String> prompts) {
    return prompts.parallelStream()
        .map(prompt -> imageService.generateAndSave(prompt))
        .collect(Collectors.toList());
}

7.4 成本控制

@Component
public class CostTracker {
    
    private final AtomicLong dailyCount = new AtomicLong(0);
    private final long dailyLimit = 100; // 每天最多100张
    
    public boolean canGenerate() {
        return dailyCount.get() < dailyLimit;
    }
    
    public void recordGeneration() {
        dailyCount.incrementAndGet();
    }
    
    @Scheduled(cron = "0 0 0 * * *") // 每天零点重置
    public void resetDailyCount() {
        dailyCount.set(0);
    }
}

📝 八、总结与最佳实践

8.1 核心要点回顾

ImageModel 使用
  • ✅ 支持同步调用 call()
  • ✅ 自动处理 URL 和 Base64 格式
  • ✅ 通过 Options 配置参数
提示词优化
  • ✅ 使用英文或中英文混合
  • ✅ 详细描述风格、光线、构图
  • ✅ 添加质量关键词(high quality, 4k)

8.2 安全注意事项

  • 🔒 API Key 存储在环境变量
  • 🔒 验证用户输入,防止注入攻击
  • 🔒 实施速率限制,防止滥用
  • 🔒 记录审计日志
  • 🔒 定期清理无用图片

8.3 生产环境建议

  1. 使用云存储:七牛云、阿里云 OSS、AWS S3
  2. CDN 加速:提升图片加载速度
  3. 图片压缩:减少存储空间和带宽
  4. 监控告警:监控用量和错误率
  5. 降级策略:主模型失败时切换到备用模型

🔮 九、下一步学习路径

恭喜你已经掌握了 Spring AI 图片生成的核心技能!接下来可以学习:

关注账号,后续持续更新内容

  1. [第6篇] Spring AI 响应式编程与流式输出 - Reactor + SSE 实时聊天
  2. [第7篇] Spring AI 函数调用实战 - Function Calling 集成业务系统
  3. [第8篇] Spring AI RAG 实战 - 基于知识库的智能问答

实践项目

  • 🎨 完善 AI 头像生成器,添加更多风格
  • 🖼️ 开发图片编辑工具(背景移除、风格转换)
  • 📱 构建社交媒体配图生成器
  • 🏨 实现酒店宣传海报自动生成系统

进阶主题-后续持续更新,敬请期待

  • 图片识别:结合 Vision 模型分析图片内容
  • 视频生成:探索 AI 视频生成技术
  • 3D 建模:AI 辅助 3D 模型生成

💬 互动环节

觉得有用?

  • ⭐ 点赞支持
  • 💾 收藏备用
  • 🔄 分享给朋友
    在这里插入图片描述
    下一篇预告:《Spring AI 函数调用实战:让 AI 调用你的业务代码》

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐