Commit 6225e18f70a385c6bfcdfcf40ee53e23d09b304c
1 parent
949d5a18
【新增】AI:绘图(MJ)接入 API KEY 管理
Showing
9 changed files
with
62 additions
and
76 deletions
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java
| ... | ... | @@ -9,7 +9,7 @@ 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.AiImagePageReqVO; |
| 11 | 11 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageRespVO; |
| 12 | -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdatePublicStatusReqVO; | |
| 12 | +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdateReqVO; | |
| 13 | 13 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO; |
| 14 | 14 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO; |
| 15 | 15 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; |
| ... | ... | @@ -114,11 +114,11 @@ public class AiImageController { |
| 114 | 114 | return success(BeanUtils.toBean(pageResult, AiImageRespVO.class)); |
| 115 | 115 | } |
| 116 | 116 | |
| 117 | - @PutMapping("/update-public-status") | |
| 118 | - @Operation(summary = "更新绘画发布状态") | |
| 117 | + @PutMapping("/update") | |
| 118 | + @Operation(summary = "更新绘画") | |
| 119 | 119 | @PreAuthorize("@ss.hasPermission('ai:image:update')") |
| 120 | - public CommonResult<Boolean> updateImagePublicStatus(@Valid @RequestBody AiImageUpdatePublicStatusReqVO updateReqVO) { | |
| 121 | - imageService.updateImagePublicStatus(updateReqVO); | |
| 120 | + public CommonResult<Boolean> updateImage(@Valid @RequestBody AiImageUpdateReqVO updateReqVO) { | |
| 121 | + imageService.updateImage(updateReqVO); | |
| 122 | 122 | return success(true); |
| 123 | 123 | } |
| 124 | 124 | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageUpdatePublicStatusReqVO.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageUpdateReqVO.java
| ... | ... | @@ -4,15 +4,15 @@ import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | 4 | import jakarta.validation.constraints.NotNull; |
| 5 | 5 | import lombok.Data; |
| 6 | 6 | |
| 7 | -@Schema(description = "管理后台 - AI 绘画修改发布状态 Request VO") | |
| 7 | +@Schema(description = "管理后台 - AI 绘画修改 Request VO") | |
| 8 | 8 | @Data |
| 9 | -public class AiImageUpdatePublicStatusReqVO { | |
| 9 | +public class AiImageUpdateReqVO { | |
| 10 | 10 | |
| 11 | 11 | @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583") |
| 12 | + @NotNull(message = "编号不能为空") | |
| 12 | 13 | private Long id; |
| 13 | 14 | |
| 14 | - @Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") | |
| 15 | - @NotNull(message = "是否发布不能为空") | |
| 15 | + @Schema(description = "是否发布", example = "true") | |
| 16 | 16 | private Boolean publicStatus; |
| 17 | 17 | |
| 18 | 18 | } |
| 19 | 19 | \ No newline at end of file | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java
| ... | ... | @@ -5,7 +5,7 @@ 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 | 7 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePageReqVO; |
| 8 | -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdatePublicStatusReqVO; | |
| 8 | +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdateReqVO; | |
| 9 | 9 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO; |
| 10 | 10 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO; |
| 11 | 11 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; |
| ... | ... | @@ -71,11 +71,11 @@ public interface AiImageService { |
| 71 | 71 | PageResult<AiImageDO> getImagePage(AiImagePageReqVO pageReqVO); |
| 72 | 72 | |
| 73 | 73 | /** |
| 74 | - * 更新绘画发布状态 | |
| 74 | + * 更新绘画 | |
| 75 | 75 | * |
| 76 | 76 | * @param updateReqVO 更新信息 |
| 77 | 77 | */ |
| 78 | - void updateImagePublicStatus(@Valid AiImageUpdatePublicStatusReqVO updateReqVO); | |
| 78 | + void updateImage(@Valid AiImageUpdateReqVO updateReqVO); | |
| 79 | 79 | |
| 80 | 80 | /** |
| 81 | 81 | * 删除绘画 | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
| ... | ... | @@ -15,7 +15,7 @@ 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 | 17 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePageReqVO; |
| 18 | -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdatePublicStatusReqVO; | |
| 18 | +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdateReqVO; | |
| 19 | 19 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO; |
| 20 | 20 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO; |
| 21 | 21 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; |
| ... | ... | @@ -62,9 +62,6 @@ public class AiImageServiceImpl implements AiImageService { |
| 62 | 62 | @Resource |
| 63 | 63 | private AiApiKeyService apiKeyService; |
| 64 | 64 | |
| 65 | - @Resource | |
| 66 | - private MidjourneyApi midjourneyApi; | |
| 67 | - | |
| 68 | 65 | @Override |
| 69 | 66 | public PageResult<AiImageDO> getImagePageMy(Long userId, PageParam pageReqVO) { |
| 70 | 67 | return imageMapper.selectPage(userId, pageReqVO); |
| ... | ... | @@ -151,7 +148,7 @@ public class AiImageServiceImpl implements AiImageService { |
| 151 | 148 | } |
| 152 | 149 | |
| 153 | 150 | @Override |
| 154 | - public void updateImagePublicStatus(AiImageUpdatePublicStatusReqVO updateReqVO) { | |
| 151 | + public void updateImage(AiImageUpdateReqVO updateReqVO) { | |
| 155 | 152 | // 1. 校验存在 |
| 156 | 153 | validateImageExists(updateReqVO.getId()); |
| 157 | 154 | // 2. 更新发布状态 |
| ... | ... | @@ -179,6 +176,7 @@ public class AiImageServiceImpl implements AiImageService { |
| 179 | 176 | @Override |
| 180 | 177 | @Transactional(rollbackFor = Exception.class) |
| 181 | 178 | public Long midjourneyImagine(Long userId, AiMidjourneyImagineReqVO reqVO) { |
| 179 | + MidjourneyApi midjourneyApi = apiKeyService.getMidjourneyApi(); | |
| 182 | 180 | // 1. 保存数据库 |
| 183 | 181 | AiImageDO image = BeanUtils.toBean(reqVO, AiImageDO.class).setUserId(userId).setPublicStatus(false) |
| 184 | 182 | .setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus()) |
| ... | ... | @@ -206,6 +204,7 @@ public class AiImageServiceImpl implements AiImageService { |
| 206 | 204 | |
| 207 | 205 | @Override |
| 208 | 206 | public Integer midjourneySync() { |
| 207 | + MidjourneyApi midjourneyApi = apiKeyService.getMidjourneyApi(); | |
| 209 | 208 | // 1.1 获取 Midjourney 平台,状态在 “进行中” 的 image |
| 210 | 209 | List<AiImageDO> imageList = imageMapper.selectListByStatusAndPlatform( |
| 211 | 210 | AiImageStatusEnum.IN_PROGRESS.getStatus(), AiPlatformEnum.MIDJOURNEY.getPlatform()); |
| ... | ... | @@ -272,6 +271,7 @@ public class AiImageServiceImpl implements AiImageService { |
| 272 | 271 | |
| 273 | 272 | @Override |
| 274 | 273 | public Long midjourneyAction(Long userId, AiMidjourneyActionReqVO reqVO) { |
| 274 | + MidjourneyApi midjourneyApi = apiKeyService.getMidjourneyApi(); | |
| 275 | 275 | // 1.1 检查 image |
| 276 | 276 | AiImageDO image = validateImageExists(reqVO.getId()); |
| 277 | 277 | if (ObjUtil.notEqual(userId, image.getUserId())) { | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/MidjourneyImageOptions.java deleted
| 1 | -package cn.iocoder.yudao.module.ai.service.image; | |
| 2 | - | |
| 3 | -import lombok.Data; | |
| 4 | -import org.springframework.ai.image.ImageOptions; | |
| 5 | - | |
| 6 | -/** | |
| 7 | - * @author fansili | |
| 8 | - * @time 2024/6/5 10:34 | |
| 9 | - * @since 1.0 | |
| 10 | - */ | |
| 11 | -@Data | |
| 12 | -public class MidjourneyImageOptions implements ImageOptions { | |
| 13 | - /** | |
| 14 | - * 模型 | |
| 15 | - */ | |
| 16 | - private String model; | |
| 17 | - /** | |
| 18 | - * 宽度 | |
| 19 | - */ | |
| 20 | - private Integer width; | |
| 21 | - /** | |
| 22 | - * 高度 | |
| 23 | - */ | |
| 24 | - private Integer height; | |
| 25 | - /** | |
| 26 | - * 版本 | |
| 27 | - */ | |
| 28 | - private String version; | |
| 29 | - /** | |
| 30 | - * 参数 | |
| 31 | - */ | |
| 32 | - private String state; | |
| 33 | - | |
| 34 | - @Override | |
| 35 | - public Integer getN() { | |
| 36 | - return 0; | |
| 37 | - } | |
| 38 | - | |
| 39 | - @Override | |
| 40 | - public String getModel() { | |
| 41 | - return model; | |
| 42 | - } | |
| 43 | - | |
| 44 | - @Override | |
| 45 | - public Integer getWidth() { | |
| 46 | - return width; | |
| 47 | - } | |
| 48 | - | |
| 49 | - @Override | |
| 50 | - public Integer getHeight() { | |
| 51 | - return height; | |
| 52 | - } | |
| 53 | - | |
| 54 | - @Override | |
| 55 | - public String getResponseFormat() { | |
| 56 | - return ""; | |
| 57 | - } | |
| 58 | -} |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyService.java
| 1 | 1 | package cn.iocoder.yudao.module.ai.service.model; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; |
| 4 | +import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; | |
| 4 | 5 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; |
| 5 | 6 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | 7 | import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO; |
| ... | ... | @@ -93,6 +94,15 @@ public interface AiApiKeyService { |
| 93 | 94 | ImageClient getImageClient(AiPlatformEnum platform); |
| 94 | 95 | |
| 95 | 96 | /** |
| 97 | + * 获得 MidjourneyApi 对象 | |
| 98 | + * | |
| 99 | + * TODO 可优化点:目前默认获取 Midjourney 对应的第一个开启的配置用于绘画;后续可以支持配置选择 | |
| 100 | + * | |
| 101 | + * @return MidjourneyApi 对象 | |
| 102 | + */ | |
| 103 | + MidjourneyApi getMidjourneyApi(); | |
| 104 | + | |
| 105 | + /** | |
| 96 | 106 | * 获得 SunoApi 对象 |
| 97 | 107 | * |
| 98 | 108 | * TODO 可优化点:目前默认获取 Suno 对应的第一个开启的配置用于音乐;后续可以支持配置选择 | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java
| ... | ... | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.ai.service.model; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; |
| 4 | 4 | import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory; |
| 5 | +import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; | |
| 5 | 6 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; |
| 6 | 7 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 7 | 8 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| ... | ... | @@ -113,6 +114,16 @@ public class AiApiKeyServiceImpl implements AiApiKeyService { |
| 113 | 114 | } |
| 114 | 115 | |
| 115 | 116 | @Override |
| 117 | + public MidjourneyApi getMidjourneyApi() { | |
| 118 | + AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus( | |
| 119 | + AiPlatformEnum.MIDJOURNEY.getPlatform(), CommonStatusEnum.ENABLE.getStatus()); | |
| 120 | + if (apiKey == null) { | |
| 121 | + return null; | |
| 122 | + } | |
| 123 | + return clientFactory.getOrCreateMidjourneyApi(apiKey.getApiKey(), apiKey.getUrl()); | |
| 124 | + } | |
| 125 | + | |
| 126 | + @Override | |
| 116 | 127 | public SunoApi getSunoApi() { |
| 117 | 128 | AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus( |
| 118 | 129 | AiPlatformEnum.SUNO.getPlatform(), CommonStatusEnum.ENABLE.getStatus()); | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiClientFactory.java
| 1 | 1 | package cn.iocoder.yudao.framework.ai.core.factory; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; |
| 4 | +import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; | |
| 4 | 5 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; |
| 5 | 6 | import org.springframework.ai.chat.StreamingChatClient; |
| 6 | 7 | import org.springframework.ai.image.ImageClient; |
| ... | ... | @@ -57,6 +58,17 @@ public interface AiClientFactory { |
| 57 | 58 | ImageClient getOrCreateImageClient(AiPlatformEnum platform, String apiKey, String url); |
| 58 | 59 | |
| 59 | 60 | /** |
| 61 | + * 基于指定配置,获得 MidjourneyApi 对象 | |
| 62 | + * | |
| 63 | + * 如果不存在,则进行创建 | |
| 64 | + * | |
| 65 | + * @param apiKey API KEY | |
| 66 | + * @param url API URL | |
| 67 | + * @return MidjourneyApi 对象 | |
| 68 | + */ | |
| 69 | + MidjourneyApi getOrCreateMidjourneyApi(String apiKey, String url); | |
| 70 | + | |
| 71 | + /** | |
| 60 | 72 | * 基于指定配置,获得 SunoApi 对象 |
| 61 | 73 | * |
| 62 | 74 | * 如果不存在,则进行创建 | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiClientFactoryImpl.java
| ... | ... | @@ -9,6 +9,7 @@ import cn.hutool.extra.spring.SpringUtil; |
| 9 | 9 | import cn.iocoder.yudao.framework.ai.config.YudaoAiAutoConfiguration; |
| 10 | 10 | import cn.iocoder.yudao.framework.ai.config.YudaoAiProperties; |
| 11 | 11 | import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; |
| 12 | +import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; | |
| 12 | 13 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; |
| 13 | 14 | import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatClient; |
| 14 | 15 | import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatModal; |
| ... | ... | @@ -111,8 +112,18 @@ public class AiClientFactoryImpl implements AiClientFactory { |
| 111 | 112 | } |
| 112 | 113 | |
| 113 | 114 | @Override |
| 115 | + public MidjourneyApi getOrCreateMidjourneyApi(String apiKey, String url) { | |
| 116 | + String cacheKey = buildClientCacheKey(MidjourneyApi.class, AiPlatformEnum.MIDJOURNEY.getPlatform(), apiKey, url); | |
| 117 | + return Singleton.get(cacheKey, (Func0<MidjourneyApi>) () -> { | |
| 118 | + YudaoAiProperties.MidjourneyProperties properties = SpringUtil.getBean(YudaoAiProperties.class).getMidjourney(); | |
| 119 | + return new MidjourneyApi(url, apiKey, properties.getNotifyUrl()); | |
| 120 | + }); | |
| 121 | + } | |
| 122 | + | |
| 123 | + @Override | |
| 114 | 124 | public SunoApi getOrCreateSunoApi(String apiKey, String url) { |
| 115 | - return new SunoApi(url); | |
| 125 | + String cacheKey = buildClientCacheKey(SunoApi.class, AiPlatformEnum.SUNO.getPlatform(), apiKey, url); | |
| 126 | + return Singleton.get(cacheKey, (Func0<SunoApi>) () -> new SunoApi(url)); | |
| 116 | 127 | } |
| 117 | 128 | |
| 118 | 129 | private static String buildClientCacheKey(Class<?> clazz, Object... params) { | ... | ... |