用 Spring Boot 轻松玩转阿里云通义万相 AI 绘画 🎨

前言

只需要几句描述词,后台就能自动生成一张精美的图片——这就是 AI 图像生成的魅力。不过,要把这项能力集成到 Java 项目里,以往可能需要研究各种 SDK、处理复杂的鉴权和流协议。

Spring AI Alibaba 把调用阿里云 DashScope 通义万相的复杂细节封装成统一的接口,让你像调用普通 Bean 一样生成图像。本指南将带着你,从底层设计原理,到三个实战功能,再到生产环境部署,一步一步把这个示例吃透、跑起来。


一、项目概览

1.1 概述

这是一个基于 Spring Boot 3 + Spring AI Alibaba 的轻量级图像生成示例应用。它提供了三个 REST 接口,覆盖了从简单到复杂的图像生成需求:

  • 基础生成:说句话,直接返回一张图
  • 批量生成:一句话生成多张变体
  • 多条件定制:分别指定主体、环境、风格,按需拼出精细化的图像

项目启动在 10008 端口,所有实验都可以用 curl 或浏览器完成。

1.2 技术栈速览

组件 作用
Spring Boot 3.x 应用框架
Spring AI Alibaba Starter 统一 AI 模型抽象,对接 DashScope
DashScope API(通义万相) 实际执行图像生成的云端服务
RESTful API 提供 HTTP 交互

整个项目只有一个核心依赖:spring-ai-alibaba-starter-dashscope,它已经替你管理好了和阿里云通信的全部逻辑。


二、核心原理:Spring AI 如何帮你画图

2.1 统一抽象:ImageModel 和 ImagePrompt

Spring AI 设计的精髓在于“换个模型只是换个实现”。对于图像生成,它定义了:

  • ImageModel 接口:代表一个能生成图像的模型,核心方法是 call(ImagePrompt request)
  • ImagePrompt:包含了提示词和你想要的各种选项(尺寸、数量、风格等)。
  • ImageResponse:模型返回的结果,其中包含生成图片的 URL 或 Base64 数据。

你只需要注入一个 ImageModel,传入 ImagePrompt,就能得到结果。底层到底是通义万相、OpenAI DALL·E 还是 Stability AI,对你来说都变成同一套代码。

2.2 DashScope 是怎么接入的?

当你引入 spring-ai-alibaba-starter-dashscope 后,Spring Boot 会自动配置一个 DashScopeImageModel 实例,它就是 ImageModel 的一种实现。配置好 API Key 后,ImageModel.call() 最终会转换成对阿里云 DashScope 的一次 HTTP POST 请求,就像这样:

POST https://dashscope.aliyuncs.com/api/v1/images/generation
Headers:
  Authorization: Bearer sk-xxxxxxxxxxx
Body:
  {
    "model": "wanx-v1",
    "input": {
      "prompt": "一只会编程的猫"
    },
    "parameters": {
      "n": 2,
      "size": "1024*1024"
    }
  }

2.3 一次图像请求的完整旅程

下面这个序列图展示了从前端发起请求到最终拿到图片的全过程:

DashScope API (通义万相) ImageModel (DashScope实现) DashScopeImageController 客户端 (curl/浏览器) DashScope API (通义万相) ImageModel (DashScope实现) DashScopeImageController 客户端 (curl/浏览器) GET /example/image call(new ImagePrompt(prompt)) POST /images/generation (prompt, options) ImageResponse (imageUrl, ...) ImageResponse GET imageUrl (下载图片流) 图片二进制流 返回图片 (Content-Type: image/png)

可以看到,Controller 并没有直接操作 HTTP 详情,它只是调用了 Spring AI 提供的 ImageModel。这种分层让代码干净、好测试,未来切换其他图像模型也非常容易。


三、架构设计全景图

整个应用从外到内分为清晰的四层:

云服务层

Spring AI 层

Web 层

客户端层

HTTP 请求

1. 构建 ImagePrompt

包含 options

2. call(ImagePrompt)

3. 返回 ImageResponse (含URL)

4. 返回图片 URL

5. 读取图片流

6. 返回图片二进制数据

7. 输出图片

浏览器 / curl

DashScopeImageController

ImageModel 接口

ImageOptionsBuilder

DashScope 通义万相 API

  • Web 层:处理请求参数,拼装提示词,并将最终图片写入 HTTP 响应。
  • Spring AI 层:提供 ImageModel 抽象和 ImageOptionsBuilder 构建工具,隔离底层实现。
  • 云服务层:实际执行 AI 推理,生成图片并返回临时下载地址。

