Commit 4c3add508b75120b60ca3e091820bf60c39ee5b3
1 parent
098483d2
【代码优化】AI:MJ 生成图片 ACTION 的优化
Showing
6 changed files
with
72 additions
and
103 deletions
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java
| @@ -8,7 +8,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | @@ -8,7 +8,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 8 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 8 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 9 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO; | 9 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO; |
| 10 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageRespVO; | 10 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageRespVO; |
| 11 | -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiImageMidjourneyImagineReqVO; | 11 | +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO; |
| 12 | +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO; | ||
| 12 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; | 13 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; |
| 13 | import cn.iocoder.yudao.module.ai.service.image.AiImageService; | 14 | import cn.iocoder.yudao.module.ai.service.image.AiImageService; |
| 14 | import io.swagger.v3.oas.annotations.Operation; | 15 | import io.swagger.v3.oas.annotations.Operation; |
| @@ -67,30 +68,24 @@ public class AiImageController { | @@ -67,30 +68,24 @@ public class AiImageController { | ||
| 67 | 68 | ||
| 68 | @Operation(summary = "【Midjourney】生成图片") | 69 | @Operation(summary = "【Midjourney】生成图片") |
| 69 | @PostMapping("/midjourney/imagine") | 70 | @PostMapping("/midjourney/imagine") |
| 70 | - public CommonResult<Long> midjourneyImagine(@Validated @RequestBody AiImageMidjourneyImagineReqVO reqVO) { | ||
| 71 | - if (true) { | ||
| 72 | - imageService.midjourneySync(); | ||
| 73 | - return null; | ||
| 74 | - } | 71 | + public CommonResult<Long> midjourneyImagine(@Validated @RequestBody AiMidjourneyImagineReqVO reqVO) { |
| 75 | Long imageId = imageService.midjourneyImagine(getLoginUserId(), reqVO); | 72 | Long imageId = imageService.midjourneyImagine(getLoginUserId(), reqVO); |
| 76 | return success(imageId); | 73 | return success(imageId); |
| 77 | } | 74 | } |
| 78 | 75 | ||
| 79 | - @Operation(summary = "Midjourney 生成图片的回调通知", description = "由 Midjourney Proxy 回调") | ||
| 80 | - @PostMapping("/midjourney-notify") | 76 | + @Operation(summary = "【Midjourney】通知图片进展", description = "由 Midjourney Proxy 回调") |
| 77 | + @PostMapping("/midjourney/notify") // 必须是 POST 方法,否则会报错 | ||
| 81 | @PermitAll | 78 | @PermitAll |
| 82 | - public void midjourneyNotify(@RequestBody MidjourneyApi.Notify notify) { | 79 | + public CommonResult<Boolean> midjourneyNotify(@Validated @RequestBody MidjourneyApi.Notify notify) { |
| 83 | imageService.midjourneyNotify(notify); | 80 | imageService.midjourneyNotify(notify); |
| 81 | + return success(true); | ||
| 84 | } | 82 | } |
| 85 | 83 | ||
| 86 | - @Operation(summary = "Midjourney Action", description = "例如说:放大、缩小、U1、U2 等") | ||
| 87 | - @GetMapping("/midjourney/action") | ||
| 88 | - @Parameter(name = "id", description = "图片id", example = "1") | ||
| 89 | - @Parameter(name = "customId", description = "操作id", example = "MJ::JOB::upsample::1::85a4b4c1-8835-46c5-a15c-aea34fad1862") | ||
| 90 | - public CommonResult<Boolean> midjourneyAction(@RequestParam("id") Long imageId, | ||
| 91 | - @RequestParam("customId") String customId) { | ||
| 92 | - imageService.midjourneyAction(getLoginUserId(), imageId, customId); | ||
| 93 | - return success(true); | 84 | + @Operation(summary = "【Midjourney】Action 操作(二次生成图片)", description = "例如说:放大、缩小、U1、U2 等") |
| 85 | + @PostMapping("/midjourney/action") | ||
| 86 | + public CommonResult<Long> midjourneyAction(@Validated @RequestBody AiMidjourneyActionReqVO reqVO) { | ||
| 87 | + Long imageId = imageService.midjourneyAction(getLoginUserId(), reqVO); | ||
| 88 | + return success(imageId); | ||
| 94 | } | 89 | } |
| 95 | 90 | ||
| 96 | } | 91 | } |
| 97 | \ No newline at end of file | 92 | \ No newline at end of file |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/MidjourneyActionReqVO.java deleted
| 1 | -package cn.iocoder.yudao.module.ai.controller.admin.image.vo; | ||
| 2 | - | ||
| 3 | -import io.swagger.v3.oas.annotations.media.Schema; | ||
| 4 | -import jakarta.validation.constraints.NotNull; | ||
| 5 | -import lombok.Data; | ||
| 6 | - | ||
| 7 | -/** | ||
| 8 | - * Midjourney:action 请求 | ||
| 9 | - * | ||
| 10 | - * @author fansili | ||
| 11 | - * @time 2024/5/30 14:02 | ||
| 12 | - * @since 1.0 | ||
| 13 | - */ | ||
| 14 | -@Data | ||
| 15 | -public class MidjourneyActionReqVO { | ||
| 16 | - | ||
| 17 | - @Schema(description = "操作按钮id", required = true) | ||
| 18 | - @NotNull(message = "customId 不能为空!") | ||
| 19 | - private String customId; | ||
| 20 | - | ||
| 21 | - @Schema(description = "操作按钮id", required = true) | ||
| 22 | - @NotNull(message = "customId 不能为空!") | ||
| 23 | - private String taskId; | ||
| 24 | - | ||
| 25 | - @Schema(description = "通知地址", required = false) | ||
| 26 | - @NotNull(message = "回调地址不能为空!") | ||
| 27 | - private String notifyHook; | ||
| 28 | - | ||
| 29 | - @Schema(description = "自定义参数", required = false) | ||
| 30 | - private String state; | ||
| 31 | -} |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/midjourney/AiMidjourneyActionReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney; | ||
| 2 | + | ||
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 4 | +import jakarta.validation.constraints.NotEmpty; | ||
| 5 | +import jakarta.validation.constraints.NotNull; | ||
| 6 | +import lombok.Data; | ||
| 7 | + | ||
| 8 | +@Schema(description = "管理后台 - Action(Midjourney) Request VO") | ||
| 9 | +@Data | ||
| 10 | +public class AiMidjourneyActionReqVO { | ||
| 11 | + | ||
| 12 | + @Schema(description = "图片编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | ||
| 13 | + @NotNull(message = "图片编号不能为空") | ||
| 14 | + private Long id; | ||
| 15 | + | ||
| 16 | + @Schema(description = "操作按钮编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "MJ::JOB::variation::4::06aa3e66-0e97-49cc-8201-e0295d883de4") | ||
| 17 | + @NotEmpty(message = "操作按钮编号不能为空") | ||
| 18 | + private String customId; | ||
| 19 | + | ||
| 20 | +} |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/midjourney/AiImageMidjourneyImagineReqVO.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/midjourney/AiMidjourneyImagineReqVO.java
| @@ -9,7 +9,7 @@ import java.util.List; | @@ -9,7 +9,7 @@ import java.util.List; | ||
| 9 | 9 | ||
| 10 | @Schema(description = "管理后台 - 绘画生成(Midjourney) Request VO") | 10 | @Schema(description = "管理后台 - 绘画生成(Midjourney) Request VO") |
| 11 | @Data | 11 | @Data |
| 12 | -public class AiImageMidjourneyImagineReqVO { | 12 | +public class AiMidjourneyImagineReqVO { |
| 13 | 13 | ||
| 14 | @Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "中国神龙") | 14 | @Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "中国神龙") |
| 15 | @NotEmpty(message = "提示词不能为空!") | 15 | @NotEmpty(message = "提示词不能为空!") |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java
| @@ -4,7 +4,8 @@ import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; | @@ -4,7 +4,8 @@ import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; | ||
| 4 | import cn.iocoder.yudao.framework.common.pojo.PageParam; | 4 | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO; | 6 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO; |
| 7 | -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiImageMidjourneyImagineReqVO; | 7 | +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO; |
| 8 | +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO; | ||
| 8 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; | 9 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; |
| 9 | 10 | ||
| 10 | /** | 11 | /** |
| @@ -57,7 +58,7 @@ public interface AiImageService { | @@ -57,7 +58,7 @@ public interface AiImageService { | ||
| 57 | * @param reqVO 绘制请求 | 58 | * @param reqVO 绘制请求 |
| 58 | * @return 绘画编号 | 59 | * @return 绘画编号 |
| 59 | */ | 60 | */ |
| 60 | - Long midjourneyImagine(Long userId, AiImageMidjourneyImagineReqVO reqVO); | 61 | + Long midjourneyImagine(Long userId, AiMidjourneyImagineReqVO reqVO); |
| 61 | 62 | ||
| 62 | /** | 63 | /** |
| 63 | * 【Midjourney】同步图片进展 | 64 | * 【Midjourney】同步图片进展 |
| @@ -74,13 +75,12 @@ public interface AiImageService { | @@ -74,13 +75,12 @@ public interface AiImageService { | ||
| 74 | void midjourneyNotify(MidjourneyApi.Notify notify); | 75 | void midjourneyNotify(MidjourneyApi.Notify notify); |
| 75 | 76 | ||
| 76 | /** | 77 | /** |
| 77 | - * midjourney - action(放大、缩小、U1、U2...) | 78 | + * 【Midjourney】Action 操作(放大、缩小、U1、U2...) |
| 78 | * | 79 | * |
| 79 | - * @param loginUserId | ||
| 80 | - * @param imageId | ||
| 81 | - * @param customId | ||
| 82 | - * @return | 80 | + * @param userId 用户编号 |
| 81 | + * @param reqVO 绘制请求 | ||
| 82 | + * @return 绘画编号 | ||
| 83 | */ | 83 | */ |
| 84 | - void midjourneyAction(Long loginUserId, Long imageId, String customId); | 84 | + Long midjourneyAction(Long userId, AiMidjourneyActionReqVO reqVO); |
| 85 | 85 | ||
| 86 | } | 86 | } |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
| @@ -14,7 +14,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; | @@ -14,7 +14,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; | ||
| 14 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 14 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 15 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 15 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 16 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO; | 16 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO; |
| 17 | -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiImageMidjourneyImagineReqVO; | 17 | +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO; |
| 18 | +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO; | ||
| 18 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; | 19 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; |
| 19 | import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper; | 20 | import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper; |
| 20 | import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum; | 21 | import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum; |
| @@ -149,7 +150,7 @@ public class AiImageServiceImpl implements AiImageService { | @@ -149,7 +150,7 @@ public class AiImageServiceImpl implements AiImageService { | ||
| 149 | 150 | ||
| 150 | @Override | 151 | @Override |
| 151 | @Transactional(rollbackFor = Exception.class) | 152 | @Transactional(rollbackFor = Exception.class) |
| 152 | - public Long midjourneyImagine(Long userId, AiImageMidjourneyImagineReqVO reqVO) { | 153 | + public Long midjourneyImagine(Long userId, AiMidjourneyImagineReqVO reqVO) { |
| 153 | // 1. 保存数据库 | 154 | // 1. 保存数据库 |
| 154 | AiImageDO image = BeanUtils.toBean(reqVO, AiImageDO.class).setUserId(userId).setPublicStatus(false) | 155 | AiImageDO image = BeanUtils.toBean(reqVO, AiImageDO.class).setUserId(userId).setPublicStatus(false) |
| 155 | .setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus()) | 156 | .setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus()) |
| @@ -170,11 +171,8 @@ public class AiImageServiceImpl implements AiImageService { | @@ -170,11 +171,8 @@ public class AiImageServiceImpl implements AiImageService { | ||
| 170 | } | 171 | } |
| 171 | 172 | ||
| 172 | // 4. 情况二【成功】:更新 taskId 和参数 | 173 | // 4. 情况二【成功】:更新 taskId 和参数 |
| 173 | - imageMapper.updateById(new AiImageDO() | ||
| 174 | - .setId(image.getId()) | ||
| 175 | - .setTaskId(imagineResponse.result()) | ||
| 176 | - .setOptions(BeanUtil.beanToMap(reqVO)) | ||
| 177 | - ); | 174 | + imageMapper.updateById(new AiImageDO().setId(image.getId()) |
| 175 | + .setTaskId(imagineResponse.result()).setOptions(BeanUtil.beanToMap(reqVO))); | ||
| 178 | return image.getId(); | 176 | return image.getId(); |
| 179 | } | 177 | } |
| 180 | 178 | ||
| @@ -245,49 +243,36 @@ public class AiImageServiceImpl implements AiImageService { | @@ -245,49 +243,36 @@ public class AiImageServiceImpl implements AiImageService { | ||
| 245 | } | 243 | } |
| 246 | 244 | ||
| 247 | @Override | 245 | @Override |
| 248 | - public void midjourneyAction(Long loginUserId, Long imageId, String customId) { | ||
| 249 | - // 1、检查 image | ||
| 250 | - AiImageDO image = validateImageExists(imageId); | ||
| 251 | - // 2、检查 customId | ||
| 252 | - validateCustomId(customId, image.getButtons()); | ||
| 253 | - | ||
| 254 | - // 3、调用 midjourney proxy | ||
| 255 | - MidjourneyApi.SubmitResponse submitResponse = midjourneyApi.action( | ||
| 256 | - new MidjourneyApi.ActionRequest(customId, image.getTaskId(), midjourneyNotifyUrl)); | ||
| 257 | - // 4、检查错误 code (状态码: 1(提交成功), 21(已存在), 22(排队中), other(错误)) | ||
| 258 | - if (!MidjourneyApi.SubmitCodeEnum.SUCCESS_CODES.contains(submitResponse.code())) { | ||
| 259 | - throw exception(AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL, submitResponse.description()); | 246 | + public Long midjourneyAction(Long userId, AiMidjourneyActionReqVO reqVO) { |
| 247 | + // 1.1 检查 image | ||
| 248 | + AiImageDO image = validateImageExists(reqVO.getId()); | ||
| 249 | + if (ObjUtil.notEqual(userId, image.getUserId())) { | ||
| 250 | + throw exception(AI_IMAGE_NOT_EXISTS); | ||
| 251 | + } | ||
| 252 | + // 1.2 检查 customId | ||
| 253 | + MidjourneyApi.Button button = CollUtil.findOne(image.getButtons(), | ||
| 254 | + buttonX -> buttonX.customId().equals(reqVO.getCustomId())); | ||
| 255 | + if (button == null) { | ||
| 256 | + throw exception(AI_IMAGE_CUSTOM_ID_NOT_EXISTS); | ||
| 260 | } | 257 | } |
| 261 | 258 | ||
| 262 | - // 5、新增 image 记录(根据 image 新增一个) | ||
| 263 | - AiImageDO newImage = new AiImageDO(); | ||
| 264 | - newImage.setUserId(image.getUserId()); | ||
| 265 | - newImage.setPrompt(image.getPrompt()); | ||
| 266 | - | ||
| 267 | - newImage.setPlatform(image.getPlatform()); | ||
| 268 | - newImage.setModel(image.getModel()); | ||
| 269 | - newImage.setWidth(image.getWidth()); | ||
| 270 | - newImage.setHeight(image.getHeight()); | ||
| 271 | - | ||
| 272 | - newImage.setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus()); | ||
| 273 | - newImage.setPublicStatus(image.getPublicStatus()); | 259 | + // 2. 调用 Midjourney Proxy 提交任务 |
| 260 | + MidjourneyApi.SubmitResponse actionResponse = midjourneyApi.action( | ||
| 261 | + new MidjourneyApi.ActionRequest(button.customId(), image.getTaskId(), midjourneyNotifyUrl)); | ||
| 262 | + if (!MidjourneyApi.SubmitCodeEnum.SUCCESS_CODES.contains(actionResponse.code())) { | ||
| 263 | + String description = actionResponse.description().contains("quota_not_enough") ? | ||
| 264 | + "账户余额不足" : actionResponse.description(); | ||
| 265 | + throw exception(AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL, description); | ||
| 266 | + } | ||
| 274 | 267 | ||
| 275 | - newImage.setOptions(image.getOptions()); | ||
| 276 | - newImage.setTaskId(submitResponse.result()); | 268 | + // 3. 新增 image 记录 |
| 269 | + AiImageDO newImage = new AiImageDO().setUserId(image.getUserId()).setPublicStatus(false).setPrompt(image.getPrompt()) | ||
| 270 | + .setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus()) | ||
| 271 | + .setPlatform(AiPlatformEnum.MIDJOURNEY.getPlatform()) | ||
| 272 | + .setModel(image.getModel()).setWidth(image.getWidth()).setHeight(image.getHeight()) | ||
| 273 | + .setOptions(image.getOptions()).setTaskId(actionResponse.result()); | ||
| 277 | imageMapper.insert(newImage); | 274 | imageMapper.insert(newImage); |
| 278 | - } | ||
| 279 | - | ||
| 280 | - private static void validateCustomId(String customId, List<MidjourneyApi.Button> buttons) { | ||
| 281 | - boolean isTrue = false; | ||
| 282 | - for (MidjourneyApi.Button button : buttons) { | ||
| 283 | - if (button.customId().equals(customId)) { | ||
| 284 | - isTrue = true; | ||
| 285 | - break; | ||
| 286 | - } | ||
| 287 | - } | ||
| 288 | - if (!isTrue) { | ||
| 289 | - throw exception(AI_IMAGE_CUSTOM_ID_NOT_EXISTS); | ||
| 290 | - } | 275 | + return newImage.getId(); |
| 291 | } | 276 | } |
| 292 | 277 | ||
| 293 | /** | 278 | /** |