Commit 23baaff84d84c34f1615ffdee3121ffa2de6d13d

Authored by YunaiV
1 parent c77fc954

【代码优化】AI:音乐生成

yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java
... ... @@ -12,9 +12,8 @@ import lombok.Getter;
12 12 @Getter
13 13 public enum AiMusicStatusEnum {
14 14  
15   - // @xin 文档中无失败这个返回值
16   - STREAMING(10, "进行中"),
17   - COMPLETE(20, "完成");
  15 + IN_PROGRESS(10, "进行中"),
  16 + SUCCESS(20, "已完成");
18 17  
19 18 /**
20 19 * 状态
... ...
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 }
... ...