下面,我们就深入到三个功能中,看看每一层具体怎么交互。


四、功能逐一拆解

4.1 功能一:基础图像生成

接口GET /example/image
参数:无(使用默认提示词)
返回:PNG 图片流

这是最简答的“说一句话,得一张图”。当你访问这个地址时,后端会用科技感满满的默认提示词去请求生成。具体步骤可以用流程图来看:

成功

失败

收到请求

创建 ImagePrompt 含默认提示词

调用 ImageModel.call

DashScope API 返回

从响应中提取 imageUrl

用 URL 读取图片输入流

边读边写入 HttpServletResponse 输出流

客户端收到图片

抛出异常/返回错误

代码实现(优化后,采用流式传输避免大图 OOM):

@GetMapping("/example/image")
public void image(HttpServletResponse response) throws IOException {
    // 1. 构建请求,使用默认提示词
    ImagePrompt prompt = new ImagePrompt(DEFAULT_PROMPT);
    // 2. 调用模型
    ImageResponse imageResponse = imageModel.call(prompt);
    String imageUrl = imageResponse.getResult().getOutput().getUrl();

    // 3. 设置响应头
    response.setContentType(MediaType.IMAGE_PNG_VALUE);

    // 4. 流式读取并写入,避免全量加载到内存
    try (InputStream in = new URL(imageUrl).openStream();
         OutputStream out = response.getOutputStream()) {
        byte[] buffer = new byte[8192];
        int bytesRead;
        while ((bytesRead = in.read(buffer)) != -1) {
            out.write(buffer, 0, bytesRead);
        }
        out.flush();
    }
}

设计亮点

  • 使用缓冲区(8KB)循环读写,即使图片很大也不会撑爆内存。
  • 提示词可以抽成常量或配置,方便后续改动。

一笔带过但想补充的
原示例中直接 in.readAllBytes() 写入内存,这里我们已经改成了流式拷贝,生产环境更推荐这种写法。


4.2 功能二:一次生成多张变体

接口GET /example/image/multiPrompt
参数

  • prompt:提示词(默认:“一只会编程的猫”)
  • count:数量(默认:2)
    返回Collection<String>(图片 URL 集合)

有些时候,同一个点子你想要多张不同构图的版本,这个接口就是干这个的。关键在于用 ImageOptionsBuilder 设置生成数量 N(count)

@GetMapping("/example/image/multiPrompt")
public Collection<String> generateMultiImages(
        @RequestParam(defaultValue = "一只会编程的猫") String prompt,
        @RequestParam(defaultValue = "2") int count) {

    ImageOptions options = ImageOptionsBuilder.builder()
            .N(count)          // 希望生成的数量
            .build();

    ImageResponse response = imageModel.call(new ImagePrompt(prompt, options));
    return response.getResults().stream()
            .map(result -> result.getOutput().getUrl())
            .collect(Collectors.toSet());  // Set 自动去重
}

工作流程

DashScope ImageModel Controller 客户端 DashScope ImageModel Controller 客户端 GET /multiPrompt?prompt=柴犬&count=3 call(ImagePrompt(prompt, N=3)) POST (n=3) 返回 3 个图片 JSON 包含 3 个 URL 的 ImageResponse ["url1", "url2", "url3"]

一点提醒
阿里云 DashScope 对批量数量通常限制在 1~4 张,超过会报错。所以别指望一次生成几十张。


4.3 功能三:多条件精细控制

接口GET /example/image/multipleConditions
参数

  • subject:主体(如“一只会编程的猫”)
  • environment:环境(如“办公室”)
  • width / height:尺寸(默认 1024x1024)
  • style:艺术风格(如“生动”“赛博朋克”)
    返回:图片 URL 或结构化的错误 JSON。

这个接口体现了实战中最常见的场景:让用户自定义主体、背景和画风,后端拼成一段完整的、高质量的提示词。例如:

String prompt = String.format(
    "一个%s,置身于%s的环境中,使用%s的艺术风格,高清4K画质,细节精致",
    subject, environment, style
);

然后根据参数设置图片尺寸:

ImageOptions options = ImageOptionsBuilder.builder()
        .height(height)
        .width(width)
        .build();

错误处理
如果生成失败(网络问题、API Key 无效、敏感词触发审核等),接口不直接裸抛异常,而是返回给前端一个友好的 JSON:

{
  "error": "图像生成失败",
  "message": "具体错误信息",
  "timestamp": "2026-05-06T12:34:56"
}

