【智能协同云图库】第七期:基于AI调用阿里云百炼大模型,实现AI图片编辑功能

摘要:AI 高速发展赋能传统业务,图库网站亦有诸多AI 应用空间。以 AI 扩图功؜能 为例,让我们来学习如何在项目⁠中快速接入 AI 绘图大模型。‏用户可以选择一张已上传的图片,‌通过 AI 扩图得到新的图片,希望可以帮到大家。

AI绘图大模型****两次扩图效果:

本节思维导图:

方案设计

1、AI 绘图大模型选择

控制台 也能看到对应的图像画面扩展模型

2、调用方式

点击对应的API参考,我们发现,API 只支持异步方式调用

这是因为 AI 绘画任务计؜算量大且耗时长,同步调用会导致服务器线程长时间被单个⁠任务占用 ,限制了并发处理能力,增加了超时和系统崩溃的‏风险。通过异步调用,服务器可以将任务放入队列中 ,合理‌调度资源,避免阻塞主线程,从而更高效地服务多个用户请‏求,提升整体系统的稳定性和可扩展性。

同步调用如下,好处是客户⁠端可以直接获取到结‏果,调用更方便:

异步调用如下,客户端需要在提交任务后⁠,不断轮询请求,来检查任务‏是否执行完成:

            ‏

由于 AI 接口؜已经选择了异步调用,所以我们作⁠为要调用 AI 接口的客户端,‏要使用轮询的方式来检查任务状态‌是否为 "已完成",如果完成了‏,才可以获取到生成的图片。

那么是前端轮询还是后端轮询呢?让我们来对比一下

1)前端轮询

前端调用后؜端提交任务后得到任⁠务 ID,然后通过‏定时器轮询请求查询‌任务状态接口,直到‏任务完成或失败。

TypeScript 复制代码
// 提交任务
async function submitTask() {
  const response = await fetch('/api/createTask', { method: 'POST' });
  const { taskId } = await response.json();
  checkTaskStatus(taskId);
}

// 调用
submitTask();

// 检查任务状态
async function checkTaskStatus(taskId) {
  const intervalId = setInterval(async () => {
    const response = await fetch(`/api/taskStatus?taskId=${taskId}`);
    const { status, result } = await response.json();

    if (status === 'success') {
      console.log('Task completed:', result);
      clearInterval(intervalId); // 停止轮询
    } else if (status === 'failed') {
      console.error('Task failed');
      clearInterval(intervalId); // 停止轮询
    }
  }, 2000); // 每隔 2 秒轮询
}

2)后端轮询

后端通过循؜环或定时任务检测任⁠务状态,接口阻‏塞,直到任务完成或‌失败,直接返回结‏果给前端。

java 复制代码
@RestController
public class TaskController {

    @PostMapping("/createTask")
    public String createTask() {
        String taskId = taskService.submitTask();
        return taskId;
    }

    @GetMapping("/waitForTask")
    public ResponseEntity<String> waitForTask(@RequestParam String taskId) {
        while (true) {
            String status = taskService.checkTaskStatus(taskId);

            if ("success".equals(status)) {
                return ResponseEntity.ok("Task completed");
            } else if ("failed".equals(status)) {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Task failed");
            }

            try {
                Thread.sleep(2000); // 等待 2 秒后重试
            } catch (InterruptedException e) {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error occurred");
            }
        }
    }
}

显然,后端轮询容易因为任务阻塞导致资源耗尽,所以通常推荐 前端轮询。除非有明确的需求要求时,才考虑后端轮询,比如任务结果需实时返回且对网络请求数敏感。


在配置文件中填写获取到的 apiKey:

bash 复制代码
# 阿里云 AI 配置
aliYunAi:
  apiKey: xxxx

新建数据模型类

api 包下新建 aliyunai 包,存放阿里云 AI 相关代码。

aliyunai.model 包下新建数据模型类,可以让 AI 根据官方文档中的请求响应信息自动生成

AI生成代码小技巧1:

第一步,将请求信息交给AI整理

bash 复制代码
创建任务获取任务ID
请求方式:POST
URL:https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting

cURL示例:

bash
curl --location --request POST 'https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting' \
--header "Authorization: Bearer $DASHSCOPE_API_KEY" \
--header 'X-DashScope-Async: enable' \
--header 'Content-Type: application/json' \
--data '{
    "model": "image-out-painting",
    "input": {
        "image_url": "http://xxx/image.jpg"
    },
    "parameters":{
        "x_scale":2,
        "y_scale":2,
        "best_quality":false,
        "limit_image_size":true
    }
}'
请求头(Headers):

