Commit 56e8707e387736294cf431425e978fdc2575bd3a

Authored by cherishsince
1 parent 36b6fee0

【增加】Midjourney Proxy 回调通知

yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/client/vo/MidjourneyNotifyVO.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/client/vo/MidjourneyNotifyReqVO.java
@@ -11,7 +11,10 @@ import lombok.Data; @@ -11,7 +11,10 @@ import lombok.Data;
11 * @since 1.0 11 * @since 1.0
12 */ 12 */
13 @Data 13 @Data
14 -public class MidjourneyNotifyVO { 14 +public class MidjourneyNotifyReqVO {
  15 +
  16 + @Schema(description = "job id")
  17 + private String id;
15 18
16 @Schema(description = "任务类型") 19 @Schema(description = "任务类型")
17 private MidjourneyTaskActionEnum action; 20 private MidjourneyTaskActionEnum action;
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.image; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.image;
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.ai.client.vo.MidjourneyNotifyReqVO;
6 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallReqVO; 7 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallReqVO;
7 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageListReqVO; 8 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageListReqVO;
8 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyImagineReqVO; 9 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyImagineReqVO;
@@ -13,7 +14,6 @@ import io.swagger.v3.oas.annotations.Operation; @@ -13,7 +14,6 @@ import io.swagger.v3.oas.annotations.Operation;
13 import io.swagger.v3.oas.annotations.Parameter; 14 import io.swagger.v3.oas.annotations.Parameter;
14 import io.swagger.v3.oas.annotations.tags.Tag; 15 import io.swagger.v3.oas.annotations.tags.Tag;
15 import jakarta.annotation.Resource; 16 import jakarta.annotation.Resource;
16 -import jakarta.servlet.http.HttpServletRequest;  
17 import lombok.extern.slf4j.Slf4j; 17 import lombok.extern.slf4j.Slf4j;
18 import org.springframework.validation.annotation.Validated; 18 import org.springframework.validation.annotation.Validated;
19 import org.springframework.web.bind.annotation.*; 19 import org.springframework.web.bind.annotation.*;
@@ -74,9 +74,9 @@ public class AiImageController { @@ -74,9 +74,9 @@ public class AiImageController {
74 return success(aiImageService.deleteIdMy(id, getLoginUserId())); 74 return success(aiImageService.deleteIdMy(id, getLoginUserId()));
75 } 75 }
76 76
77 - @Operation(summary = "删除【我的】绘画记录") 77 + @Operation(summary = "midjourney proxy - 回调通知")
78 @RequestMapping("/midjourney-notify") 78 @RequestMapping("/midjourney-notify")
79 - public CommonResult<Boolean> midjourneyNotify(HttpServletRequest request) {  
80 - return success(true); 79 + public CommonResult<Boolean> midjourneyNotify(MidjourneyNotifyReqVO notifyReqVO) {
  80 + return success(aiImageService.midjourneyNotify(getLoginUserId(), notifyReqVO));
81 } 81 }
82 } 82 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/image/AiImageDO.java
@@ -28,6 +28,9 @@ public class AiImageDO extends BaseDO { @@ -28,6 +28,9 @@ public class AiImageDO extends BaseDO {
28 @Schema(description = "用户编号") 28 @Schema(description = "用户编号")
29 private Long userId; 29 private Long userId;
30 30
  31 + @Schema(description = "midjourney proxy 关联的 job id")
  32 + private String jobId;
  33 +
31 @Schema(description = "提示词") 34 @Schema(description = "提示词")
32 private String prompt; 35 private String prompt;
33 36
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/image/AiImageMapper.java
@@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; @@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; 5 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
6 import org.apache.ibatis.annotations.Mapper; 6 import org.apache.ibatis.annotations.Mapper;
7 -import org.springframework.stereotype.Repository;  
8 7
9 /** 8 /**
10 * AI 绘图 Mapper 9 * AI 绘图 Mapper
@@ -26,4 +25,14 @@ public interface AiImageMapper extends BaseMapperX&lt;AiImageDO&gt; { @@ -26,4 +25,14 @@ public interface AiImageMapper extends BaseMapperX&lt;AiImageDO&gt; {
26 return; 25 return;
27 } 26 }
28 27
  28 + /**
  29 + * 查询 - 根据 job id
  30 + *
  31 + * @param id
  32 + * @return
  33 + */
  34 + default AiImageDO selectByJobId(String id) {
  35 + return this.selectOne(new LambdaQueryWrapperX<AiImageDO>().eq(AiImageDO::getJobId, id));
  36 + }
  37 +
29 } 38 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java
1 package cn.iocoder.yudao.module.ai.service.image; 1 package cn.iocoder.yudao.module.ai.service.image;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.ai.client.vo.MidjourneyNotifyReqVO;
4 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallReqVO; 5 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallReqVO;
5 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageListReqVO; 6 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageListReqVO;
6 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyImagineReqVO; 7 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyImagineReqVO;
@@ -65,4 +66,12 @@ public interface AiImageService { @@ -65,4 +66,12 @@ public interface AiImageService {
65 */ 66 */
66 Boolean deleteIdMy(Long id, Long loginUserId); 67 Boolean deleteIdMy(Long id, Long loginUserId);
67 68
  69 + /**
  70 + * midjourney proxy - 回调通知
  71 + *
  72 + * @param loginUserId
  73 + * @param notifyReqVO
  74 + * @return
  75 + */
  76 + Boolean midjourneyNotify(Long loginUserId, MidjourneyNotifyReqVO notifyReqVO);
68 } 77 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
1 package cn.iocoder.yudao.module.ai.service.image; 1 package cn.iocoder.yudao.module.ai.service.image;
2 2
  3 +import cn.hutool.core.bean.BeanUtil;
  4 +import cn.hutool.core.util.StrUtil;
3 import cn.hutool.http.HttpUtil; 5 import cn.hutool.http.HttpUtil;
4 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 6 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
5 import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageModelEnum; 7 import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageModelEnum;
@@ -14,9 +16,14 @@ import cn.iocoder.yudao.module.ai.ErrorCodeConstants; @@ -14,9 +16,14 @@ import cn.iocoder.yudao.module.ai.ErrorCodeConstants;
14 import cn.iocoder.yudao.module.ai.client.MidjourneyProxyClient; 16 import cn.iocoder.yudao.module.ai.client.MidjourneyProxyClient;
15 import cn.iocoder.yudao.module.ai.client.enums.MidjourneyModelEnum; 17 import cn.iocoder.yudao.module.ai.client.enums.MidjourneyModelEnum;
16 import cn.iocoder.yudao.module.ai.client.enums.MidjourneySubmitCodeEnum; 18 import cn.iocoder.yudao.module.ai.client.enums.MidjourneySubmitCodeEnum;
  19 +import cn.iocoder.yudao.module.ai.client.enums.MidjourneyTaskStatusEnum;
17 import cn.iocoder.yudao.module.ai.client.vo.MidjourneyImagineReqVO; 20 import cn.iocoder.yudao.module.ai.client.vo.MidjourneyImagineReqVO;
  21 +import cn.iocoder.yudao.module.ai.client.vo.MidjourneyNotifyReqVO;
18 import cn.iocoder.yudao.module.ai.client.vo.MidjourneySubmitRespVO; 22 import cn.iocoder.yudao.module.ai.client.vo.MidjourneySubmitRespVO;
19 -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.*; 23 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallReqVO;
  24 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageListReqVO;
  25 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyImagineReqVO;
  26 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyOperateReqVO;
20 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; 27 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
21 import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper; 28 import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper;
22 import cn.iocoder.yudao.module.ai.enums.AiImagePublicStatusEnum; 29 import cn.iocoder.yudao.module.ai.enums.AiImagePublicStatusEnum;
@@ -36,15 +43,9 @@ import org.springframework.scheduling.annotation.Async; @@ -36,15 +43,9 @@ import org.springframework.scheduling.annotation.Async;
36 import org.springframework.stereotype.Service; 43 import org.springframework.stereotype.Service;
37 import org.springframework.transaction.annotation.Transactional; 44 import org.springframework.transaction.annotation.Transactional;
38 45
39 -import java.util.HashMap;  
40 -import java.util.List;  
41 -import java.util.Map;  
42 -  
43 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 46 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
44 47
45 48
46 -// TODO @fan:注释优化下哈  
47 -  
48 /** 49 /**
49 * AI 绘画(接入 dall2/dall3、midjourney) 50 * AI 绘画(接入 dall2/dall3、midjourney)
50 * 51 *
@@ -56,9 +57,6 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU @@ -56,9 +57,6 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
56 @Slf4j 57 @Slf4j
57 public class AiImageServiceImpl implements AiImageService { 58 public class AiImageServiceImpl implements AiImageService {
58 59
59 - // TODO @fan:使用 @Resource 注入  
60 -  
61 - // TODO @fan:imageMapper  
62 @Resource 60 @Resource
63 private AiImageMapper imageMapper; 61 private AiImageMapper imageMapper;
64 @Resource 62 @Resource
@@ -173,19 +171,16 @@ public class AiImageServiceImpl implements AiImageService { @@ -173,19 +171,16 @@ public class AiImageServiceImpl implements AiImageService {
173 // 4、保存任务 id (状态码: 1(提交成功), 21(已存在), 22(排队中), other(错误)) 171 // 4、保存任务 id (状态码: 1(提交成功), 21(已存在), 22(排队中), other(错误))
174 String updateStatus = null; 172 String updateStatus = null;
175 String errorMessage = null; 173 String errorMessage = null;
176 - Map<String, Object> drawResponse = new HashMap<>();  
177 174
178 if (!MidjourneySubmitCodeEnum.SUCCESS_CODES.contains(submitRespVO.getCode())) { 175 if (!MidjourneySubmitCodeEnum.SUCCESS_CODES.contains(submitRespVO.getCode())) {
179 updateStatus = AiImageStatusEnum.FAIL.getStatus(); 176 updateStatus = AiImageStatusEnum.FAIL.getStatus();
180 errorMessage = submitRespVO.getDescription(); 177 errorMessage = submitRespVO.getDescription();
181 - } else {  
182 - drawResponse.put("jobId", submitRespVO.getResult());  
183 } 178 }
184 imageMapper.updateById(new AiImageDO() 179 imageMapper.updateById(new AiImageDO()
185 .setId(aiImageDO.getId()) 180 .setId(aiImageDO.getId())
186 .setStatus(updateStatus) 181 .setStatus(updateStatus)
187 .setErrorMessage(errorMessage) 182 .setErrorMessage(errorMessage)
188 - .setDrawResponse(drawResponse) 183 + .setJobId(submitRespVO.getResult())
189 ); 184 );
190 return aiImageDO.getId(); 185 return aiImageDO.getId();
191 } 186 }
@@ -228,28 +223,36 @@ public class AiImageServiceImpl implements AiImageService { @@ -228,28 +223,36 @@ public class AiImageServiceImpl implements AiImageService {
228 return imageMapper.deleteById(id) > 0; 223 return imageMapper.deleteById(id) > 0;
229 } 224 }
230 225
231 - private void validateMessageId(String mjMessageId, String messageId) {  
232 - if (!mjMessageId.equals(messageId)) {  
233 - throw ServiceExceptionUtil.exception(ErrorCodeConstants.AI_MIDJOURNEY_MESSAGE_ID_INCORRECT); 226 + @Override
  227 + public Boolean midjourneyNotify(Long loginUserId, MidjourneyNotifyReqVO notifyReqVO) {
  228 + // 1、根据 job id 查询关联的 image
  229 + AiImageDO image = imageMapper.selectByJobId(notifyReqVO.getId());
  230 + if (image == null) {
  231 + log.warn("midjourneyNotify 回调的 jobId 不存在! jobId: {}", notifyReqVO.getId());
  232 + return false;
234 } 233 }
235 - }  
236 -  
237 - private AiImageMidjourneyOperationsVO validateMidjourneyOperationsExists(List<AiImageMidjourneyOperationsVO> midjourneyOperations, String operateId) {  
238 - for (AiImageMidjourneyOperationsVO midjourneyOperation : midjourneyOperations) {  
239 - if (midjourneyOperation.getCustom_id().equals(operateId)) {  
240 - return midjourneyOperation;  
241 - } 234 + //
  235 + String imageStatus = null;
  236 + if (MidjourneyTaskStatusEnum.SUCCESS == notifyReqVO.getStatus()) {
  237 + imageStatus = AiImageStatusEnum.COMPLETE.getStatus();
  238 + } else if (MidjourneyTaskStatusEnum.FAILURE == notifyReqVO.getStatus()) {
  239 + imageStatus = AiImageStatusEnum.FAIL.getStatus();
242 } 240 }
243 - throw ServiceExceptionUtil.exception(ErrorCodeConstants.AI_MIDJOURNEY_OPERATION_NOT_EXISTS);  
244 - }  
245 -  
246 -  
247 - private List<AiImageMidjourneyOperationsVO> getMidjourneyOperations(AiImageDO aiImageDO) {  
248 -// if (StrUtil.isBlank(aiImageDO.getMjOperations())) {  
249 -// return Collections.emptyList();  
250 -// }  
251 -// return JsonUtils.parseArray(aiImageDO.getMjOperations(), AiImageMidjourneyOperationsVO.class);  
252 - return null; 241 + // 2、上传图片
  242 + String filePath = null;
  243 + if (!StrUtil.isBlank(notifyReqVO.getImageUrl())) {
  244 + filePath = fileApi.createFile(HttpUtil.downloadBytes(notifyReqVO.getImageUrl()));
  245 + }
  246 + // 2、更新 image 状态
  247 + imageMapper.updateById(
  248 + new AiImageDO()
  249 + .setId(image.getId())
  250 + .setStatus(imageStatus)
  251 + .setPicUrl(filePath)
  252 + .setOriginalPicUrl(notifyReqVO.getImageUrl())
  253 + .setDrawResponse(BeanUtil.beanToMap(notifyReqVO))
  254 + );
  255 + return true;
253 } 256 }
254 257
255 private AiImageDO validateExists(Long id) { 258 private AiImageDO validateExists(Long id) {