Commit 9d6b615f10cc644a8de61a68e74abcc69998d07c
1 parent
671c3016
【功能新增】AI:音乐管理 50%
Showing
19 changed files
with
326 additions
and
43 deletions
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 { | @@ -29,13 +29,16 @@ public interface ErrorCodeConstants { | ||
| 29 | 29 | ||
| 30 | // ========== API 聊天消息 1-040-004-000 ========== | 30 | // ========== API 聊天消息 1-040-004-000 ========== |
| 31 | 31 | ||
| 32 | - ErrorCode AI_CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "消息不存在!"); | ||
| 33 | - ErrorCode AI_CHAT_STREAM_ERROR = new ErrorCode(1_040_004_001, "Stream 对话异常!"); | 32 | + ErrorCode CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "消息不存在!"); |
| 33 | + ErrorCode CHAT_STREAM_ERROR = new ErrorCode(1_040_004_001, "Stream 对话异常!"); | ||
| 34 | 34 | ||
| 35 | // ========== API 绘画 1-040-005-000 ========== | 35 | // ========== API 绘画 1-040-005-000 ========== |
| 36 | 36 | ||
| 37 | - ErrorCode AI_IMAGE_NOT_EXISTS = new ErrorCode(1_022_005_000, "图片不存在!"); | ||
| 38 | - ErrorCode AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL = new ErrorCode(1_022_005_001, "Midjourney 提交失败!原因:{}"); | ||
| 39 | - ErrorCode AI_IMAGE_CUSTOM_ID_NOT_EXISTS = new ErrorCode(1_022_005_002, "Midjourney 按钮 customId 不存在! {}"); | 37 | + ErrorCode IMAGE_NOT_EXISTS = new ErrorCode(1_022_005_000, "图片不存在!"); |
| 38 | + ErrorCode IMAGE_MIDJOURNEY_SUBMIT_FAIL = new ErrorCode(1_022_005_001, "Midjourney 提交失败!原因:{}"); | ||
| 39 | + ErrorCode IMAGE_CUSTOM_ID_NOT_EXISTS = new ErrorCode(1_022_005_002, "Midjourney 按钮 customId 不存在! {}"); | ||
| 40 | + | ||
| 41 | + // ========== API 音乐 1-040-006-000 ========== | ||
| 42 | + ErrorCode MUSIC_NOT_EXISTS = new ErrorCode(1_022_006_000, "音乐不存在!"); | ||
| 40 | 43 | ||
| 41 | } | 44 | } |
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateModeEnum.java
| 1 | package cn.iocoder.yudao.module.ai.enums.music; | 1 | package cn.iocoder.yudao.module.ai.enums.music; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; | ||
| 3 | import lombok.AllArgsConstructor; | 4 | import lombok.AllArgsConstructor; |
| 4 | import lombok.Getter; | 5 | import lombok.Getter; |
| 5 | 6 | ||
| 7 | +import java.util.Arrays; | ||
| 8 | + | ||
| 6 | /** | 9 | /** |
| 7 | * AI 音乐状态的枚举 | 10 | * AI 音乐状态的枚举 |
| 8 | * | 11 | * |
| @@ -10,7 +13,7 @@ import lombok.Getter; | @@ -10,7 +13,7 @@ import lombok.Getter; | ||
| 10 | */ | 13 | */ |
| 11 | @AllArgsConstructor | 14 | @AllArgsConstructor |
| 12 | @Getter | 15 | @Getter |
| 13 | -public enum AiMusicGenerateModeEnum { | 16 | +public enum AiMusicGenerateModeEnum implements IntArrayValuable { |
| 14 | 17 | ||
| 15 | LYRIC(1, "歌词模式"), | 18 | LYRIC(1, "歌词模式"), |
| 16 | DESCRIPTION(2, "描述模式"); | 19 | DESCRIPTION(2, "描述模式"); |
| @@ -24,4 +27,11 @@ public enum AiMusicGenerateModeEnum { | @@ -24,4 +27,11 @@ public enum AiMusicGenerateModeEnum { | ||
| 24 | */ | 27 | */ |
| 25 | private final String name; | 28 | private final String name; |
| 26 | 29 | ||
| 30 | + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiMusicGenerateModeEnum::getMode).toArray(); | ||
| 31 | + | ||
| 32 | + @Override | ||
| 33 | + public int[] array() { | ||
| 34 | + return ARRAYS; | ||
| 35 | + } | ||
| 36 | + | ||
| 27 | } | 37 | } |
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java
| 1 | package cn.iocoder.yudao.module.ai.enums.music; | 1 | package cn.iocoder.yudao.module.ai.enums.music; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; | ||
| 3 | import lombok.AllArgsConstructor; | 4 | import lombok.AllArgsConstructor; |
| 4 | import lombok.Getter; | 5 | import lombok.Getter; |
| 5 | 6 | ||
| 7 | +import java.util.Arrays; | ||
| 8 | + | ||
| 6 | /** | 9 | /** |
| 7 | * AI 音乐状态的枚举 | 10 | * AI 音乐状态的枚举 |
| 8 | * | 11 | * |
| @@ -10,7 +13,7 @@ import lombok.Getter; | @@ -10,7 +13,7 @@ import lombok.Getter; | ||
| 10 | */ | 13 | */ |
| 11 | @AllArgsConstructor | 14 | @AllArgsConstructor |
| 12 | @Getter | 15 | @Getter |
| 13 | -public enum AiMusicStatusEnum { | 16 | +public enum AiMusicStatusEnum implements IntArrayValuable { |
| 14 | 17 | ||
| 15 | IN_PROGRESS(10, "进行中"), | 18 | IN_PROGRESS(10, "进行中"), |
| 16 | SUCCESS(20, "已完成"); | 19 | SUCCESS(20, "已完成"); |
| @@ -25,4 +28,11 @@ public enum AiMusicStatusEnum { | @@ -25,4 +28,11 @@ public enum AiMusicStatusEnum { | ||
| 25 | */ | 28 | */ |
| 26 | private final String name; | 29 | private final String name; |
| 27 | 30 | ||
| 31 | + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiMusicStatusEnum::getStatus).toArray(); | ||
| 32 | + | ||
| 33 | + @Override | ||
| 34 | + public int[] array() { | ||
| 35 | + return ARRAYS; | ||
| 36 | + } | ||
| 37 | + | ||
| 28 | } | 38 | } |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java
| 1 | package cn.iocoder.yudao.module.ai.controller.admin.image; | 1 | package cn.iocoder.yudao.module.ai.controller.admin.image; |
| 2 | 2 | ||
| 3 | -import cn.hutool.core.collection.CollUtil; | ||
| 4 | import cn.hutool.core.util.ObjUtil; | 3 | import cn.hutool.core.util.ObjUtil; |
| 5 | import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; | 4 | import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; |
| 6 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 5 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| @@ -26,9 +25,7 @@ import org.springframework.security.access.prepost.PreAuthorize; | @@ -26,9 +25,7 @@ import org.springframework.security.access.prepost.PreAuthorize; | ||
| 26 | import org.springframework.validation.annotation.Validated; | 25 | import org.springframework.validation.annotation.Validated; |
| 27 | import org.springframework.web.bind.annotation.*; | 26 | import org.springframework.web.bind.annotation.*; |
| 28 | 27 | ||
| 29 | -import java.util.Collections; | ||
| 30 | import java.util.List; | 28 | import java.util.List; |
| 31 | -import java.util.Objects; | ||
| 32 | 29 | ||
| 33 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | 30 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 34 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; | 31 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| @@ -42,15 +39,16 @@ public class AiImageController { | @@ -42,15 +39,16 @@ public class AiImageController { | ||
| 42 | @Resource | 39 | @Resource |
| 43 | private AiImageService imageService; | 40 | private AiImageService imageService; |
| 44 | 41 | ||
| 45 | - @Operation(summary = "获取【我的】绘图分页") | ||
| 46 | @GetMapping("/my-page") | 42 | @GetMapping("/my-page") |
| 43 | + @Operation(summary = "获取【我的】绘图分页") | ||
| 47 | public CommonResult<PageResult<AiImageRespVO>> getImagePageMy(@Validated PageParam pageReqVO) { | 44 | public CommonResult<PageResult<AiImageRespVO>> getImagePageMy(@Validated PageParam pageReqVO) { |
| 48 | PageResult<AiImageDO> pageResult = imageService.getImagePageMy(getLoginUserId(), pageReqVO); | 45 | PageResult<AiImageDO> pageResult = imageService.getImagePageMy(getLoginUserId(), pageReqVO); |
| 49 | return success(BeanUtils.toBean(pageResult, AiImageRespVO.class)); | 46 | return success(BeanUtils.toBean(pageResult, AiImageRespVO.class)); |
| 50 | } | 47 | } |
| 51 | 48 | ||
| 52 | - @Operation(summary = "获取【我的】绘图记录") | ||
| 53 | @GetMapping("/get-my") | 49 | @GetMapping("/get-my") |
| 50 | + @Operation(summary = "获取【我的】绘图记录") | ||
| 51 | + @Parameter(name = "id", required = true, description = "绘画编号", example = "1024") | ||
| 54 | public CommonResult<AiImageRespVO> getImageMy(@RequestParam("id") Long id) { | 52 | public CommonResult<AiImageRespVO> getImageMy(@RequestParam("id") Long id) { |
| 55 | AiImageDO image = imageService.getImage(id); | 53 | AiImageDO image = imageService.getImage(id); |
| 56 | if (image == null || ObjUtil.notEqual(getLoginUserId(), image.getUserId())) { | 54 | if (image == null || ObjUtil.notEqual(getLoginUserId(), image.getUserId())) { |
| @@ -59,17 +57,13 @@ public class AiImageController { | @@ -59,17 +57,13 @@ public class AiImageController { | ||
| 59 | return success(BeanUtils.toBean(image, AiImageRespVO.class)); | 57 | return success(BeanUtils.toBean(image, AiImageRespVO.class)); |
| 60 | } | 58 | } |
| 61 | 59 | ||
| 62 | - @Operation(summary = "获取【我的】绘图记录 - ids") | ||
| 63 | @GetMapping("/get-my-ids") | 60 | @GetMapping("/get-my-ids") |
| 61 | + @Operation(summary = "获取【我的】绘图记录列表") | ||
| 62 | + @Parameter(name = "ids", required = true, description = "绘画编号数组", example = "1024,2048") | ||
| 64 | public CommonResult<List<AiImageRespVO>> getImageMyIds(@RequestParam("ids") List<Long> ids) { | 63 | public CommonResult<List<AiImageRespVO>> getImageMyIds(@RequestParam("ids") List<Long> ids) { |
| 65 | List<AiImageDO> imageList = imageService.getImageByIds(ids); | 64 | List<AiImageDO> imageList = imageService.getImageByIds(ids); |
| 66 | - if (CollUtil.isEmpty(imageList)) { | ||
| 67 | - return success(Collections.emptyList()); | ||
| 68 | - } | ||
| 69 | - List<AiImageDO> userImageList = imageList.stream() | ||
| 70 | - .map(item -> ObjUtil.equal(getLoginUserId(), item.getUserId()) ? item : null) | ||
| 71 | - .filter(Objects::nonNull).toList(); | ||
| 72 | - return success(BeanUtils.toBean(userImageList, AiImageRespVO.class)); | 65 | + imageList.removeIf(item -> !ObjUtil.equal(getLoginUserId(), item.getUserId())); |
| 66 | + return success(BeanUtils.toBean(imageList, AiImageRespVO.class)); | ||
| 73 | } | 67 | } |
| 74 | 68 | ||
| 75 | @Operation(summary = "生成图片") | 69 | @Operation(summary = "生成图片") |
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 { | @@ -20,7 +20,7 @@ public class AiImagePageReqVO extends PageParam { | ||
| 20 | @Schema(description = "用户编号", example = "28987") | 20 | @Schema(description = "用户编号", example = "28987") |
| 21 | private Long userId; | 21 | private Long userId; |
| 22 | 22 | ||
| 23 | - @Schema(description = "平台") | 23 | + @Schema(description = "平台", example = "OpenAI") |
| 24 | private String platform; | 24 | private String platform; |
| 25 | 25 | ||
| 26 | @Schema(description = "绘画状态", example = "1") | 26 | @Schema(description = "绘画状态", example = "1") |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiImageController.http renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.http
| 1 | -### 生成音乐:Suno + | 1 | +### 生成音乐:Suno + 歌词模式 |
| 2 | POST {{baseUrl}}/ai/music/generate | 2 | POST {{baseUrl}}/ai/music/generate |
| 3 | Content-Type: application/json | 3 | Content-Type: application/json |
| 4 | Authorization: {{token}} | 4 | Authorization: {{token}} |
| @@ -10,4 +10,17 @@ Authorization: {{token}} | @@ -10,4 +10,17 @@ Authorization: {{token}} | ||
| 10 | "modelVersion": "chirp-v3.5", | 10 | "modelVersion": "chirp-v3.5", |
| 11 | "tags": ["Happy"], | 11 | "tags": ["Happy"], |
| 12 | "title": "Happy Song" | 12 | "title": "Happy Song" |
| 13 | +} | ||
| 14 | + | ||
| 15 | +### 生成音乐:Suno + 描述模式 | ||
| 16 | +POST {{baseUrl}}/ai/music/generate | ||
| 17 | +Content-Type: application/json | ||
| 18 | +Authorization: {{token}} | ||
| 19 | + | ||
| 20 | +{ | ||
| 21 | + "platform": "Suno", | ||
| 22 | + "generateMode": 2, | ||
| 23 | + "prompt": "来一首快乐的歌曲", | ||
| 24 | + "makeInstrumental": false, | ||
| 25 | + "title": "Happy Song" | ||
| 13 | } | 26 | } |
| 14 | \ No newline at end of file | 27 | \ No newline at end of file |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java
| 1 | package cn.iocoder.yudao.module.ai.controller.admin.music; | 1 | package cn.iocoder.yudao.module.ai.controller.admin.music; |
| 2 | 2 | ||
| 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; | ||
| 5 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | ||
| 6 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO; | ||
| 7 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicRespVO; | ||
| 8 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicUpdatePublicStatusReqVO; | ||
| 4 | import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO; | 9 | import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO; |
| 10 | +import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; | ||
| 5 | import cn.iocoder.yudao.module.ai.service.music.AiMusicService; | 11 | import cn.iocoder.yudao.module.ai.service.music.AiMusicService; |
| 6 | import io.swagger.v3.oas.annotations.Operation; | 12 | import io.swagger.v3.oas.annotations.Operation; |
| 13 | +import io.swagger.v3.oas.annotations.Parameter; | ||
| 7 | import io.swagger.v3.oas.annotations.tags.Tag; | 14 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 8 | import jakarta.annotation.Resource; | 15 | import jakarta.annotation.Resource; |
| 9 | import jakarta.validation.Valid; | 16 | import jakarta.validation.Valid; |
| 10 | -import org.springframework.web.bind.annotation.PostMapping; | ||
| 11 | -import org.springframework.web.bind.annotation.RequestBody; | ||
| 12 | -import org.springframework.web.bind.annotation.RequestMapping; | ||
| 13 | -import org.springframework.web.bind.annotation.RestController; | 17 | +import org.springframework.security.access.prepost.PreAuthorize; |
| 18 | +import org.springframework.web.bind.annotation.*; | ||
| 14 | 19 | ||
| 15 | import java.util.List; | 20 | import java.util.List; |
| 16 | 21 | ||
| @@ -31,4 +36,31 @@ public class AiMusicController { | @@ -31,4 +36,31 @@ public class AiMusicController { | ||
| 31 | return success(musicService.generateMusic(getLoginUserId(), reqVO)); | 36 | return success(musicService.generateMusic(getLoginUserId(), reqVO)); |
| 32 | } | 37 | } |
| 33 | 38 | ||
| 39 | + // ================ 绘图管理 ================ | ||
| 40 | + | ||
| 41 | + @GetMapping("/page") | ||
| 42 | + @Operation(summary = "获得音乐分页") | ||
| 43 | + @PreAuthorize("@ss.hasPermission('ai:music:query')") | ||
| 44 | + public CommonResult<PageResult<AiMusicRespVO>> getMusicPage(@Valid AiMusicPageReqVO pageReqVO) { | ||
| 45 | + PageResult<AiMusicDO> pageResult = musicService.getMusicPage(pageReqVO); | ||
| 46 | + return success(BeanUtils.toBean(pageResult, AiMusicRespVO.class)); | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + @DeleteMapping("/delete") | ||
| 50 | + @Operation(summary = "删除音乐") | ||
| 51 | + @Parameter(name = "id", description = "编号", required = true) | ||
| 52 | + @PreAuthorize("@ss.hasPermission('ai:music:delete')") | ||
| 53 | + public CommonResult<Boolean> deleteMusic(@RequestParam("id") Long id) { | ||
| 54 | + musicService.deleteMusic(id); | ||
| 55 | + return success(true); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + @PutMapping("/update") | ||
| 59 | + @Operation(summary = "更新音乐发布状态") | ||
| 60 | + @PreAuthorize("@ss.hasPermission('ai:music:update')") | ||
| 61 | + public CommonResult<Boolean> updateMusicPublicStatus(@Valid @RequestBody AiMusicUpdatePublicStatusReqVO updateReqVO) { | ||
| 62 | + musicService.updateMusicPublicStatus(updateReqVO); | ||
| 63 | + return success(true); | ||
| 64 | + } | ||
| 65 | + | ||
| 34 | } | 66 | } |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicPageReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.controller.admin.music.vo; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageParam; | ||
| 4 | +import cn.iocoder.yudao.framework.common.validation.InEnum; | ||
| 5 | +import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum; | ||
| 6 | +import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum; | ||
| 7 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 8 | +import lombok.Data; | ||
| 9 | +import lombok.EqualsAndHashCode; | ||
| 10 | +import lombok.ToString; | ||
| 11 | +import org.springframework.format.annotation.DateTimeFormat; | ||
| 12 | + | ||
| 13 | +import java.time.LocalDateTime; | ||
| 14 | + | ||
| 15 | +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; | ||
| 16 | + | ||
| 17 | +@Schema(description = "管理后台 - AI 音乐分页 Request VO") | ||
| 18 | +@Data | ||
| 19 | +@EqualsAndHashCode(callSuper = true) | ||
| 20 | +@ToString(callSuper = true) | ||
| 21 | +public class AiMusicPageReqVO extends PageParam { | ||
| 22 | + | ||
| 23 | + @Schema(description = "用户编号", example = "12212") | ||
| 24 | + private Long userId; | ||
| 25 | + | ||
| 26 | + @Schema(description = "音乐名称", example = "夜空中最亮的星") | ||
| 27 | + private String title; | ||
| 28 | + | ||
| 29 | + @Schema(description = "音乐状态", example = "20") | ||
| 30 | + @InEnum(AiMusicStatusEnum.class) | ||
| 31 | + private Integer status; | ||
| 32 | + | ||
| 33 | + @Schema(description = "生成模式", example = "1") | ||
| 34 | + @InEnum(AiMusicGenerateModeEnum.class) | ||
| 35 | + private Integer generateMode; | ||
| 36 | + | ||
| 37 | + @Schema(description = "是否发布", example = "true") | ||
| 38 | + private Boolean publicStatus; | ||
| 39 | + | ||
| 40 | + @Schema(description = "创建时间") | ||
| 41 | + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | ||
| 42 | + private LocalDateTime[] createTime; | ||
| 43 | + | ||
| 44 | +} | ||
| 0 | \ No newline at end of file | 45 | \ No newline at end of file |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.controller.admin.music.vo; | ||
| 2 | + | ||
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 4 | +import lombok.Data; | ||
| 5 | + | ||
| 6 | +import java.time.LocalDateTime; | ||
| 7 | +import java.util.List; | ||
| 8 | + | ||
| 9 | +@Schema(description = "管理后台 - AI 音乐 Response VO") | ||
| 10 | +@Data | ||
| 11 | +public class AiMusicRespVO { | ||
| 12 | + | ||
| 13 | + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790") | ||
| 14 | + private Long id; | ||
| 15 | + | ||
| 16 | + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12212") | ||
| 17 | + private Long userId; | ||
| 18 | + | ||
| 19 | + @Schema(description = "音乐名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "夜空中最亮的星") | ||
| 20 | + private String title; | ||
| 21 | + | ||
| 22 | + @Schema(description = "歌词", example = "oh~卖糕的") | ||
| 23 | + private String lyric; | ||
| 24 | + | ||
| 25 | + @Schema(description = "图片地址", example = "https://www.iocoder.cn") | ||
| 26 | + private String imageUrl; | ||
| 27 | + | ||
| 28 | + @Schema(description = "音频地址", example = "https://www.iocoder.cn") | ||
| 29 | + private String audioUrl; | ||
| 30 | + | ||
| 31 | + @Schema(description = "视频地址", example = "https://www.iocoder.cn") | ||
| 32 | + private String videoUrl; | ||
| 33 | + | ||
| 34 | + @Schema(description = "音乐状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") | ||
| 35 | + private Integer status; | ||
| 36 | + | ||
| 37 | + @Schema(description = "描述词", example = "一首轻快的歌曲") | ||
| 38 | + private String gptDescriptionPrompt; | ||
| 39 | + | ||
| 40 | + @Schema(description = "提示词", example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。") | ||
| 41 | + private String prompt; | ||
| 42 | + | ||
| 43 | + @Schema(description = "模型平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "Suno") | ||
| 44 | + private String platform; | ||
| 45 | + | ||
| 46 | + @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5") | ||
| 47 | + private String model; | ||
| 48 | + | ||
| 49 | + @Schema(description = "生成模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | ||
| 50 | + private Integer generateMode; | ||
| 51 | + | ||
| 52 | + @Schema(description = "音乐风格标签") | ||
| 53 | + private List<String> tags; | ||
| 54 | + | ||
| 55 | + @Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") | ||
| 56 | + private Boolean publicStatus; | ||
| 57 | + | ||
| 58 | + @Schema(description = "任务编号", example = "11369") | ||
| 59 | + private String taskId; | ||
| 60 | + | ||
| 61 | + @Schema(description = "错误信息") | ||
| 62 | + private String errorMessage; | ||
| 63 | + | ||
| 64 | + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) | ||
| 65 | + private LocalDateTime createTime; | ||
| 66 | + | ||
| 67 | +} | ||
| 0 | \ No newline at end of file | 68 | \ No newline at end of file |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdatePublicStatusReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.controller.admin.music.vo; | ||
| 2 | + | ||
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 4 | +import jakarta.validation.constraints.NotNull; | ||
| 5 | +import lombok.Data; | ||
| 6 | + | ||
| 7 | +@Schema(description = "管理后台 - AI 音乐修改发布状态 Request VO") | ||
| 8 | +@Data | ||
| 9 | +public class AiMusicUpdatePublicStatusReqVO { | ||
| 10 | + | ||
| 11 | + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583") | ||
| 12 | + private Long id; | ||
| 13 | + | ||
| 14 | + @Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") | ||
| 15 | + @NotNull(message = "是否发布不能为空") | ||
| 16 | + private Boolean publicStatus; | ||
| 17 | + | ||
| 18 | +} | ||
| 0 | \ No newline at end of file | 19 | \ No newline at end of file |
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; | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.music.vo; | ||
| 2 | 2 | ||
| 3 | import io.swagger.v3.oas.annotations.media.Schema; | 3 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | import jakarta.validation.constraints.NotBlank; | 4 | import jakarta.validation.constraints.NotBlank; |
| 5 | +import jakarta.validation.constraints.NotEmpty; | ||
| 5 | import jakarta.validation.constraints.NotNull; | 6 | import jakarta.validation.constraints.NotNull; |
| 6 | import lombok.Data; | 7 | import lombok.Data; |
| 7 | 8 | ||
| @@ -15,6 +16,10 @@ public class AiSunoGenerateReqVO { | @@ -15,6 +16,10 @@ public class AiSunoGenerateReqVO { | ||
| 15 | @NotBlank(message = "平台不能为空") | 16 | @NotBlank(message = "平台不能为空") |
| 16 | private String platform; // 参见 AiPlatformEnum 枚举 | 17 | private String platform; // 参见 AiPlatformEnum 枚举 |
| 17 | 18 | ||
| 19 | + /** | ||
| 20 | + * 1. 描述模式:描述词 + 是否纯音乐 + 模型 TODO @xin:目前貌似描述词没弄对?看着不是 prompt 字段(也可能我弄错了)。可以微信再沟通下哈 | ||
| 21 | + * 2. 歌词模式:歌词 + 音乐风格 + 标题 + 模型 TODO @xin:目前这块少传递了标题; | ||
| 22 | + */ | ||
| 18 | @Schema(description = "生成模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") | 23 | @Schema(description = "生成模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") |
| 19 | @NotNull(message = "生成模式不能为空") | 24 | @NotNull(message = "生成模式不能为空") |
| 20 | private Integer generateMode; // 参见 AiMusicGenerateModeEnum 枚举 | 25 | private Integer generateMode; // 参见 AiMusicGenerateModeEnum 枚举 |
| @@ -26,7 +31,9 @@ public class AiSunoGenerateReqVO { | @@ -26,7 +31,9 @@ public class AiSunoGenerateReqVO { | ||
| 26 | @Schema(description = "是否纯音乐", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "true") | 31 | @Schema(description = "是否纯音乐", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "true") |
| 27 | private Boolean makeInstrumental; | 32 | private Boolean makeInstrumental; |
| 28 | 33 | ||
| 29 | - @Schema(description = "模型版本", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "chirp-v3.5") | 34 | + // TODO @xin:看了下这个字段,发现最终还是 model 合适点;因为它其实是模型 |
| 35 | + @Schema(description = "模型版本", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5") | ||
| 36 | + @NotEmpty(message = "模型不能为空") | ||
| 30 | private String modelVersion; // 参见 AiModelEnum 枚举 | 37 | private String modelVersion; // 参见 AiModelEnum 枚举 |
| 31 | 38 | ||
| 32 | @Schema(description = "音乐风格", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "[\"pop\",\"jazz\",\"punk\"]") | 39 | @Schema(description = "音乐风格", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "[\"pop\",\"jazz\",\"punk\"]") |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java
| @@ -99,6 +99,11 @@ public class AiMusicDO extends BaseDO { | @@ -99,6 +99,11 @@ public class AiMusicDO extends BaseDO { | ||
| 99 | private List<String> tags; | 99 | private List<String> tags; |
| 100 | 100 | ||
| 101 | /** | 101 | /** |
| 102 | + * 是否公开 | ||
| 103 | + */ | ||
| 104 | + private Boolean publicStatus; | ||
| 105 | + | ||
| 106 | + /** | ||
| 102 | * 任务编号 | 107 | * 任务编号 |
| 103 | */ | 108 | */ |
| 104 | private String taskId; | 109 | private String taskId; |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/music/AiMusicMapper.java
| 1 | package cn.iocoder.yudao.module.ai.dal.mysql.music; | 1 | package cn.iocoder.yudao.module.ai.dal.mysql.music; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 3 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | ||
| 6 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO; | ||
| 4 | import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; | 7 | import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; |
| 5 | import org.apache.ibatis.annotations.Mapper; | 8 | import org.apache.ibatis.annotations.Mapper; |
| 6 | 9 | ||
| @@ -18,4 +21,15 @@ public interface AiMusicMapper extends BaseMapperX<AiMusicDO> { | @@ -18,4 +21,15 @@ public interface AiMusicMapper extends BaseMapperX<AiMusicDO> { | ||
| 18 | return selectList(AiMusicDO::getStatus, status); | 21 | return selectList(AiMusicDO::getStatus, status); |
| 19 | } | 22 | } |
| 20 | 23 | ||
| 24 | + default PageResult<AiMusicDO> selectPage(AiMusicPageReqVO reqVO) { | ||
| 25 | + return selectPage(reqVO, new LambdaQueryWrapperX<AiMusicDO>() | ||
| 26 | + .eqIfPresent(AiMusicDO::getUserId, reqVO.getUserId()) | ||
| 27 | + .eqIfPresent(AiMusicDO::getTitle, reqVO.getTitle()) | ||
| 28 | + .eqIfPresent(AiMusicDO::getStatus, reqVO.getStatus()) | ||
| 29 | + .eqIfPresent(AiMusicDO::getGenerateMode, reqVO.getGenerateMode()) | ||
| 30 | + .betweenIfPresent(AiMusicDO::getCreateTime, reqVO.getCreateTime()) | ||
| 31 | + .eqIfPresent(AiMusicDO::getPublicStatus, reqVO.getPublicStatus()) | ||
| 32 | + .orderByDesc(AiMusicDO::getId)); | ||
| 33 | + } | ||
| 34 | + | ||
| 21 | } | 35 | } |
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 | @@ -44,7 +44,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU | ||
| 44 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error; | 44 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error; |
| 45 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | 45 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 46 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | 46 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| 47 | -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.AI_CHAT_MESSAGE_NOT_EXIST; | 47 | +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_MESSAGE_NOT_EXIST; |
| 48 | import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS; | 48 | import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS; |
| 49 | 49 | ||
| 50 | /** | 50 | /** |
| @@ -150,7 +150,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -150,7 +150,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 150 | log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); | 150 | log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); |
| 151 | chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage())); | 151 | chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage())); |
| 152 | }).onErrorResume(error -> { | 152 | }).onErrorResume(error -> { |
| 153 | - return Flux.just(error(ErrorCodeConstants.AI_CHAT_STREAM_ERROR)); | 153 | + return Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR)); |
| 154 | }); | 154 | }); |
| 155 | } | 155 | } |
| 156 | 156 | ||
| @@ -257,7 +257,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -257,7 +257,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 257 | // 1. 校验消息存在 | 257 | // 1. 校验消息存在 |
| 258 | AiChatMessageDO message = chatMessageMapper.selectById(id); | 258 | AiChatMessageDO message = chatMessageMapper.selectById(id); |
| 259 | if (message == null || ObjUtil.notEqual(message.getUserId(), userId)) { | 259 | if (message == null || ObjUtil.notEqual(message.getUserId(), userId)) { |
| 260 | - throw exception(AI_CHAT_MESSAGE_NOT_EXIST); | 260 | + throw exception(CHAT_MESSAGE_NOT_EXIST); |
| 261 | } | 261 | } |
| 262 | // 2. 执行删除 | 262 | // 2. 执行删除 |
| 263 | chatMessageMapper.deleteById(id); | 263 | chatMessageMapper.deleteById(id); |
| @@ -268,7 +268,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -268,7 +268,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 268 | // 1. 校验消息存在 | 268 | // 1. 校验消息存在 |
| 269 | List<AiChatMessageDO> messages = chatMessageMapper.selectListByConversationId(conversationId); | 269 | List<AiChatMessageDO> messages = chatMessageMapper.selectListByConversationId(conversationId); |
| 270 | if (CollUtil.isEmpty(messages) || ObjUtil.notEqual(messages.get(0).getUserId(), userId)) { | 270 | if (CollUtil.isEmpty(messages) || ObjUtil.notEqual(messages.get(0).getUserId(), userId)) { |
| 271 | - throw exception(AI_CHAT_MESSAGE_NOT_EXIST); | 271 | + throw exception(CHAT_MESSAGE_NOT_EXIST); |
| 272 | } | 272 | } |
| 273 | // 2. 执行删除 | 273 | // 2. 执行删除 |
| 274 | chatMessageMapper.deleteBatchIds(convertList(messages, AiChatMessageDO::getId)); | 274 | chatMessageMapper.deleteBatchIds(convertList(messages, AiChatMessageDO::getId)); |
| @@ -279,7 +279,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -279,7 +279,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 279 | // 1. 校验消息存在 | 279 | // 1. 校验消息存在 |
| 280 | AiChatMessageDO message = chatMessageMapper.selectById(id); | 280 | AiChatMessageDO message = chatMessageMapper.selectById(id); |
| 281 | if (message == null) { | 281 | if (message == null) { |
| 282 | - throw exception(AI_CHAT_MESSAGE_NOT_EXIST); | 282 | + throw exception(CHAT_MESSAGE_NOT_EXIST); |
| 283 | } | 283 | } |
| 284 | // 2. 执行删除 | 284 | // 2. 执行删除 |
| 285 | chatMessageMapper.deleteById(id); | 285 | chatMessageMapper.deleteById(id); |
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; | @@ -35,6 +35,7 @@ import org.springframework.scheduling.annotation.Async; | ||
| 35 | import org.springframework.stereotype.Service; | 35 | import org.springframework.stereotype.Service; |
| 36 | import org.springframework.transaction.annotation.Transactional; | 36 | import org.springframework.transaction.annotation.Transactional; |
| 37 | 37 | ||
| 38 | +import java.util.Collections; | ||
| 38 | import java.util.List; | 39 | import java.util.List; |
| 39 | import java.util.Map; | 40 | import java.util.Map; |
| 40 | 41 | ||
| @@ -76,6 +77,9 @@ public class AiImageServiceImpl implements AiImageService { | @@ -76,6 +77,9 @@ public class AiImageServiceImpl implements AiImageService { | ||
| 76 | 77 | ||
| 77 | @Override | 78 | @Override |
| 78 | public List<AiImageDO> getImageByIds(List<Long> ids) { | 79 | public List<AiImageDO> getImageByIds(List<Long> ids) { |
| 80 | + if (CollUtil.isEmpty(ids)) { | ||
| 81 | + return Collections.emptyList(); | ||
| 82 | + } | ||
| 79 | return imageMapper.selectBatchIds(ids); | 83 | return imageMapper.selectBatchIds(ids); |
| 80 | } | 84 | } |
| 81 | 85 | ||
| @@ -135,7 +139,7 @@ public class AiImageServiceImpl implements AiImageService { | @@ -135,7 +139,7 @@ public class AiImageServiceImpl implements AiImageService { | ||
| 135 | // 1. 校验是否存在 | 139 | // 1. 校验是否存在 |
| 136 | AiImageDO image = validateImageExists(id); | 140 | AiImageDO image = validateImageExists(id); |
| 137 | if (ObjUtil.notEqual(image.getUserId(), userId)) { | 141 | if (ObjUtil.notEqual(image.getUserId(), userId)) { |
| 138 | - throw exception(AI_IMAGE_NOT_EXISTS); | 142 | + throw exception(IMAGE_NOT_EXISTS); |
| 139 | } | 143 | } |
| 140 | // 2. 删除记录 | 144 | // 2. 删除记录 |
| 141 | imageMapper.deleteById(id); | 145 | imageMapper.deleteById(id); |
| @@ -165,7 +169,7 @@ public class AiImageServiceImpl implements AiImageService { | @@ -165,7 +169,7 @@ public class AiImageServiceImpl implements AiImageService { | ||
| 165 | private AiImageDO validateImageExists(Long id) { | 169 | private AiImageDO validateImageExists(Long id) { |
| 166 | AiImageDO image = imageMapper.selectById(id); | 170 | AiImageDO image = imageMapper.selectById(id); |
| 167 | if (image == null) { | 171 | if (image == null) { |
| 168 | - throw exception(AI_IMAGE_NOT_EXISTS); | 172 | + throw exception(IMAGE_NOT_EXISTS); |
| 169 | } | 173 | } |
| 170 | return image; | 174 | return image; |
| 171 | } | 175 | } |
| @@ -191,7 +195,7 @@ public class AiImageServiceImpl implements AiImageService { | @@ -191,7 +195,7 @@ public class AiImageServiceImpl implements AiImageService { | ||
| 191 | if (!MidjourneyApi.SubmitCodeEnum.SUCCESS_CODES.contains(imagineResponse.code())) { | 195 | if (!MidjourneyApi.SubmitCodeEnum.SUCCESS_CODES.contains(imagineResponse.code())) { |
| 192 | String description = imagineResponse.description().contains("quota_not_enough") ? | 196 | String description = imagineResponse.description().contains("quota_not_enough") ? |
| 193 | "账户余额不足" : imagineResponse.description(); | 197 | "账户余额不足" : imagineResponse.description(); |
| 194 | - throw exception(AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL, description); | 198 | + throw exception(IMAGE_MIDJOURNEY_SUBMIT_FAIL, description); |
| 195 | } | 199 | } |
| 196 | 200 | ||
| 197 | // 4. 情况二【成功】:更新 taskId 和参数 | 201 | // 4. 情况二【成功】:更新 taskId 和参数 |
| @@ -271,13 +275,13 @@ public class AiImageServiceImpl implements AiImageService { | @@ -271,13 +275,13 @@ public class AiImageServiceImpl implements AiImageService { | ||
| 271 | // 1.1 检查 image | 275 | // 1.1 检查 image |
| 272 | AiImageDO image = validateImageExists(reqVO.getId()); | 276 | AiImageDO image = validateImageExists(reqVO.getId()); |
| 273 | if (ObjUtil.notEqual(userId, image.getUserId())) { | 277 | if (ObjUtil.notEqual(userId, image.getUserId())) { |
| 274 | - throw exception(AI_IMAGE_NOT_EXISTS); | 278 | + throw exception(IMAGE_NOT_EXISTS); |
| 275 | } | 279 | } |
| 276 | // 1.2 检查 customId | 280 | // 1.2 检查 customId |
| 277 | MidjourneyApi.Button button = CollUtil.findOne(image.getButtons(), | 281 | MidjourneyApi.Button button = CollUtil.findOne(image.getButtons(), |
| 278 | buttonX -> buttonX.customId().equals(reqVO.getCustomId())); | 282 | buttonX -> buttonX.customId().equals(reqVO.getCustomId())); |
| 279 | if (button == null) { | 283 | if (button == null) { |
| 280 | - throw exception(AI_IMAGE_CUSTOM_ID_NOT_EXISTS); | 284 | + throw exception(IMAGE_CUSTOM_ID_NOT_EXISTS); |
| 281 | } | 285 | } |
| 282 | 286 | ||
| 283 | // 2. 调用 Midjourney Proxy 提交任务 | 287 | // 2. 调用 Midjourney Proxy 提交任务 |
| @@ -286,7 +290,7 @@ public class AiImageServiceImpl implements AiImageService { | @@ -286,7 +290,7 @@ public class AiImageServiceImpl implements AiImageService { | ||
| 286 | if (!MidjourneyApi.SubmitCodeEnum.SUCCESS_CODES.contains(actionResponse.code())) { | 290 | if (!MidjourneyApi.SubmitCodeEnum.SUCCESS_CODES.contains(actionResponse.code())) { |
| 287 | String description = actionResponse.description().contains("quota_not_enough") ? | 291 | String description = actionResponse.description().contains("quota_not_enough") ? |
| 288 | "账户余额不足" : actionResponse.description(); | 292 | "账户余额不足" : actionResponse.description(); |
| 289 | - throw exception(AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL, description); | 293 | + throw exception(IMAGE_MIDJOURNEY_SUBMIT_FAIL, description); |
| 290 | } | 294 | } |
| 291 | 295 | ||
| 292 | // 3. 新增 image 记录 | 296 | // 3. 新增 image 记录 |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java
| 1 | package cn.iocoder.yudao.module.ai.service.music; | 1 | package cn.iocoder.yudao.module.ai.service.music; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 4 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO; | ||
| 5 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicUpdatePublicStatusReqVO; | ||
| 3 | import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO; | 6 | import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO; |
| 7 | +import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; | ||
| 8 | +import jakarta.validation.Valid; | ||
| 4 | 9 | ||
| 5 | import java.util.List; | 10 | import java.util.List; |
| 6 | 11 | ||
| @@ -27,4 +32,26 @@ public interface AiMusicService { | @@ -27,4 +32,26 @@ public interface AiMusicService { | ||
| 27 | */ | 32 | */ |
| 28 | Integer syncMusic(); | 33 | Integer syncMusic(); |
| 29 | 34 | ||
| 35 | + /** | ||
| 36 | + * 更新音乐发布状态 | ||
| 37 | + * | ||
| 38 | + * @param updateReqVO 更新信息 | ||
| 39 | + */ | ||
| 40 | + void updateMusicPublicStatus(@Valid AiMusicUpdatePublicStatusReqVO updateReqVO); | ||
| 41 | + | ||
| 42 | + /** | ||
| 43 | + * 删除AI 音乐 | ||
| 44 | + * | ||
| 45 | + * @param id 编号 | ||
| 46 | + */ | ||
| 47 | + void deleteMusic(Long id); | ||
| 48 | + | ||
| 49 | + /** | ||
| 50 | + * 获得音乐分页 | ||
| 51 | + * | ||
| 52 | + * @param pageReqVO 分页查询 | ||
| 53 | + * @return 音乐分页 | ||
| 54 | + */ | ||
| 55 | + PageResult<AiMusicDO> getMusicPage(AiMusicPageReqVO pageReqVO); | ||
| 56 | + | ||
| 30 | } | 57 | } |
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; | @@ -4,6 +4,9 @@ import cn.hutool.core.collection.CollUtil; | ||
| 4 | import cn.hutool.core.text.StrPool; | 4 | import cn.hutool.core.text.StrPool; |
| 5 | import cn.hutool.core.util.StrUtil; | 5 | import cn.hutool.core.util.StrUtil; |
| 6 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | 6 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; |
| 7 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 8 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO; | ||
| 9 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicUpdatePublicStatusReqVO; | ||
| 7 | import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO; | 10 | import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO; |
| 8 | import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; | 11 | import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; |
| 9 | import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; | 12 | import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; |
| @@ -15,8 +18,10 @@ import org.springframework.stereotype.Service; | @@ -15,8 +18,10 @@ import org.springframework.stereotype.Service; | ||
| 15 | 18 | ||
| 16 | import java.util.*; | 19 | import java.util.*; |
| 17 | 20 | ||
| 21 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||
| 18 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | 22 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| 19 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | 23 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; |
| 24 | +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MUSIC_NOT_EXISTS; | ||
| 20 | 25 | ||
| 21 | /** | 26 | /** |
| 22 | * AI 音乐 Service 实现类 | 27 | * AI 音乐 Service 实现类 |
| @@ -56,7 +61,7 @@ public class AiMusicServiceImpl implements AiMusicService { | @@ -56,7 +61,7 @@ public class AiMusicServiceImpl implements AiMusicService { | ||
| 56 | return Collections.emptyList(); | 61 | return Collections.emptyList(); |
| 57 | } | 62 | } |
| 58 | List<AiMusicDO> musicList = buildMusicDOList(musicDataList); | 63 | List<AiMusicDO> musicList = buildMusicDOList(musicDataList); |
| 59 | - musicList.forEach(music -> music.setUserId(userId).setPlatform(music.getPlatform()).setGenerateMode(reqVO.getGenerateMode())); | 64 | + musicList.forEach(music -> music.setUserId(userId).setPlatform(reqVO.getPlatform()).setGenerateMode(reqVO.getGenerateMode())); |
| 60 | musicMapper.insertBatch(musicList); | 65 | musicMapper.insertBatch(musicList); |
| 61 | return convertList(musicList, AiMusicDO::getId); | 66 | return convertList(musicList, AiMusicDO::getId); |
| 62 | } | 67 | } |
| @@ -92,12 +97,41 @@ public class AiMusicServiceImpl implements AiMusicService { | @@ -92,12 +97,41 @@ public class AiMusicServiceImpl implements AiMusicService { | ||
| 92 | * @return AiMusicDO 集合 | 97 | * @return AiMusicDO 集合 |
| 93 | */ | 98 | */ |
| 94 | private static List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) { | 99 | private static List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) { |
| 100 | + // TODO @xin:成功的情况下,需要下载到自己的文件服务器。参考图片的处理 | ||
| 95 | return convertList(musicList, musicData -> new AiMusicDO() | 101 | return convertList(musicList, musicData -> new AiMusicDO() |
| 96 | .setTaskId(musicData.id()).setModel(musicData.modelName()) | 102 | .setTaskId(musicData.id()).setModel(musicData.modelName()) |
| 97 | .setPrompt(musicData.prompt()).setGptDescriptionPrompt(musicData.gptDescriptionPrompt()) | 103 | .setPrompt(musicData.prompt()).setGptDescriptionPrompt(musicData.gptDescriptionPrompt()) |
| 98 | .setAudioUrl(musicData.audioUrl()).setVideoUrl(musicData.videoUrl()).setImageUrl(musicData.imageUrl()) | 104 | .setAudioUrl(musicData.audioUrl()).setVideoUrl(musicData.videoUrl()).setImageUrl(musicData.imageUrl()) |
| 99 | .setTitle(musicData.title()).setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA)) | 105 | .setTitle(musicData.title()).setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA)) |
| 100 | - .setStatus(Objects.equals("complete", musicData.status()) ? AiMusicStatusEnum.SUCCESS.getStatus() : AiMusicStatusEnum.IN_PROGRESS.getStatus())); | 106 | + .setStatus(Objects.equals("complete", musicData.status()) ? |
| 107 | + AiMusicStatusEnum.SUCCESS.getStatus() : AiMusicStatusEnum.IN_PROGRESS.getStatus())); | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + @Override | ||
| 111 | + public void updateMusicPublicStatus(AiMusicUpdatePublicStatusReqVO updateReqVO) { | ||
| 112 | + // 校验存在 | ||
| 113 | + validateMusicExists(updateReqVO.getId()); | ||
| 114 | + // 更新 | ||
| 115 | + musicMapper.updateBatch(new AiMusicDO().setPublicStatus(updateReqVO.getPublicStatus())); | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + @Override | ||
| 119 | + public void deleteMusic(Long id) { | ||
| 120 | + // 校验存在 | ||
| 121 | + validateMusicExists(id); | ||
| 122 | + // 删除 | ||
| 123 | + musicMapper.deleteById(id); | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + private void validateMusicExists(Long id) { | ||
| 127 | + if (musicMapper.selectById(id) == null) { | ||
| 128 | + throw exception(MUSIC_NOT_EXISTS); | ||
| 129 | + } | ||
| 130 | + } | ||
| 101 | 131 | ||
| 132 | + @Override | ||
| 133 | + public PageResult<AiMusicDO> getMusicPage(AiMusicPageReqVO pageReqVO) { | ||
| 134 | + return musicMapper.selectPage(pageReqVO); | ||
| 102 | } | 135 | } |
| 136 | + | ||
| 103 | } | 137 | } |
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 { | @@ -17,7 +17,8 @@ public class SunoTests { | ||
| 17 | 17 | ||
| 18 | @Before | 18 | @Before |
| 19 | public void setup() { | 19 | public void setup() { |
| 20 | - String url = "https://suno-om0w1cy6e-status2xxs-projects.vercel.app"; | 20 | +// String url = "https://suno-om0w1cy6e-status2xxs-projects.vercel.app"; |
| 21 | + String url = "http://127.0.0.1:3001"; | ||
| 21 | this.sunoApi = new SunoApi(url); | 22 | this.sunoApi = new SunoApi(url); |
| 22 | } | 23 | } |
| 23 | 24 | ||
| @@ -53,5 +54,4 @@ public class SunoTests { | @@ -53,5 +54,4 @@ public class SunoTests { | ||
| 53 | System.out.println(limitUsageData); | 54 | System.out.println(limitUsageData); |
| 54 | } | 55 | } |
| 55 | 56 | ||
| 56 | - | ||
| 57 | } | 57 | } |
yudao-server/src/main/resources/application.yaml
| @@ -201,7 +201,8 @@ yudao.ai: | @@ -201,7 +201,8 @@ yudao.ai: | ||
| 201 | notify-url: http://java.nat300.top/admin-api/ai/image/midjourney/notify | 201 | notify-url: http://java.nat300.top/admin-api/ai/image/midjourney/notify |
| 202 | suno: | 202 | suno: |
| 203 | enable: true | 203 | enable: true |
| 204 | - base-url: https://suno-om0w1cy6e-status2xxs-projects.vercel.app | 204 | +# base-url: https://suno-om0w1cy6e-status2xxs-projects.vercel.app |
| 205 | + base-url: http://127.0.0.1:3001 | ||
| 205 | 206 | ||
| 206 | --- #################### 芋道相关配置 #################### | 207 | --- #################### 芋道相关配置 #################### |
| 207 | 208 |