Commit 23baaff84d84c34f1615ffdee3121ffa2de6d13d
1 parent
c77fc954
【代码优化】AI:音乐生成
Showing
6 changed files
with
62 additions
and
62 deletions
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiSunoGenerateReqVO.java
| ... | ... | @@ -7,11 +7,20 @@ import lombok.Data; |
| 7 | 7 | |
| 8 | 8 | import java.util.List; |
| 9 | 9 | |
| 10 | -@Schema(description = "管理后台 - 音乐生成 Request VO") | |
| 10 | +@Schema(description = "管理后台 - AI 音乐生成 Request VO") | |
| 11 | 11 | @Data |
| 12 | 12 | public class AiSunoGenerateReqVO { |
| 13 | 13 | |
| 14 | - @Schema(description = "用于生成音乐音频的提示", requiredMode = Schema.RequiredMode.REQUIRED, example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。") | |
| 14 | + @Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "Suno") | |
| 15 | + @NotBlank(message = "平台不能为空") | |
| 16 | + private String platform; // 参见 AiPlatformEnum 枚举 | |
| 17 | + | |
| 18 | + @Schema(description = "生成模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") | |
| 19 | + @NotNull(message = "生成模式不能为空") | |
| 20 | + private Integer generateMode; // 参见 AiMusicGenerateEnum 枚举 | |
| 21 | + | |
| 22 | + @Schema(description = "用于生成音乐音频的提示", requiredMode = Schema.RequiredMode.REQUIRED, | |
| 23 | + example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。") | |
| 15 | 24 | private String prompt; |
| 16 | 25 | |
| 17 | 26 | @Schema(description = "是否纯音乐", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "true") |
| ... | ... | @@ -26,12 +35,4 @@ public class AiSunoGenerateReqVO { |
| 26 | 35 | @Schema(description = "音乐/歌曲名称", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "夜空中最亮的星") |
| 27 | 36 | private String title; |
| 28 | 37 | |
| 29 | - @Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "Suno") | |
| 30 | - @NotBlank(message = "平台不能为空") | |
| 31 | - private String platform; // 参见 AiPlatformEnum 枚举 | |
| 32 | - | |
| 33 | - @Schema(description = "生成模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") | |
| 34 | - @NotNull(message = "生成模式不能为空") | |
| 35 | - private Integer generateMode; // 参见 AiMusicGenerateEnum 枚举 | |
| 36 | - | |
| 37 | 38 | } |
| 38 | 39 | \ No newline at end of file | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java
| 1 | 1 | package cn.iocoder.yudao.module.ai.dal.dataobject.music; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; | |
| 3 | 4 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 5 | +import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum; | |
| 4 | 6 | import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum; |
| 5 | 7 | import com.baomidou.mybatisplus.annotation.IdType; |
| 6 | 8 | import com.baomidou.mybatisplus.annotation.TableField; |
| ... | ... | @@ -39,20 +41,18 @@ public class AiMusicDO extends BaseDO { |
| 39 | 41 | private String title; |
| 40 | 42 | |
| 41 | 43 | /** |
| 42 | - * 图片地址 | |
| 43 | - */ | |
| 44 | - private String imageUrl; | |
| 45 | - | |
| 46 | - /** | |
| 47 | 44 | * 歌词 |
| 48 | 45 | */ |
| 49 | 46 | private String lyric; |
| 50 | 47 | |
| 51 | 48 | /** |
| 49 | + * 图片地址 | |
| 50 | + */ | |
| 51 | + private String imageUrl; | |
| 52 | + /** | |
| 52 | 53 | * 音频地址 |
| 53 | 54 | */ |
| 54 | 55 | private String audioUrl; |
| 55 | - | |
| 56 | 56 | /** |
| 57 | 57 | * 视频地址 |
| 58 | 58 | */ |
| ... | ... | @@ -66,6 +66,13 @@ public class AiMusicDO extends BaseDO { |
| 66 | 66 | private Integer status; |
| 67 | 67 | |
| 68 | 68 | /** |
| 69 | + * 生成模式 | |
| 70 | + * | |
| 71 | + * 枚举 {@link AiMusicGenerateModeEnum} | |
| 72 | + */ | |
| 73 | + private Integer generateMode; | |
| 74 | + | |
| 75 | + /** | |
| 69 | 76 | * 描述词 |
| 70 | 77 | */ |
| 71 | 78 | private String gptDescriptionPrompt; |
| ... | ... | @@ -75,28 +82,17 @@ public class AiMusicDO extends BaseDO { |
| 75 | 82 | private String prompt; |
| 76 | 83 | |
| 77 | 84 | /** |
| 78 | - * 生成模式 | |
| 79 | - */ | |
| 80 | - private Integer generateMode; | |
| 81 | - | |
| 82 | - /** | |
| 83 | 85 | * 平台 |
| 84 | 86 | * <p> |
| 85 | - * 枚举 {@link cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum} | |
| 87 | + * 枚举 {@link AiPlatformEnum} | |
| 86 | 88 | */ |
| 87 | 89 | private String platform; |
| 88 | - | |
| 89 | 90 | /** |
| 90 | 91 | * 模型 |
| 91 | 92 | */ |
| 92 | 93 | private String model; |
| 93 | 94 | |
| 94 | 95 | /** |
| 95 | - * 错误信息 | |
| 96 | - */ | |
| 97 | - private String errorMessage; | |
| 98 | - | |
| 99 | - /** | |
| 100 | 96 | * 音乐风格标签 |
| 101 | 97 | */ |
| 102 | 98 | @TableField(typeHandler = JacksonTypeHandler.class) |
| ... | ... | @@ -107,4 +103,9 @@ public class AiMusicDO extends BaseDO { |
| 107 | 103 | */ |
| 108 | 104 | private String taskId; |
| 109 | 105 | |
| 106 | + /** | |
| 107 | + * 错误信息 | |
| 108 | + */ | |
| 109 | + private String errorMessage; | |
| 110 | + | |
| 110 | 111 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/music/AiMusicMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.ai.dal.mysql.music; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 4 | -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | |
| 5 | 4 | import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; |
| 6 | 5 | import org.apache.ibatis.annotations.Mapper; |
| 7 | 6 | |
| ... | ... | @@ -16,8 +15,7 @@ import java.util.List; |
| 16 | 15 | public interface AiMusicMapper extends BaseMapperX<AiMusicDO> { |
| 17 | 16 | |
| 18 | 17 | default List<AiMusicDO> selectListByStatus(Integer status) { |
| 19 | - return selectList(new LambdaQueryWrapperX<AiMusicDO>() | |
| 20 | - .eq(AiMusicDO::getStatus, status)); | |
| 18 | + return selectList(AiMusicDO::getStatus, status); | |
| 21 | 19 | } |
| 22 | 20 | |
| 23 | 21 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java
| ... | ... | @@ -14,10 +14,11 @@ public interface AiMusicService { |
| 14 | 14 | /** |
| 15 | 15 | * 音乐生成 |
| 16 | 16 | * |
| 17 | + * @param userId 用户编号 | |
| 17 | 18 | * @param reqVO 请求参数 |
| 18 | 19 | * @return 生成的音乐ID |
| 19 | 20 | */ |
| 20 | - List<Long> generateMusic(AiSunoGenerateReqVO reqVO); | |
| 21 | + List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO); | |
| 21 | 22 | |
| 22 | 23 | /** |
| 23 | 24 | * 同步音乐任务 |
| ... | ... | @@ -25,4 +26,5 @@ public interface AiMusicService { |
| 25 | 26 | * @return 同步数量 |
| 26 | 27 | */ |
| 27 | 28 | Integer syncMusic(); |
| 29 | + | |
| 28 | 30 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java
| ... | ... | @@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.text.StrPool; |
| 5 | 5 | import cn.hutool.core.util.StrUtil; |
| 6 | 6 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; |
| 7 | -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 8 | 7 | import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO; |
| 9 | 8 | import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; |
| 10 | 9 | import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; |
| ... | ... | @@ -16,7 +15,8 @@ import org.springframework.stereotype.Service; |
| 16 | 15 | |
| 17 | 16 | import java.util.*; |
| 18 | 17 | |
| 19 | -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; | |
| 18 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | |
| 19 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | |
| 20 | 20 | |
| 21 | 21 | /** |
| 22 | 22 | * AI 音乐 Service 实现类 |
| ... | ... | @@ -34,54 +34,53 @@ public class AiMusicServiceImpl implements AiMusicService { |
| 34 | 34 | private AiMusicMapper musicMapper; |
| 35 | 35 | |
| 36 | 36 | @Override |
| 37 | - public List<Long> generateMusic(AiSunoGenerateReqVO reqVO) { | |
| 37 | + public List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO) { | |
| 38 | + // 1. 调用 Suno 生成音乐 | |
| 38 | 39 | List<SunoApi.MusicData> musicDataList; |
| 39 | 40 | if (Objects.equals(AiMusicGenerateModeEnum.LYRIC.getMode(), reqVO.getGenerateMode())) { |
| 40 | 41 | // 1.1 歌词模式 |
| 41 | - SunoApi.MusicGenerateRequest sunoReq = new SunoApi.MusicGenerateRequest( | |
| 42 | + SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest( | |
| 42 | 43 | reqVO.getPrompt(), reqVO.getModelVersion(), CollUtil.join(reqVO.getTags(), StrPool.COMMA), reqVO.getTitle()); |
| 43 | - musicDataList = sunoApi.customGenerate(sunoReq); | |
| 44 | + musicDataList = sunoApi.customGenerate(generateRequest); | |
| 44 | 45 | } else if (Objects.equals(AiMusicGenerateModeEnum.DESCRIPTION.getMode(), reqVO.getGenerateMode())) { |
| 45 | 46 | // 1.2 描述模式 |
| 46 | - SunoApi.MusicGenerateRequest sunoReq = new SunoApi.MusicGenerateRequest( | |
| 47 | + SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest( | |
| 47 | 48 | reqVO.getPrompt(), reqVO.getModelVersion(), reqVO.getMakeInstrumental()); |
| 48 | - musicDataList = sunoApi.generate(sunoReq); | |
| 49 | + musicDataList = sunoApi.generate(generateRequest); | |
| 49 | 50 | } else { |
| 50 | 51 | throw new IllegalArgumentException(StrUtil.format("未知生成模式({})", reqVO)); |
| 51 | 52 | } |
| 53 | + | |
| 52 | 54 | // 2. 插入数据库 |
| 53 | 55 | if (CollUtil.isEmpty(musicDataList)) { |
| 54 | - | |
| 55 | 56 | return Collections.emptyList(); |
| 56 | 57 | } |
| 57 | - List<AiMusicDO> aiMusicDOList = CollectionUtils.convertList(buildMusicDOList(musicDataList), musicDO -> | |
| 58 | - musicDO.setUserId(getLoginUserId()) | |
| 59 | - .setGenerateMode(reqVO.getGenerateMode()) | |
| 60 | - .setPlatform(reqVO.getPlatform() | |
| 61 | - )); | |
| 62 | - musicMapper.insertBatch(aiMusicDOList); | |
| 63 | - return CollectionUtils.convertList(aiMusicDOList, AiMusicDO::getId); | |
| 58 | + List<AiMusicDO> musicList = buildMusicDOList(musicDataList); | |
| 59 | + musicList.forEach(music -> music.setUserId(userId).setPlatform(music.getPlatform()).setGenerateMode(reqVO.getGenerateMode())); | |
| 60 | + musicMapper.insertBatch(musicList); | |
| 61 | + return convertList(musicList, AiMusicDO::getId); | |
| 64 | 62 | } |
| 65 | 63 | |
| 66 | 64 | @Override |
| 67 | 65 | public Integer syncMusic() { |
| 68 | - List<AiMusicDO> streamingTask = musicMapper.selectListByStatus(AiMusicStatusEnum.STREAMING.getStatus()); | |
| 66 | + List<AiMusicDO> streamingTask = musicMapper.selectListByStatus(AiMusicStatusEnum.IN_PROGRESS.getStatus()); | |
| 69 | 67 | if (CollUtil.isEmpty(streamingTask)) { |
| 70 | 68 | return 0; |
| 71 | 69 | } |
| 72 | 70 | log.info("[syncMusic][Suno 开始同步, 共 ({}) 个任务]", streamingTask.size()); |
| 71 | + | |
| 73 | 72 | // GET 请求,为避免参数过长,分批次处理 |
| 74 | - CollUtil.split(streamingTask, 36).forEach(chunk -> { | |
| 75 | - Map<String, Long> taskIdMap = CollectionUtils.convertMap(chunk, AiMusicDO::getTaskId, AiMusicDO::getId); | |
| 73 | + CollUtil.split(streamingTask, 36).forEach(chunkList -> { | |
| 74 | + Map<String, Long> taskIdMap = convertMap(chunkList, AiMusicDO::getTaskId, AiMusicDO::getId); | |
| 76 | 75 | List<SunoApi.MusicData> musicTaskList = sunoApi.getMusicList(new ArrayList<>(taskIdMap.keySet())); |
| 77 | 76 | if (CollUtil.isEmpty(musicTaskList)) { |
| 78 | 77 | log.warn("Suno 任务同步失败, 任务ID: [{}]", taskIdMap.keySet()); |
| 79 | 78 | return; |
| 80 | 79 | } |
| 81 | - List<AiMusicDO> aiMusicDOS = buildMusicDOList(musicTaskList); | |
| 82 | - //回填id | |
| 83 | - aiMusicDOS.forEach(aiMusicDO -> aiMusicDO.setId(taskIdMap.get(aiMusicDO.getTaskId()))); | |
| 84 | - musicMapper.updateBatch(aiMusicDOS); | |
| 80 | + // 更新进度 | |
| 81 | + List<AiMusicDO> updateMusicList = buildMusicDOList(musicTaskList); | |
| 82 | + updateMusicList.forEach(music -> music.setId(taskIdMap.get(music.getTaskId()))); | |
| 83 | + musicMapper.updateBatch(updateMusicList); | |
| 85 | 84 | }); |
| 86 | 85 | return streamingTask.size(); |
| 87 | 86 | } |
| ... | ... | @@ -89,16 +88,16 @@ public class AiMusicServiceImpl implements AiMusicService { |
| 89 | 88 | /** |
| 90 | 89 | * 构建 AiMusicDO 集合 |
| 91 | 90 | * |
| 92 | - * @param musicTaskList suno 音乐任务列表 | |
| 91 | + * @param musicList suno 音乐任务列表 | |
| 93 | 92 | * @return AiMusicDO 集合 |
| 94 | 93 | */ |
| 95 | - private static List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicTaskList) { | |
| 96 | - return CollectionUtils.convertList(musicTaskList, musicData -> new AiMusicDO() | |
| 97 | - .setTaskId(musicData.id()) | |
| 94 | + private static List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) { | |
| 95 | + return convertList(musicList, musicData -> new AiMusicDO() | |
| 96 | + .setTaskId(musicData.id()).setModel(musicData.modelName()) | |
| 98 | 97 | .setPrompt(musicData.prompt()).setGptDescriptionPrompt(musicData.gptDescriptionPrompt()) |
| 99 | 98 | .setAudioUrl(musicData.audioUrl()).setVideoUrl(musicData.videoUrl()).setImageUrl(musicData.imageUrl()) |
| 100 | 99 | .setTitle(musicData.title()).setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA)) |
| 101 | - .setModel(musicData.modelName()).setStatus(Objects.equals("complete", musicData.status()) ? AiMusicStatusEnum.COMPLETE.getStatus() : AiMusicStatusEnum.STREAMING.getStatus())); | |
| 100 | + .setStatus(Objects.equals("complete", musicData.status()) ? AiMusicStatusEnum.SUCCESS.getStatus() : AiMusicStatusEnum.IN_PROGRESS.getStatus())); | |
| 102 | 101 | |
| 103 | 102 | } |
| 104 | 103 | } | ... | ... |