一、即梦文生图能力申请

账号登录-火山引擎

进入即梦AI:账号登录-火山引擎

进行实名认证,服务开通,获取密钥(这个是重点,后面调用功能的入口)

二、使用步骤

官方文档地址:即梦AI-图片生成4.0-接口文档--即梦AI-火山引擎

SDK方案地址:SDK使用说明--AI中台公用文档-火山引擎

java-SDK:GitHub - volcengine/volc-sdk-java · GitHub

1.引入库

获取版本地址:https://central.sonatype.com/artifact/com.volcengine/volc-sdk-java?smo=true

<dependency>
    <groupId>com.volcengine</groupId>
    <artifactId>volc-sdk-java</artifactId>
    <version>1.0.273</version>
</dependency>

2.java代码,注意替换你AK和SK

代码如下(示例):

package cn.iocoder.yudao.module.iscs.utils;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.volcengine.service.visual.IVisualService;
import com.volcengine.service.visual.impl.VisualServiceImpl;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.net.URL;
import java.util.Base64;

/**
 * 火山引擎「即梦 AI」文生图 Demo。
 * <p>
 * 使用模型:{@code jimeng_t2i_v40}(即梦文生图 4.0)
 * <p>
 * 官方文档:https://www.volcengine.com/docs/85128/1554673
 * <p>
 * 调用流程(异步):
 * <pre>
 *   1. cvSync2AsyncSubmitTask  — 提交文生图任务,返回 task_id
 *   2. cvSync2AsyncGetResult   — 按 task_id 轮询,直到 status=done
 *   3. 从返回的 image_urls 或 binary_data_base64 中解析图片并保存到本地
 * </pre>
 * <p>
 * 注意:本类为本地调试 Demo,AK/SK 硬编码仅用于测试,生产环境请改用配置中心或环境变量。
 */
public class CVProcessDemo {

    /** 火山引擎 Access Key,用于 API 身份认证 */
    private static final String AK = "你的AK";

    /** 火山引擎 Secret Key,用于请求签名(注意 Base64 编码,末尾通常带 ==) */
    private static final String SK = "你的SK";

    /**
     * 能力标识(req_key),指定调用的 AI 模型/能力。
     * jimeng_t2i_v40 = 即梦文生图 4.0,提交与查询时均需携带同一 req_key。
     */
    private static final String REQ_KEY = "jimeng_t2i_v40";

    /** 轮询间隔(毫秒),即梦文生图通常需 5~30 秒,建议 3 秒查一次 */
    private static final long POLL_INTERVAL_MS = 3_000;

    /** 最大轮询次数,60 × 3s ≈ 3 分钟,超时则放弃 */
    private static final int MAX_POLL_TIMES = 60;