参数	类型	必选	说明
Content-Type	string	是	必须设置为 application/json
Authorization	string	是	身份认证(示例:Bearer d1xxx2a)
X-DashScope-Async	string	是	必须设置为 enable
请求体(Request Body)
顶层参数:

字段	类型	必选	说明	示例值
model	string	是	模型名称	image-out-painting
input	object	是	输入图像信息	
parameters	object	是	图像处理参数	
input对象属性:

字段	类型	必选	说明	限制条件
image_url	string	是	图像URL或base64数据	格式:JPG/JPEG/PNG/HEIF/WEBP
大小:≤10MB
分辨率:512×512 ~ 4096×4096像素
单边长度:[512, 4096]像素
parameters对象属性:

字段	类型	必选	说明	默认值	取值范围/可选值	示例
angle	integer	否	逆时针旋转角度(度)	0	[0, 359]	-
output_ratio	string	否	输出图像宽高比	""	["", "1:1", "3:4", "4:3", "9:16", "16:9"]	-
x_scale	float	否	水平方向扩展比例	1.0	[1.0, 3.0]	输入1000×1000 → 设置2.0 → 输出2000×1000
y_scale	float	否	垂直方向扩展比例	1.0	[1.0, 3.0]	输入1000×1000 → 设置2.0 → 输出1000×2000
top_offset	integer	否	图像上方添加像素	0	top_offset + bottom_offset < 3×原图高度	输入1000×1000 → 设置500 → 输出1000×1500
bottom_offset	integer	否	图像下方添加像素	0	top_offset + bottom_offset < 3×原图高度	输入1000×1000 → 设置500 → 输出1000×1500
left_offset	integer	否	图像左侧添加像素	0	left_offset + right_offset < 3×原图宽度	输入1000×1000 → 设置500 → 输出1500×1000
right_offset	integer	否	图像右侧添加像素	0	left_offset + right_offset < 3×原图宽度	输入1000×1000 → 设置500 → 输出1500×1000
best_quality	boolean	否	开启最佳质量模式	false	true/false	true:耗时增加但细节更丰富
limit_image_size	boolean	否	限制输出图像大小	true	true/false	建议保持true(输出≤5MB)
add_watermark	boolean	否	添加AI水印	true	true/false	在左下角添加"Generated by AI"

第二步,让AI生成对应的请求类代码

java 复制代码
@Data
public class CreateOutPaintingTaskRequest implements Serializable {

    /**
     * 模型,例如 "image-out-painting"
     */
    private String model = "image-out-painting";

    /**
     * 输入图像信息
     */
    private Input input;

    /**
     * 图像处理参数
     */
    private Parameters parameters;

    @Data
    public static class Input {
        /**
         * 必选,图像 URL
         */
        @Alias("image_url")
        private String imageUrl;
    }

    @Data
    public static class Parameters implements Serializable {
        /**
         * 可选,逆时针旋转角度,默认值 0,取值范围 [0, 359]
         */
        private Integer angle;

        /**
         * 可选,输出图像的宽高比,默认空字符串,不设置宽高比
         * 可选值:["", "1:1", "3:4", "4:3", "9:16", "16:9"]
         */
        @Alias("output_ratio")
        private String outputRatio;

        /**
         * 可选,图像居中,在水平方向上按比例扩展,默认值 1.0,范围 [1.0, 3.0]
         */
        @Alias("x_scale")
        @JsonProperty("xScale")
        private Float xScale;

        /**
         * 可选,图像居中,在垂直方向上按比例扩展,默认值 1.0,范围 [1.0, 3.0]
         */
        @Alias("y_scale")
        @JsonProperty("yScale")
        private Float yScale;

        /**
         * 可选,在图像上方添加像素,默认值 0
         */
        @Alias("top_offset")
        private Integer topOffset;

        /**
         * 可选,在图像下方添加像素,默认值 0
         */
        @Alias("bottom_offset")
        private Integer bottomOffset;

        /**
         * 可选,在图像左侧添加像素,默认值 0
         */
        @Alias("left_offset")
        private Integer leftOffset;

        /**
         * 可选,在图像右侧添加像素,默认值 0
         */
        @Alias("right_offset")
        private Integer rightOffset;

