Commit 56e8707e387736294cf431425e978fdc2575bd3a
1 parent
36b6fee0
【增加】Midjourney Proxy 回调通知
Showing
6 changed files
with
67 additions
and
40 deletions
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 | 11 | * @since 1.0 |
| 12 | 12 | */ |
| 13 | 13 | @Data |
| 14 | -public class MidjourneyNotifyVO { | |
| 14 | +public class MidjourneyNotifyReqVO { | |
| 15 | + | |
| 16 | + @Schema(description = "job id") | |
| 17 | + private String id; | |
| 15 | 18 | |
| 16 | 19 | @Schema(description = "任务类型") |
| 17 | 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 | 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 6 | +import cn.iocoder.yudao.module.ai.client.vo.MidjourneyNotifyReqVO; | |
| 6 | 7 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallReqVO; |
| 7 | 8 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageListReqVO; |
| 8 | 9 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyImagineReqVO; |
| ... | ... | @@ -13,7 +14,6 @@ import io.swagger.v3.oas.annotations.Operation; |
| 13 | 14 | import io.swagger.v3.oas.annotations.Parameter; |
| 14 | 15 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 15 | 16 | import jakarta.annotation.Resource; |
| 16 | -import jakarta.servlet.http.HttpServletRequest; | |
| 17 | 17 | import lombok.extern.slf4j.Slf4j; |
| 18 | 18 | import org.springframework.validation.annotation.Validated; |
| 19 | 19 | import org.springframework.web.bind.annotation.*; |
| ... | ... | @@ -74,9 +74,9 @@ public class AiImageController { |
| 74 | 74 | return success(aiImageService.deleteIdMy(id, getLoginUserId())); |
| 75 | 75 | } |
| 76 | 76 | |
| 77 | - @Operation(summary = "删除【我的】绘画记录") | |
| 77 | + @Operation(summary = "midjourney proxy - 回调通知") | |
| 78 | 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 | 28 | @Schema(description = "用户编号") |
| 29 | 29 | private Long userId; |
| 30 | 30 | |
| 31 | + @Schema(description = "midjourney proxy 关联的 job id") | |
| 32 | + private String jobId; | |
| 33 | + | |
| 31 | 34 | @Schema(description = "提示词") |
| 32 | 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 | 4 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 5 | 5 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; |
| 6 | 6 | import org.apache.ibatis.annotations.Mapper; |
| 7 | -import org.springframework.stereotype.Repository; | |
| 8 | 7 | |
| 9 | 8 | /** |
| 10 | 9 | * AI 绘图 Mapper |
| ... | ... | @@ -26,4 +25,14 @@ public interface AiImageMapper extends BaseMapperX<AiImageDO> { |
| 26 | 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 | 1 | package cn.iocoder.yudao.module.ai.service.image; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | +import cn.iocoder.yudao.module.ai.client.vo.MidjourneyNotifyReqVO; | |
| 4 | 5 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallReqVO; |
| 5 | 6 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageListReqVO; |
| 6 | 7 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyImagineReqVO; |
| ... | ... | @@ -65,4 +66,12 @@ public interface AiImageService { |
| 65 | 66 | */ |
| 66 | 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 | 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 | 5 | import cn.hutool.http.HttpUtil; |
| 4 | 6 | import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; |
| 5 | 7 | import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageModelEnum; |
| ... | ... | @@ -14,9 +16,14 @@ import cn.iocoder.yudao.module.ai.ErrorCodeConstants; |
| 14 | 16 | import cn.iocoder.yudao.module.ai.client.MidjourneyProxyClient; |
| 15 | 17 | import cn.iocoder.yudao.module.ai.client.enums.MidjourneyModelEnum; |
| 16 | 18 | import cn.iocoder.yudao.module.ai.client.enums.MidjourneySubmitCodeEnum; |
| 19 | +import cn.iocoder.yudao.module.ai.client.enums.MidjourneyTaskStatusEnum; | |
| 17 | 20 | import cn.iocoder.yudao.module.ai.client.vo.MidjourneyImagineReqVO; |
| 21 | +import cn.iocoder.yudao.module.ai.client.vo.MidjourneyNotifyReqVO; | |
| 18 | 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 | 27 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; |
| 21 | 28 | import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper; |
| 22 | 29 | import cn.iocoder.yudao.module.ai.enums.AiImagePublicStatusEnum; |
| ... | ... | @@ -36,15 +43,9 @@ import org.springframework.scheduling.annotation.Async; |
| 36 | 43 | import org.springframework.stereotype.Service; |
| 37 | 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 | 46 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 44 | 47 | |
| 45 | 48 | |
| 46 | -// TODO @fan:注释优化下哈 | |
| 47 | - | |
| 48 | 49 | /** |
| 49 | 50 | * AI 绘画(接入 dall2/dall3、midjourney) |
| 50 | 51 | * |
| ... | ... | @@ -56,9 +57,6 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU |
| 56 | 57 | @Slf4j |
| 57 | 58 | public class AiImageServiceImpl implements AiImageService { |
| 58 | 59 | |
| 59 | - // TODO @fan:使用 @Resource 注入 | |
| 60 | - | |
| 61 | - // TODO @fan:imageMapper | |
| 62 | 60 | @Resource |
| 63 | 61 | private AiImageMapper imageMapper; |
| 64 | 62 | @Resource |
| ... | ... | @@ -173,19 +171,16 @@ public class AiImageServiceImpl implements AiImageService { |
| 173 | 171 | // 4、保存任务 id (状态码: 1(提交成功), 21(已存在), 22(排队中), other(错误)) |
| 174 | 172 | String updateStatus = null; |
| 175 | 173 | String errorMessage = null; |
| 176 | - Map<String, Object> drawResponse = new HashMap<>(); | |
| 177 | 174 | |
| 178 | 175 | if (!MidjourneySubmitCodeEnum.SUCCESS_CODES.contains(submitRespVO.getCode())) { |
| 179 | 176 | updateStatus = AiImageStatusEnum.FAIL.getStatus(); |
| 180 | 177 | errorMessage = submitRespVO.getDescription(); |
| 181 | - } else { | |
| 182 | - drawResponse.put("jobId", submitRespVO.getResult()); | |
| 183 | 178 | } |
| 184 | 179 | imageMapper.updateById(new AiImageDO() |
| 185 | 180 | .setId(aiImageDO.getId()) |
| 186 | 181 | .setStatus(updateStatus) |
| 187 | 182 | .setErrorMessage(errorMessage) |
| 188 | - .setDrawResponse(drawResponse) | |
| 183 | + .setJobId(submitRespVO.getResult()) | |
| 189 | 184 | ); |
| 190 | 185 | return aiImageDO.getId(); |
| 191 | 186 | } |
| ... | ... | @@ -228,28 +223,36 @@ public class AiImageServiceImpl implements AiImageService { |
| 228 | 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 | 258 | private AiImageDO validateExists(Long id) { | ... | ... |