实现上,它把异常捕获后包装成“结构化错误响应”。更好的做法是抽取一个全局异常处理器,这样每个接口不用反复写 try-catch。我给你补一个推荐写法:

// 自定义异常
public class ImageGenerationException extends RuntimeException {
    public ImageGenerationException(String message) { super(message); }
}

// 全局处理器
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ImageGenerationException.class)
    public ResponseEntity<Map<String, Object>> handleImageException(ImageGenerationException e) {
        Map<String, Object> body = new LinkedHashMap<>();
        body.put("error", "图像生成失败");
        body.put("message", e.getMessage());
        body.put("timestamp", LocalDateTime.now());
        return ResponseEntity.internalServerError().body(body);
    }
}

这样,Controller 里只需:

try {
    // 图像生成逻辑
} catch (Exception e) {
    throw new ImageGenerationException("生成失败:" + e.getMessage());
}

代码更清爽,也方便统一记录日志。


五、配置与扩展

5.1 最小化配置

application.yml 里,你只需要两样东西:

server:
  port: 10008

spring:
  application:
    name: spring-ai-alibaba-dashscope-image-example
  ai:
    dashscope:
      api-key: ${AI_DASHSCOPE_API_KEY}  # 从环境变量读取

${AI_DASHSCOPE_API_KEY} 会从系统环境变量中取值。本地开发时,设置方式如下:

# Linux / Mac
export AI_DASHSCOPE_API_KEY=sk-xxxxxxxxxxxxxxxx

# Windows PowerShell
$env:AI_DASHSCOPE_API_KEY="sk-xxxxxxxxxxxxxxxx"

5.2 进阶配置

有时候你需要更细粒度的控制,比如切换模型、调超时:

spring:
  ai:
    dashscope:
      api-key: ${AI_DASHSCOPE_API_KEY}
      image:
        options:
          model: wanx-v1          # 指定模型,也可用 wanx-plus
      connection-timeout: 30s     # 连接超时
      read-timeout: 120s          # 读取超时(图像生成可能需要更久)

5.3 异步与流式返回

默认调用是同步的,即请求一直等待直到 DashScope 返回。如果生成时间较长,客户端也会被阻塞。你可以用 CompletableFuture 快速让接口变成异步:

@GetMapping("/image/async")
public CompletableFuture<ResponseEntity<String>> generateAsync(@RequestParam String prompt) {
    return CompletableFuture.supplyAsync(() -> {
        ImageResponse res = imageModel.call(new ImagePrompt(prompt));
        return ResponseEntity.ok(res.getResult().getOutput().getUrl());
    });
}

你也可以借助 Spring WebFlux 的 SSE(Server-Sent Events)实时推送生成进度(这里简化成返回 URL):

@GetMapping(value = "/image/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> streamImage(@RequestParam String prompt) {
    return Flux.create(sink -> {
        ImageResponse res = imageModel.call(new ImagePrompt(prompt));
        sink.next(ServerSentEvent.builder(res.getResult().getOutput().getUrl()).build());
        sink.complete();
    });
}

5.4 将图片保存到本地或 OSS

DashScope 返回的图片 URL 有有效期,过期就无法访问。你可以顺手把图片下载并持久化存储,比如保存到服务器本地或阿里云 OSS:

// 保存到本地
URL imageUrl = new URL(imageResponse.getResult().getOutput().getUrl());
BufferedImage image = ImageIO.read(imageUrl);
File outputFile = new File("/data/images/generated.png");
ImageIO.write(image, "png", outputFile);

生产环境建议上传到 OSS 并返回新的永久 URL。


六、部署实操:让服务跑起来

现在我们把服务搬到生产环境。这里提供从本地运行到 Docker 部署的完整步骤。

6.1 本地开发运行

进入项目目录(例如 dashscope-image),先确保环境变量已设置,然后:

mvn spring-boot:run

看到 Started DashScopeImageApplication in ... seconds 就说明启动成功。

快速测试:

# 生成一张图片并保存
curl http://localhost:10008/example/image -o test.png

6.2 打包为可执行 JAR

mvn clean package -DskipTests

target/ 目录下会生成 dashscope-image-1.0.0.jar(名字可能略有不同)。这个 JAR 内嵌了 Tomcat,可以直接运行:

export AI_DASHSCOPE_API_KEY=sk-xxx
java -jar target/dashscope-image-1.0.0.jar

6.3 部署到 Linux 服务器