        /**
         * 可选,开启图像最佳质量模式,默认值 false
         * 若为 true,耗时会成倍增加
         */
        @Alias("best_quality")
        private Boolean bestQuality;

        /**
         * 可选,限制模型生成的图像文件大小,默认值 true
         * - 单边长度 <= 10000:输出图像文件大小限制为 5MB 以下
         * - 单边长度 > 10000:输出图像文件大小限制为 10MB 以下
         */
        @Alias("limit_image_size")
        private Boolean limitImageSize;

        /**
         * 可选,添加 "Generated by AI" 水印,默认值 true
         */
        @Alias("add_watermark")
        private Boolean addWatermark = false;
    }
}

然后重复操作,生成响应类即可


创建扩图任务请求类:

java 复制代码
@Data
public class CreateOutPaintingTaskRequest implements Serializable {

    /**
     * 模型,例如 "image-out-painting"
     */
    private String model = "image-out-painting";

    /**
     * 输入图像信息
     */
    private Input input;

    /**
     * 图像处理参数
     */
    private Parameters parameters;

    @Data
    public static class Input {
        /**
         * 必选,图像 URL
         */
        @Alias("image_url")
        private String imageUrl;
    }

    @Data
    public static class Parameters implements Serializable {
        /**
         * 可选,逆时针旋转角度,默认值 0,取值范围 [0, 359]
         */
        private Integer angle;

        /**
         * 可选,输出图像的宽高比,默认空字符串,不设置宽高比
         * 可选值:["", "1:1", "3:4", "4:3", "9:16", "16:9"]
         */
        @Alias("output_ratio")
        private String outputRatio;

        /**
         * 可选,图像居中,在水平方向上按比例扩展,默认值 1.0,范围 [1.0, 3.0]
         */
        @Alias("x_scale")
        @JsonProperty("xScale")
        private Float xScale;

        /**
         * 可选,图像居中,在垂直方向上按比例扩展,默认值 1.0,范围 [1.0, 3.0]
         */
        @Alias("y_scale")
        @JsonProperty("yScale")
        private Float yScale;

        /**
         * 可选,在图像上方添加像素,默认值 0
         */
        @Alias("top_offset")
        private Integer topOffset;

        /**
         * 可选,在图像下方添加像素,默认值 0
         */
        @Alias("bottom_offset")
        private Integer bottomOffset;

        /**
         * 可选,在图像左侧添加像素,默认值 0
         */
        @Alias("left_offset")
        private Integer leftOffset;

        /**
         * 可选,在图像右侧添加像素,默认值 0
         */
        @Alias("right_offset")
        private Integer rightOffset;

        /**
         * 可选,开启图像最佳质量模式,默认值 false
         * 若为 true,耗时会成倍增加
         */
        @Alias("best_quality")
        private Boolean bestQuality;

        /**
         * 可选,限制模型生成的图像文件大小,默认值 true
         * - 单边长度 <= 10000:输出图像文件大小限制为 5MB 以下
         * - 单边长度 > 10000:输出图像文件大小限制为 10MB 以下
         */
        @Alias("limit_image_size")
        private Boolean limitImageSize;

        /**
         * 可选,添加 "Generated by AI" 水印,默认值 true
         */
        @Alias("add_watermark")
        private Boolean addWatermark = false;
    }
}

创建扩图任务响应类:

java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CreateOutPaintingTaskResponse {

    private Output output;

    /**
     * 表示任务的输出信息
     */
    @Data
    public static class Output {

        /**
         * 任务 ID
         */
        private String taskId;

        /**
         * 任务状态
         * <ul>
         *     <li>PENDING:排队中</li>
         *     <li>RUNNING:处理中</li>
         *     <li>SUSPENDED:挂起</li>
         *     <li>SUCCEEDED:执行成功</li>
         *     <li>FAILED:执行失败</li>
         *     <li>UNKNOWN:任务不存在或状态未知</li>
         * </ul>
         */
        private String taskStatus;
    }

    /**
     * 接口错误码。
     * <p>接口成功请求不会返回该参数。</p>
     */
    private String code;

    /**
     * 接口错误信息。
     * <p>接口成功请求不会返回该参数。</p>
     */
    private String message;

    /**
     * 请求唯一标识。
     * <p>可用于请求明细溯源和问题排查。</p>
     */
    private String requestId;

}

查询任务响应类:

java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GetOutPaintingTaskResponse {

    /**
     * 请求唯一标识
     */
    private String requestId;

    /**
     * 输出信息
     */
    private Output output;

    /**
     * 表示任务的输出信息
     */
    @Data
    public static class Output {

        /**
         * 任务 ID
         */
        private String taskId;

        /**
         * 任务状态
         * <ul>
         *     <li>PENDING:排队中</li>
         *     <li>RUNNING:处理中</li>
         *     <li>SUSPENDED:挂起</li>
         *     <li>SUCCEEDED:执行成功</li>
         *     <li>FAILED:执行失败</li>
         *     <li>UNKNOWN:任务不存在或状态未知</li>
         * </ul>
         */
        private String taskStatus;

        /**
         * 提交时间
         * 格式:YYYY-MM-DD HH:mm:ss.SSS
         */
        private String submitTime;

        /**
         * 调度时间
         * 格式:YYYY-MM-DD HH:mm:ss.SSS
         */
        private String scheduledTime;

        /**
         * 结束时间
         * 格式:YYYY-MM-DD HH:mm:ss.SSS
         */
        private String endTime;

        /**
         * 输出图像的 URL
         */
        private String outputImageUrl;

        /**
         * 接口错误码
         * <p>接口成功请求不会返回该参数</p>
         */
        private String code;

        /**
         * 接口错误信息
         * <p>接口成功请求不会返回该参数</p>
         */
        private String message;

        /**
         * 任务指标信息
         */
        private TaskMetrics taskMetrics;
    }

    /**
     * 表示任务的统计信息
     */
    @Data
    public static class TaskMetrics {

        /**
         * 总任务数
         */
        private Integer total;

        /**
         * 成功任务数
         */
        private Integer succeeded;

        /**
         * 失败任务数
         */
        private Integer failed;
    }
}

5)开发 ؜API 调用类,通⁠过 Hutool ‏的 HTTP 请求‌工具类来调用阿里云‏百炼的 API:

AI生成代码小技巧2:

第一步,把发送请求样例传递给AI

第二步,让AI利用Hutool包发送请求生成代码

java 复制代码
@Component
public class AliyunAiApi {

    @Value("${aliyunAi.apiKey}")
    private String apiKey;

    //创建任务
    // 创建任务地址
    public static final String CREATE_OUT_PAINTING_TASK_URL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting";

    // 查询任务状态
    public static final String GET_OUT_PAINTING_TASK_URL = "https://dashscope.aliyuncs.com/api/v1/tasks/%s";


    public CreateOutPaintingTaskResponse createPaintingTask(CreateOutPaintingTaskRequest request) {
        //1.异常判断
        ThrowUtils.throwIf(request == null, ErrorCode.PARAMS_ERROR);

        // 2. 构建JSON请求体
        String jsonBody = JSONUtil.toJsonStr(request);

        // 3. 发送HTTP请求
        try (HttpResponse response = HttpRequest.post(CREATE_OUT_PAINTING_TASK_URL)
                .header("Authorization", "Bearer " + apiKey)
                .header("X-DashScope-Async", "enable")
                .header("Content-Type", "application/json")
                .body(jsonBody)
                .execute()) {

            // 4. 检查响应状态
            if (!response.isOk()) {
                throw new RuntimeException("请求失败,状态码: " + response.getStatus());
            }

            // 5. 解析响应体
            String body = response.body();
            return JSONUtil.toBean(body, CreateOutPaintingTaskResponse.class);

        } catch (Exception e) {
            throw new RuntimeException("创建扩图任务失败", e);
        }


    }

    /**
     * 查询创建的任务
     *
     * @param taskId
     * @return
     */
    public GetOutPaintingTaskResponse getOutPaintingTask(String taskId) {
        if (StrUtil.isBlank(taskId)) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "任务 id 不能为空");
        }
        try (HttpResponse httpResponse = HttpRequest.get(String.format(GET_OUT_PAINTING_TASK_URL, taskId))
                .header(Header.AUTHORIZATION, "Bearer " + apiKey)
                .execute()) {
            if (!httpResponse.isOk()) {
                throw new BusinessException(ErrorCode.OPERATION_ERROR, "获取任务失败");
            }
            return JSONUtil.toBean(httpResponse.body(), GetOutPaintingTaskResponse.class);
        }
    }
}
2、扩图服务

