Commit 4c3add508b75120b60ca3e091820bf60c39ee5b3

Authored by YunaiV
1 parent 098483d2

【代码优化】AI:MJ 生成图片 ACTION 的优化

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 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 9 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO;
10 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 13 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
13 14 import cn.iocoder.yudao.module.ai.service.image.AiImageService;
14 15 import io.swagger.v3.oas.annotations.Operation;
... ... @@ -67,30 +68,24 @@ public class AiImageController {
67 68  
68 69 @Operation(summary = "【Midjourney】生成图片")
69 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 72 Long imageId = imageService.midjourneyImagine(getLoginUserId(), reqVO);
76 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 78 @PermitAll
82   - public void midjourneyNotify(@RequestBody MidjourneyApi.Notify notify) {
  79 + public CommonResult<Boolean> midjourneyNotify(@Validated @RequestBody MidjourneyApi.Notify notify) {
83 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 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 9  
10 10 @Schema(description = "管理后台 - 绘画生成(Midjourney) Request VO")
11 11 @Data
12   -public class AiImageMidjourneyImagineReqVO {
  12 +public class AiMidjourneyImagineReqVO {
13 13  
14 14 @Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "中国神龙")
15 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 4 import cn.iocoder.yudao.framework.common.pojo.PageParam;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 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 9 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
9 10  
10 11 /**
... ... @@ -57,7 +58,7 @@ public interface AiImageService {
57 58 * @param reqVO 绘制请求
58 59 * @return 绘画编号
59 60 */
60   - Long midjourneyImagine(Long userId, AiImageMidjourneyImagineReqVO reqVO);
  61 + Long midjourneyImagine(Long userId, AiMidjourneyImagineReqVO reqVO);
61 62  
62 63 /**
63 64 * 【Midjourney】同步图片进展
... ... @@ -74,13 +75,12 @@ public interface AiImageService {
74 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 14 import cn.iocoder.yudao.framework.common.pojo.PageResult;
15 15 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
16 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 19 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
19 20 import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper;
20 21 import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum;
... ... @@ -149,7 +150,7 @@ public class AiImageServiceImpl implements AiImageService {
149 150  
150 151 @Override
151 152 @Transactional(rollbackFor = Exception.class)
152   - public Long midjourneyImagine(Long userId, AiImageMidjourneyImagineReqVO reqVO) {
  153 + public Long midjourneyImagine(Long userId, AiMidjourneyImagineReqVO reqVO) {
153 154 // 1. 保存数据库
154 155 AiImageDO image = BeanUtils.toBean(reqVO, AiImageDO.class).setUserId(userId).setPublicStatus(false)
155 156 .setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus())
... ... @@ -170,11 +171,8 @@ public class AiImageServiceImpl implements AiImageService {
170 171 }
171 172  
172 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 176 return image.getId();
179 177 }
180 178  
... ... @@ -245,49 +243,36 @@ public class AiImageServiceImpl implements AiImageService {
245 243 }
246 244  
247 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 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 /**
... ...