把 JAR 上传到服务器的 /opt/dashscope-image/ 目录,然后创建 Systemd 服务来管理:

sudo tee /etc/systemd/system/dashscope-image.service > /dev/null <<EOF
[Unit]
Description=Spring AI DashScope Image Service
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/opt/dashscope-image
ExecStart=/usr/bin/java -jar /opt/dashscope-image/dashscope-image-1.0.0.jar
Environment="AI_DASHSCOPE_API_KEY=sk-你的Key"
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

然后启动并设置开机自启:

sudo systemctl daemon-reload
sudo systemctl start dashscope-image
sudo systemctl enable dashscope-image

6.4 Docker 部署

如果喜欢用容器,可以这么玩。先在项目根目录创建 Dockerfile

FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY target/*.jar app.jar
ENV AI_DASHSCOPE_API_KEY=""
EXPOSE 10008
ENTRYPOINT ["java", "-jar", "app.jar"]

构建并运行:

docker build -t dashscope-image:latest .
docker run -d --name dashscope-image -p 10008:10008 \
  -e AI_DASHSCOPE_API_KEY=sk-你的Key \
  dashscope-image:latest

6.5 Nginx 反向代理 + HTTPS

为服务添加域名和 HTTPS 更安全。配置 Nginx 反向代理:

server {
    listen 80;
    server_name your-domain.com;

    location / {
        proxy_pass http://127.0.0.1:10008;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

然后使用 Let’s Encrypt 免费申请证书:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com

6.6 一键部署脚本

我还为你准备了一个快速部署脚本,把上面的手工操作自动化:

#!/bin/bash
JAR_FILE="dashscope-image-1.0.0.jar"
SERVICE_NAME="dashscope-image"
PORT=10008
API_KEY="sk-你的Key"   # 实际使用时请用环境变量或参数传入

# 安装 Java(如未安装)
if ! command -v java &> /dev/null; then
    sudo apt update && sudo apt install -y openjdk-21-jre
fi

# 准备目录
sudo mkdir -p /opt/$SERVICE_NAME
sudo cp $JAR_FILE /opt/$SERVICE_NAME/

# 创建 systemd 服务
sudo tee /etc/systemd/system/$SERVICE_NAME.service > /dev/null <<EOF
[Unit]
Description=Spring AI DashScope Image Service
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/opt/$SERVICE_NAME
ExecStart=/usr/bin/java -jar /opt/$SERVICE_NAME/$JAR_FILE
Environment="AI_DASHSCOPE_API_KEY=$API_KEY"
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable $SERVICE_NAME --now
echo "✅ 服务已启动,访问 http://<服务器IP>:$PORT/example/image"

七、常见问题排查

Q1:启动报错“API Key 为空”
先检查环境变量是否设置:echo $AI_DASHSCOPE_API_KEY。如果没有,重新设置后重启应用。记得每次新建终端都要重新设置,或写入 ~/.bashrc

Q2:接口返回的图片 URL 打不开
DashScope 返回的链接有效期较短,可能是几个小时。建议将图片下载保存到本地或对象存储。

Q3:批量生成数量超过 4 张报错
这是阿里云的限制,目前一次请求最多生成 1-4 张。需要更多可以循环调用。

Q4:外网无法访问
检查服务器安全组/防火墙是否放行了 10008 端口(sudo ufw allow 10008),以及 Nginx 是否正常运行。

Q5:生成失败返回“敏感内容”
调整提示词,避免过于露骨或违规描述。也可以联系阿里云提交审核豁免。


八、总结与下一步

恭喜你,完成了从理解原理、动手实践到生产部署的全过程!我们一起掌握了:

  • 如何用 ImageModelImagePrompt 抽象图像生成任务
  • 如何构建三种不同复杂度的生成接口
  • 如何优化大图传输、统一错误处理、扩展异步流式
  • 如何把应用部署到 Linux 或 Docker 环境

接下来,你可以玩这些花样:

  • 写个前端页面,让用户拖拽输入提示词,直接看到生成的图片。
  • 对接自己的业务,例如电商自动生成商品宣传图,或者游戏生成角色概念图。
  • 尝试其他模型,把 DashScope 换成 OpenAI 或 Stability AI,代码几乎不用改。
  • 加入监控和日志,使用 Micrometer + Prometheus 观察接口耗时和成功率。

整个示例就像一个乐高模块,希望你能把它嵌入到更大的系统里,创造出更有意思的 AI 应用!如果遇到困难,记得回来看这篇博文 😄

Logo

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

更多推荐