diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java index 1c5b40fc5..15e4304e6 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java @@ -1,13 +1,14 @@ package cn.iocoder.yudao.module.ai.controller.admin.image; +import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.ai.client.vo.MidjourneyNotifyReqVO; import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageListReqVO; import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyImagineReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePageMyRespVO; +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageRespVO; import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; import cn.iocoder.yudao.module.ai.service.image.AiImageService; import io.swagger.v3.oas.annotations.Operation; @@ -21,35 +22,30 @@ import org.springframework.web.bind.annotation.*; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; -@Tag(name = "管理后台 - Ai 绘画") +@Tag(name = "管理后台 - AI 绘画") @RestController @RequestMapping("/ai/image") @Slf4j public class AiImageController { @Resource - private AiImageService aiImageService; + private AiImageService imageService; @Operation(summary = "获取【我的】绘图分页") @GetMapping("/my-page") - public CommonResult> getImagePageMy(@Validated AiImageListReqVO req) { - // 转换 resp - PageResult pageResult = aiImageService.getImagePageMy(getLoginUserId(), req); - // 转换 PageResult 返回 - PageResult result = new PageResult<>(); - result.setTotal(pageResult.getTotal()); - result.setList(BeanUtils.toBean(pageResult.getList(), AiImagePageMyRespVO.class)); - return success(result); + public CommonResult> getImagePageMy(@Validated PageParam pageReqVO) { + PageResult pageResult = imageService.getImagePageMy(getLoginUserId(), pageReqVO); + return success(BeanUtils.toBean(pageResult, AiImageRespVO.class)); } - // TODO @fan:类似 /my-page 的建议 - @Operation(summary = "获取【我的】绘图记录", description = "...") + @Operation(summary = "获取【我的】绘图记录") @GetMapping("/get-my") - public CommonResult getMy(@RequestParam("id") Long id) { - // 获取 image 信息 - AiImageDO imageDO = aiImageService.getMy(id); - // 转 resp 并返回 - return CommonResult.success(BeanUtils.toBean(imageDO, AiImagePageMyRespVO.class)); + public CommonResult getImageMy(@RequestParam("id") Long id) { + AiImageDO image = imageService.getImage(id); + if (image == null || ObjUtil.notEqual(getLoginUserId(), image.getUserId())) { + return success(null); + } + return success(BeanUtils.toBean(image, AiImageRespVO.class)); } // TODO @fan:建议把 dallDrawing、midjourney 融合成一个 draw 接口,异步绘制;然后返回一个 id 给前端;前端通过 get 接口轮询,直到获取到生成成功 @@ -58,14 +54,15 @@ public class AiImageController { @Operation(summary = "dall2/dall3绘画", description = "openAi dall3是付费的!") @PostMapping("/dall") public CommonResult dall(@Validated @RequestBody AiImageDallReqVO req) { - return success(aiImageService.dall(getLoginUserId(), req)); + return success(imageService.dall(getLoginUserId(), req)); } @Operation(summary = "删除【我的】绘画记录") - @DeleteMapping("/delete-id-my") + @DeleteMapping("/delete-my") @Parameter(name = "id", required = true, description = "绘画编号", example = "1024") - public CommonResult deleteIdMy(@RequestParam("id") Long id) { - return success(aiImageService.deleteIdMy(id, getLoginUserId())); + public CommonResult deleteImageMy(@RequestParam("id") Long id) { + imageService.deleteImageMy(id, getLoginUserId()); + return success(true); } // ================ midjourney 接口 @@ -73,12 +70,14 @@ public class AiImageController { @Operation(summary = "midjourney-imagine 绘画", description = "...") @PostMapping("/midjourney/imagine") public CommonResult midjourneyImagine(@Validated @RequestBody AiImageMidjourneyImagineReqVO req) { - return success(aiImageService.midjourneyImagine(getLoginUserId(), req)); + return success(imageService.midjourneyImagine(getLoginUserId(), req)); } + // TODO @fan:可以考虑,复用 AiImageDallRespVO,统一成 AIImageRespVO @Operation(summary = "midjourney proxy - 回调通知") @RequestMapping("/midjourney-notify") public CommonResult midjourneyNotify(MidjourneyNotifyReqVO notifyReqVO) { - return success(aiImageService.midjourneyNotify(getLoginUserId(), notifyReqVO)); + return success(imageService.midjourneyNotify(getLoginUserId(), notifyReqVO)); } + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageListReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageListReqVO.java deleted file mode 100644 index 6af980363..000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageListReqVO.java +++ /dev/null @@ -1,18 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.image.vo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import lombok.Data; -import lombok.experimental.Accessors; - -/** - * AI Image 我的图片列表 req - * - * @author fansili - * @time 2024/4/28 17:42 - * @since 1.0 - */ -@Data -@Accessors(chain = true) -public class AiImageListReqVO extends PageParam { - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageMidjourneyRes.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageMidjourneyRes.java deleted file mode 100644 index 8d1a52e8c..000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageMidjourneyRes.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.image.vo; - -import lombok.Data; -import lombok.experimental.Accessors; - -/** - * midjourney - * - * @author fansili - * @time 2024/4/28 17:42 - * @since 1.0 - */ -@Data -@Accessors(chain = true) -public class AiImageMidjourneyRes { -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImagePageMyRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageRespVO.java similarity index 84% rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImagePageMyRespVO.java rename to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageRespVO.java index e542879ca..4d12f7f4b 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImagePageMyRespVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageRespVO.java @@ -1,23 +1,13 @@ package cn.iocoder.yudao.module.ai.controller.admin.image.vo; -import cn.iocoder.yudao.framework.common.pojo.PageParam; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import lombok.experimental.Accessors; import java.util.Map; -// TODO @fan:可以考虑,复用 AiImageDallRespVO,统一成 AIImageRespVO -/** - * midjourney req - * - * @author fansili - * @time 2024/4/28 17:42 - * @since 1.0 - */ +// TODO @芋艿:完善 swagger 注解 @Data -@Accessors(chain = true) -public class AiImagePageMyRespVO extends PageParam { +public class AiImageRespVO { @Schema(description = "id编号", example = "1") private Long id; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/image/AiImageMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/image/AiImageMapper.java index af37a2937..aada8a661 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/image/AiImageMapper.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/image/AiImageMapper.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.module.ai.dal.mysql.image; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; @@ -35,4 +37,10 @@ public interface AiImageMapper extends BaseMapperX { return this.selectOne(new LambdaQueryWrapperX().eq(AiImageDO::getJobId, id)); } + default PageResult selectPage(Long userId, PageParam pageReqVO) { + return selectPage(pageReqVO, new LambdaQueryWrapperX() + .eq(AiImageDO::getUserId, userId) + .orderByDesc(AiImageDO::getId)); + } + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java index 05ad0ed28..2442e79a9 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java @@ -1,38 +1,35 @@ package cn.iocoder.yudao.module.ai.service.image; +import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.ai.client.vo.MidjourneyNotifyReqVO; import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageListReqVO; import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyImagineReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyOperateReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; /** - * ai 作图 + * AI 绘图 Service 接口 * * @author fansili - * @time 2024/4/25 15:50 - * @since 1.0 */ public interface AiImageService { /** - * ai绘画 - 列表 + * 获取【我的】绘图分页 * - * @param loginUserId - * @param req - * @return + * @param userId 用户编号 + * @param pageReqVO 分页条件 + * @return 绘图分页 */ - PageResult getImagePageMy(Long loginUserId, AiImageListReqVO req); + PageResult getImagePageMy(Long userId, PageParam pageReqVO); /** - * 获取 - image 信息 + * 获得绘图记录 * - * @param id - * @return + * @param id 绘图编号 + * @return 绘图记录 */ - AiImageDO getMy(Long id); + AiImageDO getImage(Long id); /** * ai绘画 - dall2/dall3 绘画 @@ -52,19 +49,12 @@ public interface AiImageService { Long midjourneyImagine(Long loginUserId, AiImageMidjourneyImagineReqVO req); /** - * midjourney 操作(u1、u2、放大、换一批...) + * 删除【我的】绘画记录 * - * @param req + * @param id 绘画编号 + * @param userId 用户编号 */ - void midjourneyOperate(AiImageMidjourneyOperateReqVO req); - - /** - * 删除 - image 记录 - * - * @param id - * @param loginUserId - */ - Boolean deleteIdMy(Long id, Long loginUserId); + void deleteImageMy(Long id, Long userId); /** * midjourney proxy - 回调通知 diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java index 16887045f..2b4f7e617 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java @@ -2,17 +2,17 @@ package cn.iocoder.yudao.module.ai.service.image; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.extra.spring.SpringUtil; import cn.hutool.http.HttpUtil; import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageModelEnum; import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageStyleEnum; import cn.iocoder.yudao.framework.ai.core.exception.AiException; -import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; +import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.ai.AiCommonConstants; -import cn.iocoder.yudao.module.ai.ErrorCodeConstants; import cn.iocoder.yudao.module.ai.client.MidjourneyProxyClient; import cn.iocoder.yudao.module.ai.client.enums.MidjourneyModelEnum; import cn.iocoder.yudao.module.ai.client.enums.MidjourneySubmitCodeEnum; @@ -21,9 +21,7 @@ import cn.iocoder.yudao.module.ai.client.vo.MidjourneyImagineReqVO; import cn.iocoder.yudao.module.ai.client.vo.MidjourneyNotifyReqVO; import cn.iocoder.yudao.module.ai.client.vo.MidjourneySubmitRespVO; import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageListReqVO; import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyImagineReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyOperateReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper; import cn.iocoder.yudao.module.ai.enums.AiImagePublicStatusEnum; @@ -46,12 +44,12 @@ import org.springframework.transaction.annotation.Transactional; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.AI_IMAGE_NOT_EXISTS; + /** - * AI 绘画(接入 dall2/dall3、midjourney) + * AI 绘画 Service 实现类 * * @author fansili - * @time 2024/4/25 15:51 - * @since 1.0 */ @Service @Slf4j @@ -59,10 +57,13 @@ public class AiImageServiceImpl implements AiImageService { @Resource private AiImageMapper imageMapper; + @Resource private FileApi fileApi; + @Resource private OpenAiImageClient openAiImageClient; + @Autowired private MidjourneyProxyClient midjourneyProxyClient; @@ -70,16 +71,12 @@ public class AiImageServiceImpl implements AiImageService { private String midjourneyNotifyUrl; @Override - public PageResult getImagePageMy(Long loginUserId, AiImageListReqVO req) { - // 查询当前用户下所有的绘画记录 - return imageMapper.selectPage(req, - new LambdaQueryWrapperX() - .eq(AiImageDO::getUserId, loginUserId) - .orderByDesc(AiImageDO::getId)); + public PageResult getImagePageMy(Long userId, PageParam pageReqVO) { + return imageMapper.selectPage(userId, pageReqVO); } @Override - public AiImageDO getMy(Long id) { + public AiImageDO getImage(Long id) { return imageMapper.selectById(id); } @@ -95,7 +92,7 @@ public class AiImageServiceImpl implements AiImageService { .setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus()); imageMapper.insert(aiImageDO); // 异步执行 - doDall(aiImageDO, req); + getSelf().doDall(aiImageDO, req); // 转换 AiImageDallDrawingRespVO return aiImageDO.getId(); } @@ -185,42 +182,15 @@ public class AiImageServiceImpl implements AiImageService { return aiImageDO.getId(); } - @Transactional(rollbackFor = Exception.class) @Override - public void midjourneyOperate(AiImageMidjourneyOperateReqVO req) { -// // 校验是否存在 -// AiImageDO aiImageDO = validateExists(req.getId()); -// // 获取 midjourneyOperations -// List midjourneyOperations = getMidjourneyOperations(aiImageDO); -// // 校验 OperateId 是否存在 -// AiImageMidjourneyOperationsVO midjourneyOperationsVO = validateMidjourneyOperationsExists(midjourneyOperations, req.getOperateId()); -// // 校验 messageId -// validateMessageId(aiImageDO.getMjNonceId(), req.getMessageId()); -// // 获取 mjOperationName -// String mjOperationName = midjourneyOperationsVO.getLabel(); -// // 保存一个 image 任务记录 -// // todo -//// doSave(aiImageDO.getPrompt(), aiImageDO.getSize(), aiImageDO.getModel(), -//// null, null, AiImageStatusEnum.SUBMIT, null, -//// req.getMessageId(), req.getOperateId(), mjOperationName); -// // 提交操作 -// midjourneyInteractionsApi.reRoll( -// new ReRollReq() -// .setCustomId(req.getOperateId()) -// .setMessageId(req.getMessageId()) -// ); - } - - @Override - public Boolean deleteIdMy(Long id, Long userId) { - // 校验是否存在,并获取 image - AiImageDO image = validateExists(id); - // 是否属于当前用户 - if (!image.getUserId().equals(userId)) { - throw exception(ErrorCodeConstants.AI_IMAGE_NOT_EXISTS); + public void deleteImageMy(Long id, Long userId) { + // 1. 校验是否存在 + AiImageDO image = validateImageExists(id); + if (ObjUtil.notEqual(image.getUserId(), userId)) { + throw exception(AI_IMAGE_NOT_EXISTS); } // 删除记录 - return imageMapper.deleteById(id) > 0; + imageMapper.deleteById(id); } @Override @@ -255,11 +225,21 @@ public class AiImageServiceImpl implements AiImageService { return true; } - private AiImageDO validateExists(Long id) { - AiImageDO aiImageDO = imageMapper.selectById(id); - if (aiImageDO == null) { - throw ServiceExceptionUtil.exception(ErrorCodeConstants.AI_MIDJOURNEY_IMAGINE_FAIL); + private AiImageDO validateImageExists(Long id) { + AiImageDO image = imageMapper.selectById(id); + if (image == null) { + throw exception(AI_IMAGE_NOT_EXISTS); } - return aiImageDO; + return image; } + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private AiImageServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + }