智能图库模块-Ai图片编辑
本来该功能应该昨天搞完的,但是昨天上午以及下午莫名其妙地去捣鼓了windows terminal和starship美化终端,而且还搞着搞着样式搞出了问题,然后发现win11自带Windows terminal,不需要自行安装,我又升级系统等了一堆时间,属于是纯纯给自己没事找事做了,不过美化后的Windows terminal还是挺好看的捏:
今天上午刷了一小时面试鸭后,下午就投入到Ai图片编辑模块的学习了。
项目中的Ai扩图采用的阿里云百炼大模型来实现ai编辑的功能,由于官方文档中对于Java SDK的介绍比较少,因此采用http调用的方式,在官方文档中提到:
为了减少等待时间并且避免请求超时,服务采用异步方式提供。您需要发起两个请求: 创建任务:首先发送一个请求创建扩图任务,该请求会返回任务ID。 即:POST https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting 根据任务ID查询结果:使用上一步获得的任务ID,查询模型生成的结果。 即:GET https://dashscope.aliyuncs.com/api/v1/tasks/{task_id}
也就是我们要发两次http请求才能获得生成后的图片,在官方文档中有给到请求头和请求参数,分别把两个请求的请求头和请求参数丢给Ai来给我们生成Java实体类代码,节省时间。调用Api需要Apikey,因此记得在配置文件上配上Apikey。
跟之前写以图搜图的思路一样,在api目录下创建aliYunAi来存放与该api相关的实体类和方法,创建AliYunAiApi来编写相关代码:
▼java复制代码@Slf4j @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 createOutPaintingTask(CreateOutPaintingTaskRequest createOutPaintingTaskRequest){ if (createOutPaintingTaskRequest == null){ throw new BusinessException(ErrorCode.OPERATION_ERROR,"扩图参数为空"); } //发送请求 HttpRequest request = HttpRequest.post(CREATE_OUT_PAINTING_TASK_URL) .header("Authorization", "Bearer " + apiKey) //开启异步处理 .header("X-DashScope-Async", "enable") .header("Content-Type", "application/json") .body(JSONUtil.toJsonStr(createOutPaintingTaskRequest)); //处理响应 try(HttpResponse httpResponse = request.execute()){ if (!httpResponse.isOk()){ log.error("请求异常:{}",httpResponse.body()); throw new BusinessException(ErrorCode.OPERATION_ERROR,"AI扩图失败"); } CreateOutPaintingTaskResponse createOutPaintingTaskResponse = JSONUtil.toBean(httpResponse.body(), CreateOutPaintingTaskResponse.class); if (createOutPaintingTaskResponse.getCode() != null){ String errorMessage = createOutPaintingTaskResponse.getMessage(); log.error("请求异常:{}",errorMessage); throw new BusinessException(ErrorCode.OPERATION_ERROR,"AI扩图失败 " + errorMessage); } return createOutPaintingTaskResponse; } } /** * 查询创建的任务结果 * @param taskId * @return */ public GetOutPaintingTaskResponse getOutPaintingTask(String taskId){ if (taskId == null){ throw new BusinessException(ErrorCode.OPERATION_ERROR,"任务id为空"); } String url = String.format(GET_OUT_PAINTING_TASK_URL,taskId); //处理响应 try(HttpResponse httpResponse = HttpRequest.get(url) .header("Authorization", "Bearer " + apiKey) .execute()){ if (!httpResponse.isOk()){ log.error("请求异常:{}",httpResponse.body()); throw new BusinessException(ErrorCode.OPERATION_ERROR,"获取任务结果失败"); } return JSONUtil.toBean(httpResponse.body(), GetOutPaintingTaskResponse.class); } } }
其中CreateOutPaintingTaskRequest里有两个变量xScale和yScale需要加多一个@JsonProperty的注解,因为Jackson对于第二个字母是大写的参数无法映射,前端如果穿了这两个参数会无法赋值到相应的字段。
前端部分页面的设计不多作记录,因为该请求是需要异步调用的,所以需要对请求进行轮询,为了不让后端因为轮询而导致请求还没完成时导致任务阻塞资源耗尽,采用前端轮询的方式来进行调用,即前端每隔几秒向后端发送请求。像之前编辑图片一样也是采用弹窗组件:
▼vue复制代码<a-modal class="image-out-painting" v-model:open="visible" title="AI 扩图" :footer="false" @cancel="closeModal" > <a-row gutter="16"> <a-col span="12"> <h4>原始图片</h4> <img :src="picture?.url" :alt="picture?.name" style="max-width: 100%" /> </a-col> <a-col span="12"> <h4>扩图结果</h4> <img v-if="resultImageUrl" :src="resultImageUrl" :alt="picture?.name" style="max-width: 100%" /> </a-col> </a-row> <div style="margin-bottom: 16px" /> <a-flex justify="center" gap="16"> <a-button type="primary" :loading="!!taskId" @click="createTask" ghost>生成图片</a-button> <a-button v-if="resultImageUrl" type="primary" :loading="uploading" @click="handleUpload">应用结果</a-button> </a-flex> </a-modal>
在createTask中,编写了创建任务的请求:
▼ts复制代码const createTask = async () => { if (!props.picture?.id) { return } const res = await createPictureOutPaintingTaskUsingPost({ pictureId: props.picture.id, //根据需要设置扩图参数 parameters: { xScale: 2, yScale: 2 } }) if (res.data.code === 0 && res.data.data) { message.success('创建任务成功,耐心等待,请不要退出') console.log(res.data.data.output?.taskId)//打印任务ID taskId.value = res.data.data.output?.taskId //开启轮询 startPolling() } else { message.error('创建任务失败,' + res.data.msg) } }
这里面的starPolling()就是轮询相关的代码,要特别注意的是,在启动轮询后无论任务成功还是失败都要记得及时关闭轮询,有异常也要关闭,不然出问题时难以排查出来:
▼ts复制代码//轮询定时器 let pollingTimer: NodeJs.Timeout = null const startPolling = () => { if (!taskId.value){ return } pollingTimer = setInterval(async () => { try { const res = await getPictureOutPaintingTaskUsingGet({ taskId: taskId.value }) if (res.data.code === 0 && res.data.data) { const taskResult = res.data.data.output if (taskResult?.taskStatus === 'SUCCEEDED'){ message.success('扩图任务执行成功') resultImageUrl.value = taskResult?.outputImageUrl clearPolling() }else if (taskResult?.taskStatus === 'FAILED'){ message.error('扩图任务执行失败') clearPolling() } } }catch (error){ console.log('扩图任务轮询失败',error) message.error('检测任务失败 请稍后重试') clearPolling() } }, 3000)//每3s轮询一次 } //清理轮询 const clearPolling = () => { if (pollingTimer){ clearInterval(pollingTimer) pollingTimer = null taskId.value = null } } onMounted(() =>{ clearPolling() })
这些便是本次学习对我来说有所收获的记录。
