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,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 /**