model.dto.picture 包下新建 AI 扩图请求类,用于接受前端传来的参数并传递给 Service 服务层。字段包括图片 id 和扩图参数:

java 复制代码
@Data
public class CreatePictureOutPaintingTaskRequest implements Serializable {

    /**
     * 图片 id
     */
    private Long pictureId;

    /**
     * 扩图参数
     */
    private CreateOutPaintingTaskRequest.Parameters parameters;

    private static final long serialVersionUID = 1L;
}

在图片服务中编写创؜建扩图任务方法,从数据库中获取图片⁠信息和 url 地址,构造请求参数‏后调用 api 创建扩图任务。注意‌,如果图片有空间 id,则需要校验‏权限,直接复用以前的权限校验方法。

java 复制代码
@Override
public CreateOutPaintingTaskResponse createPictureOutPaintingTask(CreatePictureOutPaintingTaskRequest createPictureOutPaintingTaskRequest, User loginUser) {
    // 获取图片信息
    Long pictureId = createPictureOutPaintingTaskRequest.getPictureId();
    Picture picture = Optional.ofNullable(this.getById(pictureId))
            .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND_ERROR));
    // 权限校验
    checkPictureAuth(loginUser, picture);
    // 构造请求参数
    CreateOutPaintingTaskRequest taskRequest = new CreateOutPaintingTaskRequest();
    CreateOutPaintingTaskRequest.Input input = new CreateOutPaintingTaskRequest.Input();
    input.setImageUrl(picture.getUrl());
    taskRequest.setInput(input);
    BeanUtil.copyProperties(createPictureOutPaintingTaskRequest, taskRequest);
    // 创建任务
    return aliYunAiApi.createOutPaintingTask(taskRequest);
}
3、扩图接口

在 Pictu؜reController 添⁠加 AI 扩图接口,包括创建‏任务和查询任务状态接口:  ‌

java 复制代码
/**
 * 创建 AI 扩图任务
 */
@PostMapping("/out_painting/create_task")
public BaseResponse<CreateOutPaintingTaskResponse> createPictureOutPaintingTask(
        @RequestBody CreatePictureOutPaintingTaskRequest createPictureOutPaintingTaskRequest,
        HttpServletRequest request) {
    if (createPictureOutPaintingTaskRequest == null || createPictureOutPaintingTaskRequest.getPictureId() == null) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR);
    }
    User loginUser = userService.getLoginUser(request);
    CreateOutPaintingTaskResponse response = pictureService.createPictureOutPaintingTask(createPictureOutPaintingTaskRequest, loginUser);
    return ResultUtils.success(response);
}

/**
 * 查询 AI 扩图任务
 */
@GetMapping("/out_painting/get_task")
public BaseResponse<GetOutPaintingTaskResponse> getPictureOutPaintingTask(String taskId) {
    ThrowUtils.throwIf(StrUtil.isBlank(taskId), ErrorCode.PARAMS_ERROR);
    GetOutPaintingTaskResponse task = aliYunAiApi.getOutPaintingTask(taskId);
    return ResultUtils.success(task);
}

使用Swagger接口进行测试,成功!

相关推荐
Warren9810 分钟前
Vue2博客项目笔记(第一天)
java·开发语言·javascript·vue.js·windows·笔记·ecmascript
Charles Wesley10 分钟前
11:java学习笔记:1D array(1维数组)
java·笔记·学习·eclipse
王者鳜錸42 分钟前
VUE+SPRINGBOOT从0-1打造前后端-前后台系统-邮箱重置密码
前端·vue.js·spring boot
stillaliveQEJ2 小时前
【JavaEE】多线程(一)
java·java-ee
present--012 小时前
【JAVA EE初阶】多线程(下)
java·java-ee
jakeswang2 小时前
去哪儿StarRocks实践
starrocks·后端
可曾去过倒悬山2 小时前
SpringMvc跨域配置方法详解
spring·mvc
字节逆旅3 小时前
从一次爬坑看前端的出路
前端·后端·程序员
2501_924879363 小时前
口罩识别场景误报率↓79%:陌讯多模态融合算法实战解析
人工智能·深度学习·算法·目标检测·智慧城市
万粉变现经纪人3 小时前
如何解决pip安装报错ModuleNotFoundError: No module named ‘keras’问题
人工智能·python·深度学习·scrapy·pycharm·keras·pip