From 9d6b615f10cc644a8de61a68e74abcc69998d07c Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 27 Jun 2024 23:09:17 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E3=80=91AI=EF=BC=9A=E9=9F=B3=E4=B9=90=E7=AE=A1=E7=90=86=2050%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/ai/enums/ErrorCodeConstants.java | 13 ++-- .../enums/music/AiMusicGenerateModeEnum.java | 12 +++- .../ai/enums/music/AiMusicStatusEnum.java | 12 +++- .../admin/image/AiImageController.java | 20 ++---- .../admin/image/vo/AiImagePageReqVO.java | 2 +- .../admin/music/AiImageController.http | 13 ---- .../admin/music/AiMusicController.http | 26 +++++++ .../admin/music/AiMusicController.java | 40 +++++++++-- .../admin/music/vo/AiMusicPageReqVO.java | 44 ++++++++++++ .../admin/music/vo/AiMusicRespVO.java | 67 +++++++++++++++++++ .../vo/AiMusicUpdatePublicStatusReqVO.java | 18 +++++ .../admin/music/vo/AiSunoGenerateReqVO.java | 9 ++- .../ai/dal/dataobject/music/AiMusicDO.java | 5 ++ .../ai/dal/mysql/music/AiMusicMapper.java | 14 ++++ .../chat/AiChatMessageServiceImpl.java | 10 +-- .../ai/service/image/AiImageServiceImpl.java | 16 +++-- .../ai/service/music/AiMusicService.java | 27 ++++++++ .../ai/service/music/AiMusicServiceImpl.java | 40 ++++++++++- .../yudao/framework/ai/music/SunoTests.java | 4 +- .../src/main/resources/application.yaml | 3 +- 20 files changed, 339 insertions(+), 56 deletions(-) delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiImageController.http create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.http create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicPageReqVO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicRespVO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdatePublicStatusReqVO.java diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java index 6dbd08730..860f68f55 100644 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java +++ b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java @@ -29,13 +29,16 @@ public interface ErrorCodeConstants { // ========== API 聊天消息 1-040-004-000 ========== - ErrorCode AI_CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "消息不存在!"); - ErrorCode AI_CHAT_STREAM_ERROR = new ErrorCode(1_040_004_001, "Stream 对话异常!"); + ErrorCode CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "消息不存在!"); + ErrorCode CHAT_STREAM_ERROR = new ErrorCode(1_040_004_001, "Stream 对话异常!"); // ========== API 绘画 1-040-005-000 ========== - ErrorCode AI_IMAGE_NOT_EXISTS = new ErrorCode(1_022_005_000, "图片不存在!"); - ErrorCode AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL = new ErrorCode(1_022_005_001, "Midjourney 提交失败!原因:{}"); - ErrorCode AI_IMAGE_CUSTOM_ID_NOT_EXISTS = new ErrorCode(1_022_005_002, "Midjourney 按钮 customId 不存在! {}"); + ErrorCode IMAGE_NOT_EXISTS = new ErrorCode(1_022_005_000, "图片不存在!"); + ErrorCode IMAGE_MIDJOURNEY_SUBMIT_FAIL = new ErrorCode(1_022_005_001, "Midjourney 提交失败!原因:{}"); + ErrorCode IMAGE_CUSTOM_ID_NOT_EXISTS = new ErrorCode(1_022_005_002, "Midjourney 按钮 customId 不存在! {}"); + + // ========== API 音乐 1-040-006-000 ========== + ErrorCode MUSIC_NOT_EXISTS = new ErrorCode(1_022_006_000, "音乐不存在!"); } diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateModeEnum.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateModeEnum.java index 2bb6a120f..425e2bb8c 100644 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateModeEnum.java +++ b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateModeEnum.java @@ -1,8 +1,11 @@ package cn.iocoder.yudao.module.ai.enums.music; +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.Arrays; + /** * AI 音乐状态的枚举 * @@ -10,7 +13,7 @@ import lombok.Getter; */ @AllArgsConstructor @Getter -public enum AiMusicGenerateModeEnum { +public enum AiMusicGenerateModeEnum implements IntArrayValuable { LYRIC(1, "歌词模式"), DESCRIPTION(2, "描述模式"); @@ -24,4 +27,11 @@ public enum AiMusicGenerateModeEnum { */ private final String name; + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiMusicGenerateModeEnum::getMode).toArray(); + + @Override + public int[] array() { + return ARRAYS; + } + } diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java index 408dc93bc..39ceb1e17 100644 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java +++ b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java @@ -1,8 +1,11 @@ package cn.iocoder.yudao.module.ai.enums.music; +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.Arrays; + /** * AI 音乐状态的枚举 * @@ -10,7 +13,7 @@ import lombok.Getter; */ @AllArgsConstructor @Getter -public enum AiMusicStatusEnum { +public enum AiMusicStatusEnum implements IntArrayValuable { IN_PROGRESS(10, "进行中"), SUCCESS(20, "已完成"); @@ -25,4 +28,11 @@ public enum AiMusicStatusEnum { */ private final String name; + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiMusicStatusEnum::getStatus).toArray(); + + @Override + public int[] array() { + return ARRAYS; + } + } 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 69ffb2d40..baa52da83 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,6 +1,5 @@ package cn.iocoder.yudao.module.ai.controller.admin.image; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; import cn.iocoder.yudao.framework.common.pojo.CommonResult; @@ -26,9 +25,7 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.Collections; import java.util.List; -import java.util.Objects; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @@ -42,15 +39,16 @@ public class AiImageController { @Resource private AiImageService imageService; - @Operation(summary = "获取【我的】绘图分页") @GetMapping("/my-page") + @Operation(summary = "获取【我的】绘图分页") public CommonResult> getImagePageMy(@Validated PageParam pageReqVO) { PageResult pageResult = imageService.getImagePageMy(getLoginUserId(), pageReqVO); return success(BeanUtils.toBean(pageResult, AiImageRespVO.class)); } - @Operation(summary = "获取【我的】绘图记录") @GetMapping("/get-my") + @Operation(summary = "获取【我的】绘图记录") + @Parameter(name = "id", required = true, description = "绘画编号", example = "1024") public CommonResult getImageMy(@RequestParam("id") Long id) { AiImageDO image = imageService.getImage(id); if (image == null || ObjUtil.notEqual(getLoginUserId(), image.getUserId())) { @@ -59,17 +57,13 @@ public class AiImageController { return success(BeanUtils.toBean(image, AiImageRespVO.class)); } - @Operation(summary = "获取【我的】绘图记录 - ids") @GetMapping("/get-my-ids") + @Operation(summary = "获取【我的】绘图记录列表") + @Parameter(name = "ids", required = true, description = "绘画编号数组", example = "1024,2048") public CommonResult> getImageMyIds(@RequestParam("ids") List ids) { List imageList = imageService.getImageByIds(ids); - if (CollUtil.isEmpty(imageList)) { - return success(Collections.emptyList()); - } - List userImageList = imageList.stream() - .map(item -> ObjUtil.equal(getLoginUserId(), item.getUserId()) ? item : null) - .filter(Objects::nonNull).toList(); - return success(BeanUtils.toBean(userImageList, AiImageRespVO.class)); + imageList.removeIf(item -> !ObjUtil.equal(getLoginUserId(), item.getUserId())); + return success(BeanUtils.toBean(imageList, AiImageRespVO.class)); } @Operation(summary = "生成图片") diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImagePageReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImagePageReqVO.java index 6f6c6596f..57dea8fbc 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImagePageReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImagePageReqVO.java @@ -20,7 +20,7 @@ public class AiImagePageReqVO extends PageParam { @Schema(description = "用户编号", example = "28987") private Long userId; - @Schema(description = "平台") + @Schema(description = "平台", example = "OpenAI") private String platform; @Schema(description = "绘画状态", example = "1") diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiImageController.http b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiImageController.http deleted file mode 100644 index b3d11ff30..000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiImageController.http +++ /dev/null @@ -1,13 +0,0 @@ -### 生成音乐:Suno + -POST {{baseUrl}}/ai/music/generate -Content-Type: application/json -Authorization: {{token}} - -{ - "platform": "Suno", - "generateMode": 1, - "prompt": "来一首快乐的歌曲", - "modelVersion": "chirp-v3.5", - "tags": ["Happy"], - "title": "Happy Song" -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.http b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.http new file mode 100644 index 000000000..58524ee83 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.http @@ -0,0 +1,26 @@ +### 生成音乐:Suno + 歌词模式 +POST {{baseUrl}}/ai/music/generate +Content-Type: application/json +Authorization: {{token}} + +{ + "platform": "Suno", + "generateMode": 1, + "prompt": "来一首快乐的歌曲", + "modelVersion": "chirp-v3.5", + "tags": ["Happy"], + "title": "Happy Song" +} + +### 生成音乐:Suno + 描述模式 +POST {{baseUrl}}/ai/music/generate +Content-Type: application/json +Authorization: {{token}} + +{ + "platform": "Suno", + "generateMode": 2, + "prompt": "来一首快乐的歌曲", + "makeInstrumental": false, + "title": "Happy Song" +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java index 92887f19a..ed86faa13 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java @@ -1,16 +1,21 @@ package cn.iocoder.yudao.module.ai.controller.admin.music; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicRespVO; +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicUpdatePublicStatusReqVO; import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO; +import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; import cn.iocoder.yudao.module.ai.service.music.AiMusicService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.validation.Valid; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; import java.util.List; @@ -31,4 +36,31 @@ public class AiMusicController { return success(musicService.generateMusic(getLoginUserId(), reqVO)); } + // ================ 绘图管理 ================ + + @GetMapping("/page") + @Operation(summary = "获得音乐分页") + @PreAuthorize("@ss.hasPermission('ai:music:query')") + public CommonResult> getMusicPage(@Valid AiMusicPageReqVO pageReqVO) { + PageResult pageResult = musicService.getMusicPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, AiMusicRespVO.class)); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除音乐") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('ai:music:delete')") + public CommonResult deleteMusic(@RequestParam("id") Long id) { + musicService.deleteMusic(id); + return success(true); + } + + @PutMapping("/update") + @Operation(summary = "更新音乐发布状态") + @PreAuthorize("@ss.hasPermission('ai:music:update')") + public CommonResult updateMusicPublicStatus(@Valid @RequestBody AiMusicUpdatePublicStatusReqVO updateReqVO) { + musicService.updateMusicPublicStatus(updateReqVO); + return success(true); + } + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicPageReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicPageReqVO.java new file mode 100644 index 000000000..f68e25702 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicPageReqVO.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.ai.controller.admin.music.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum; +import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - AI 音乐分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class AiMusicPageReqVO extends PageParam { + + @Schema(description = "用户编号", example = "12212") + private Long userId; + + @Schema(description = "音乐名称", example = "夜空中最亮的星") + private String title; + + @Schema(description = "音乐状态", example = "20") + @InEnum(AiMusicStatusEnum.class) + private Integer status; + + @Schema(description = "生成模式", example = "1") + @InEnum(AiMusicGenerateModeEnum.class) + private Integer generateMode; + + @Schema(description = "是否发布", example = "true") + private Boolean publicStatus; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicRespVO.java new file mode 100644 index 000000000..470592fff --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicRespVO.java @@ -0,0 +1,67 @@ +package cn.iocoder.yudao.module.ai.controller.admin.music.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - AI 音乐 Response VO") +@Data +public class AiMusicRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790") + private Long id; + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12212") + private Long userId; + + @Schema(description = "音乐名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "夜空中最亮的星") + private String title; + + @Schema(description = "歌词", example = "oh~卖糕的") + private String lyric; + + @Schema(description = "图片地址", example = "https://www.iocoder.cn") + private String imageUrl; + + @Schema(description = "音频地址", example = "https://www.iocoder.cn") + private String audioUrl; + + @Schema(description = "视频地址", example = "https://www.iocoder.cn") + private String videoUrl; + + @Schema(description = "音乐状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") + private Integer status; + + @Schema(description = "描述词", example = "一首轻快的歌曲") + private String gptDescriptionPrompt; + + @Schema(description = "提示词", example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。") + private String prompt; + + @Schema(description = "模型平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "Suno") + private String platform; + + @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5") + private String model; + + @Schema(description = "生成模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer generateMode; + + @Schema(description = "音乐风格标签") + private List tags; + + @Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + private Boolean publicStatus; + + @Schema(description = "任务编号", example = "11369") + private String taskId; + + @Schema(description = "错误信息") + private String errorMessage; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdatePublicStatusReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdatePublicStatusReqVO.java new file mode 100644 index 000000000..30d7afba1 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdatePublicStatusReqVO.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.ai.controller.admin.music.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - AI 音乐修改发布状态 Request VO") +@Data +public class AiMusicUpdatePublicStatusReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583") + private Long id; + + @Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "是否发布不能为空") + private Boolean publicStatus; + +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiSunoGenerateReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiSunoGenerateReqVO.java index d1069e991..4f0859134 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiSunoGenerateReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiSunoGenerateReqVO.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.music.vo; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -15,6 +16,10 @@ public class AiSunoGenerateReqVO { @NotBlank(message = "平台不能为空") private String platform; // 参见 AiPlatformEnum 枚举 + /** + * 1. 描述模式:描述词 + 是否纯音乐 + 模型 TODO @xin:目前貌似描述词没弄对?看着不是 prompt 字段(也可能我弄错了)。可以微信再沟通下哈 + * 2. 歌词模式:歌词 + 音乐风格 + 标题 + 模型 TODO @xin:目前这块少传递了标题; + */ @Schema(description = "生成模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") @NotNull(message = "生成模式不能为空") private Integer generateMode; // 参见 AiMusicGenerateModeEnum 枚举 @@ -26,7 +31,9 @@ public class AiSunoGenerateReqVO { @Schema(description = "是否纯音乐", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "true") private Boolean makeInstrumental; - @Schema(description = "模型版本", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "chirp-v3.5") + // TODO @xin:看了下这个字段,发现最终还是 model 合适点;因为它其实是模型 + @Schema(description = "模型版本", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5") + @NotEmpty(message = "模型不能为空") private String modelVersion; // 参见 AiModelEnum 枚举 @Schema(description = "音乐风格", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "[\"pop\",\"jazz\",\"punk\"]") diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java index 5ec441418..cc990d915 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java @@ -98,6 +98,11 @@ public class AiMusicDO extends BaseDO { @TableField(typeHandler = JacksonTypeHandler.class) private List tags; + /** + * 是否公开 + */ + private Boolean publicStatus; + /** * 任务编号 */ diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/music/AiMusicMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/music/AiMusicMapper.java index 973d1838d..8d41086b2 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/music/AiMusicMapper.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/music/AiMusicMapper.java @@ -1,6 +1,9 @@ package cn.iocoder.yudao.module.ai.dal.mysql.music; +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.controller.admin.music.vo.AiMusicPageReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; import org.apache.ibatis.annotations.Mapper; @@ -18,4 +21,15 @@ public interface AiMusicMapper extends BaseMapperX { return selectList(AiMusicDO::getStatus, status); } + default PageResult selectPage(AiMusicPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(AiMusicDO::getUserId, reqVO.getUserId()) + .eqIfPresent(AiMusicDO::getTitle, reqVO.getTitle()) + .eqIfPresent(AiMusicDO::getStatus, reqVO.getStatus()) + .eqIfPresent(AiMusicDO::getGenerateMode, reqVO.getGenerateMode()) + .betweenIfPresent(AiMusicDO::getCreateTime, reqVO.getCreateTime()) + .eqIfPresent(AiMusicDO::getPublicStatus, reqVO.getPublicStatus()) + .orderByDesc(AiMusicDO::getId)); + } + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java index 566859abf..172c6658d 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java @@ -44,7 +44,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.AI_CHAT_MESSAGE_NOT_EXIST; +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_MESSAGE_NOT_EXIST; import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS; /** @@ -150,7 +150,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage())); }).onErrorResume(error -> { - return Flux.just(error(ErrorCodeConstants.AI_CHAT_STREAM_ERROR)); + return Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR)); }); } @@ -257,7 +257,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { // 1. 校验消息存在 AiChatMessageDO message = chatMessageMapper.selectById(id); if (message == null || ObjUtil.notEqual(message.getUserId(), userId)) { - throw exception(AI_CHAT_MESSAGE_NOT_EXIST); + throw exception(CHAT_MESSAGE_NOT_EXIST); } // 2. 执行删除 chatMessageMapper.deleteById(id); @@ -268,7 +268,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { // 1. 校验消息存在 List messages = chatMessageMapper.selectListByConversationId(conversationId); if (CollUtil.isEmpty(messages) || ObjUtil.notEqual(messages.get(0).getUserId(), userId)) { - throw exception(AI_CHAT_MESSAGE_NOT_EXIST); + throw exception(CHAT_MESSAGE_NOT_EXIST); } // 2. 执行删除 chatMessageMapper.deleteBatchIds(convertList(messages, AiChatMessageDO::getId)); @@ -279,7 +279,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { // 1. 校验消息存在 AiChatMessageDO message = chatMessageMapper.selectById(id); if (message == null) { - throw exception(AI_CHAT_MESSAGE_NOT_EXIST); + throw exception(CHAT_MESSAGE_NOT_EXIST); } // 2. 执行删除 chatMessageMapper.deleteById(id); 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 42cba8084..c65eb19f1 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 @@ -35,6 +35,7 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -76,6 +77,9 @@ public class AiImageServiceImpl implements AiImageService { @Override public List getImageByIds(List ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } return imageMapper.selectBatchIds(ids); } @@ -135,7 +139,7 @@ public class AiImageServiceImpl implements AiImageService { // 1. 校验是否存在 AiImageDO image = validateImageExists(id); if (ObjUtil.notEqual(image.getUserId(), userId)) { - throw exception(AI_IMAGE_NOT_EXISTS); + throw exception(IMAGE_NOT_EXISTS); } // 2. 删除记录 imageMapper.deleteById(id); @@ -165,7 +169,7 @@ public class AiImageServiceImpl implements AiImageService { private AiImageDO validateImageExists(Long id) { AiImageDO image = imageMapper.selectById(id); if (image == null) { - throw exception(AI_IMAGE_NOT_EXISTS); + throw exception(IMAGE_NOT_EXISTS); } return image; } @@ -191,7 +195,7 @@ public class AiImageServiceImpl implements AiImageService { if (!MidjourneyApi.SubmitCodeEnum.SUCCESS_CODES.contains(imagineResponse.code())) { String description = imagineResponse.description().contains("quota_not_enough") ? "账户余额不足" : imagineResponse.description(); - throw exception(AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL, description); + throw exception(IMAGE_MIDJOURNEY_SUBMIT_FAIL, description); } // 4. 情况二【成功】:更新 taskId 和参数 @@ -271,13 +275,13 @@ public class AiImageServiceImpl implements AiImageService { // 1.1 检查 image AiImageDO image = validateImageExists(reqVO.getId()); if (ObjUtil.notEqual(userId, image.getUserId())) { - throw exception(AI_IMAGE_NOT_EXISTS); + throw exception(IMAGE_NOT_EXISTS); } // 1.2 检查 customId MidjourneyApi.Button button = CollUtil.findOne(image.getButtons(), buttonX -> buttonX.customId().equals(reqVO.getCustomId())); if (button == null) { - throw exception(AI_IMAGE_CUSTOM_ID_NOT_EXISTS); + throw exception(IMAGE_CUSTOM_ID_NOT_EXISTS); } // 2. 调用 Midjourney Proxy 提交任务 @@ -286,7 +290,7 @@ public class AiImageServiceImpl implements AiImageService { if (!MidjourneyApi.SubmitCodeEnum.SUCCESS_CODES.contains(actionResponse.code())) { String description = actionResponse.description().contains("quota_not_enough") ? "账户余额不足" : actionResponse.description(); - throw exception(AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL, description); + throw exception(IMAGE_MIDJOURNEY_SUBMIT_FAIL, description); } // 3. 新增 image 记录 diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java index e32a5ac13..d21cf0e0d 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java @@ -1,6 +1,11 @@ package cn.iocoder.yudao.module.ai.service.music; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicUpdatePublicStatusReqVO; import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO; +import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; +import jakarta.validation.Valid; import java.util.List; @@ -27,4 +32,26 @@ public interface AiMusicService { */ Integer syncMusic(); + /** + * 更新音乐发布状态 + * + * @param updateReqVO 更新信息 + */ + void updateMusicPublicStatus(@Valid AiMusicUpdatePublicStatusReqVO updateReqVO); + + /** + * 删除AI 音乐 + * + * @param id 编号 + */ + void deleteMusic(Long id); + + /** + * 获得音乐分页 + * + * @param pageReqVO 分页查询 + * @return 音乐分页 + */ + PageResult getMusicPage(AiMusicPageReqVO pageReqVO); + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java index a9cee3a72..ce7dd01d0 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java @@ -4,6 +4,9 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.text.StrPool; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicUpdatePublicStatusReqVO; import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; @@ -15,8 +18,10 @@ import org.springframework.stereotype.Service; import java.util.*; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MUSIC_NOT_EXISTS; /** * AI 音乐 Service 实现类 @@ -56,7 +61,7 @@ public class AiMusicServiceImpl implements AiMusicService { return Collections.emptyList(); } List musicList = buildMusicDOList(musicDataList); - musicList.forEach(music -> music.setUserId(userId).setPlatform(music.getPlatform()).setGenerateMode(reqVO.getGenerateMode())); + musicList.forEach(music -> music.setUserId(userId).setPlatform(reqVO.getPlatform()).setGenerateMode(reqVO.getGenerateMode())); musicMapper.insertBatch(musicList); return convertList(musicList, AiMusicDO::getId); } @@ -92,12 +97,41 @@ public class AiMusicServiceImpl implements AiMusicService { * @return AiMusicDO 集合 */ private static List buildMusicDOList(List musicList) { + // TODO @xin:成功的情况下,需要下载到自己的文件服务器。参考图片的处理 return convertList(musicList, musicData -> new AiMusicDO() .setTaskId(musicData.id()).setModel(musicData.modelName()) .setPrompt(musicData.prompt()).setGptDescriptionPrompt(musicData.gptDescriptionPrompt()) .setAudioUrl(musicData.audioUrl()).setVideoUrl(musicData.videoUrl()).setImageUrl(musicData.imageUrl()) .setTitle(musicData.title()).setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA)) - .setStatus(Objects.equals("complete", musicData.status()) ? AiMusicStatusEnum.SUCCESS.getStatus() : AiMusicStatusEnum.IN_PROGRESS.getStatus())); - + .setStatus(Objects.equals("complete", musicData.status()) ? + AiMusicStatusEnum.SUCCESS.getStatus() : AiMusicStatusEnum.IN_PROGRESS.getStatus())); } + + @Override + public void updateMusicPublicStatus(AiMusicUpdatePublicStatusReqVO updateReqVO) { + // 校验存在 + validateMusicExists(updateReqVO.getId()); + // 更新 + musicMapper.updateBatch(new AiMusicDO().setPublicStatus(updateReqVO.getPublicStatus())); + } + + @Override + public void deleteMusic(Long id) { + // 校验存在 + validateMusicExists(id); + // 删除 + musicMapper.deleteById(id); + } + + private void validateMusicExists(Long id) { + if (musicMapper.selectById(id) == null) { + throw exception(MUSIC_NOT_EXISTS); + } + } + + @Override + public PageResult getMusicPage(AiMusicPageReqVO pageReqVO) { + return musicMapper.selectPage(pageReqVO); + } + } diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/music/SunoTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/music/SunoTests.java index ad43e373c..4fd81cf89 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/music/SunoTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/music/SunoTests.java @@ -17,7 +17,8 @@ public class SunoTests { @Before public void setup() { - String url = "https://suno-om0w1cy6e-status2xxs-projects.vercel.app"; +// String url = "https://suno-om0w1cy6e-status2xxs-projects.vercel.app"; + String url = "http://127.0.0.1:3001"; this.sunoApi = new SunoApi(url); } @@ -53,5 +54,4 @@ public class SunoTests { System.out.println(limitUsageData); } - } diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 5ffae8ae2..7a33f1980 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -201,7 +201,8 @@ yudao.ai: notify-url: http://java.nat300.top/admin-api/ai/image/midjourney/notify suno: enable: true - base-url: https://suno-om0w1cy6e-status2xxs-projects.vercel.app +# base-url: https://suno-om0w1cy6e-status2xxs-projects.vercel.app + base-url: http://127.0.0.1:3001 --- #################### 芋道相关配置 ####################