    /**
     * 程序入口:提交文生图任务 → 轮询结果 → 保存图片。
     */
    public static void main(String[] args) {
        // ---------- 1. 初始化火山视觉 SDK ----------
        // VisualServiceImpl 为单例,内部封装 HMAC-SHA256 签名与 HTTP 请求
        IVisualService visualService = VisualServiceImpl.getInstance();
        visualService.setAccessKey(AK);
        visualService.setSecretKey(SK);
        // 视觉智能 API 固定 endpoint,不可使用 open.volcengineapi.com
        visualService.setRegion("cn-north-1");
        visualService.setHost("visual.volcengineapi.com");

        // ---------- 2. 构建提交任务请求体 ----------
        JSONObject submitReq = new JSONObject();
        submitReq.put("req_key", REQ_KEY);
        // prompt:文本描述,即「画什么」;支持中文,越具体效果越好
        submitReq.put("prompt", "正在打LOL的边牧,但是这局已经输了,在图片的上方加上语言气泡,显示小狗说:'队友的锅,我是svp'");
        submitReq.put("width", 1024);   // 输出宽度(像素),常用 1024
        submitReq.put("height", 1024);  // 输出高度(像素),常用 1024

        try {
            // ---------- 3. 提交异步任务 ----------
            // 对应 API:Action=CVSync2AsyncSubmitTask, Version=2022-08-31
            // 成功时返回:{ "code": 10000, "data": { "task_id": "..." } }
            JSONObject submitResp = (JSONObject) visualService.cvSync2AsyncSubmitTask(submitReq);
            System.out.println("提交任务返回:" + submitResp.toJSONString());
            checkResponse(submitResp, "提交任务");

            // 从 data 中取出 task_id,后续轮询必需
            String taskId = submitResp.getJSONObject("data").getString("task_id");
            if (StringUtils.isBlank(taskId)) {
                System.out.println("task_id 为空,无法查询结果");
                return;
            }
            System.out.println("task_id:" + taskId);

            // ---------- 4. 轮询直到任务完成 ----------
            JSONObject resultData = pollTaskResult(visualService, taskId);
            if (resultData == null) {
                System.out.println("任务超时或失败,未获取到图片");
                return;
            }

            // ---------- 5. 解析并保存图片 ----------
            // 使用时间戳命名,避免覆盖已有文件
            long now = System.currentTimeMillis();
            String outputPath = "C:\\Users\\HP\\Pictures\\Saved Pictures\\" + now + ".png";
            if (saveImageFromResult(resultData, outputPath)) {
                System.out.println("图片保存成功:" + new File(outputPath).getAbsolutePath());
            } else {
                System.out.println("未能从结果中解析图片,data 内容:" + resultData.toJSONString());
            }
        } catch (Exception e) {
            System.err.println("调用失败: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 轮询查询任务结果。
     * <p>
     * 对应 API:Action=CVSync2AsyncGetResult, Version=2022-08-31
     * <p>
     * 任务状态(data.status)常见值:
     * <ul>
     *   <li>{@code in_queue}   — 排队中,继续等待</li>
     *   <li>{@code generating} — 生成中,继续等待</li>
     *   <li>{@code done} / {@code success} — 完成,可解析图片</li>
     *   <li>{@code failed} / {@code error} — 失败,抛出异常</li>
     * </ul>
     *
     * @param visualService SDK 实例
     * @param taskId        提交任务时返回的任务 ID
     * @return 任务完成时的 data 对象(含图片数据);超时返回 null
     */
    private static JSONObject pollTaskResult(IVisualService visualService, String taskId) throws Exception {
        JSONObject queryReq = new JSONObject();
        queryReq.put("req_key", REQ_KEY);
        queryReq.put("task_id", taskId);

        for (int i = 1; i <= MAX_POLL_TIMES; i++) {
            Thread.sleep(POLL_INTERVAL_MS);

            JSONObject queryResp = (JSONObject) visualService.cvSync2AsyncGetResult(queryReq);
            if (queryResp == null) {
                System.out.println("第 " + i + " 次查询:响应为空");
                continue;
            }

            // 响应体可能含 MB 级 Base64,超过 1000 字符时截断打印,避免控制台刷屏
            String logContent = queryResp.toJSONString().length() < 1000
                    ? queryResp.toJSONString()
                    : "太长不显示";
            System.out.println("第 " + i + " 次查询:" + logContent);

            // code=10000 为火山视觉 API 成功码;部分旧接口使用 code=0
            if (queryResp.containsKey("code") && queryResp.getIntValue("code") != 10000
                    && queryResp.getIntValue("code") != 0) {
                throw new RuntimeException("查询失败: " + queryResp.getString("message"));
            }

            JSONObject data = queryResp.getJSONObject("data");
            if (data == null) {
                continue;
            }

            String status = data.getString("status");
            if ("done".equalsIgnoreCase(status) || "success".equalsIgnoreCase(status)) {
                return data;
            }
            if ("failed".equalsIgnoreCase(status) || "error".equalsIgnoreCase(status)) {
                throw new RuntimeException("任务失败: " + data.toJSONString());
            }
            // in_queue / generating 等中间状态:不 return,进入下一轮轮询
        }
        return null;
    }

    /**
     * 从任务结果 data 中解析图片并写入本地文件。
     * <p>
     * 火山 API 可能返回以下任一格式(按优先级依次尝试):
     * <ol>
     *   <li>{@code image_urls} — URL 数组,下载后保存</li>
     *   <li>{@code image_url}  — 单个 URL 字符串</li>
     *   <li>{@code binary_data_base64} — Base64 数组或字符串,解码后直接写文件</li>
     * </ol>
     *
     * @param data 轮询完成后的 data 对象
     * @param path 本地保存路径(含文件名)
     * @return 是否成功保存
     */
    private static boolean saveImageFromResult(JSONObject data, String path) throws Exception {
        // 方式一:image_urls 数组(部分接口返回 CDN 临时链接,需尽快下载)
        JSONArray imageUrls = data.getJSONArray("image_urls");
        if (imageUrls != null && !imageUrls.isEmpty()) {
            String imageUrl = imageUrls.getString(0);
            if (StringUtils.isNotBlank(imageUrl)) {
                System.out.println("图片地址:" + imageUrl);
                saveImageFromUrl(imageUrl, path);
                return true;
            }
        }

        // 方式二:单个 image_url 字段
        String imageUrl = data.getString("image_url");
        if (StringUtils.isNotBlank(imageUrl)) {
            System.out.println("图片地址:" + imageUrl);
            saveImageFromUrl(imageUrl, path);
            return true;
        }

        // 方式三:binary_data_base64 数组(即梦 v4 实际返回此格式)
        JSONArray base64List = data.getJSONArray("binary_data_base64");
        if (base64List != null && !base64List.isEmpty()) {
            byte[] bytes = Base64.getDecoder().decode(base64List.getString(0));
            FileUtils.writeByteArrayToFile(new File(path), bytes);
            return true;
        }

        // 方式四:binary_data_base64 单字符串(兼容不同版本响应结构)
        String base64 = data.getString("binary_data_base64");
        if (StringUtils.isNotBlank(base64)) {
            FileUtils.writeByteArrayToFile(new File(path), Base64.getDecoder().decode(base64));
            return true;
        }
        return false;
    }

    /**
     * 校验 API 响应是否成功。
     *
     * @param resp  API 返回的 JSON 对象
     * @param step  当前步骤描述(用于异常信息,如「提交任务」)
     * @throws RuntimeException 响应为空、code 非成功、或 data 缺失时抛出
     */
    private static void checkResponse(JSONObject resp, String step) {
        if (resp == null) {
            throw new RuntimeException(step + "响应为空");
        }
        // 火山视觉 API:code=10000 表示成功;部分旧接口 code=0
        int code = resp.getIntValue("code");
        if (code != 10000 && code != 0) {
            throw new RuntimeException(step + "失败: " + resp.getString("message"));
        }
        if (resp.getJSONObject("data") == null) {
            throw new RuntimeException(step + " data 字段为空");
        }
    }

    /**
     * 从 URL 下载图片并保存到本地。
     *
     * @param url  图片地址(通常为火山 CDN 临时链接)
     * @param path 本地保存路径
     */
    public static void saveImageFromUrl(String url, String path) throws Exception {
        // 连接超时 10s,读取超时 60s(大图下载可能较慢)
        FileUtils.copyURLToFile(new URL(url), new File(path), 10_000, 60_000);
    }

}

注意:图片生成是异步的,需要时间,所以需要轮询检查任务完成情况,然后输出,注意修改文件输出。


三、验证结果

Logo

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

更多推荐