Commit ec1376f4cb16bcf076552a8126c3b97e24094452

Authored by YunaiV
1 parent 5c73e5e1

【代码评审】AI:音乐接入

yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateEnum.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.ai.enums.music; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.ai.enums.music;
3 import lombok.AllArgsConstructor; 3 import lombok.AllArgsConstructor;
4 import lombok.Getter; 4 import lombok.Getter;
5 5
  6 +// TODO @xiaoxin:这个也叫 AiMusicGenerateModeEnum 吧。虽然长,但是和项目统一一点。
6 /** 7 /**
7 * AI 音乐状态的枚举 8 * AI 音乐状态的枚举
8 * 9 *
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java
@@ -12,7 +12,7 @@ import lombok.Getter; @@ -12,7 +12,7 @@ import lombok.Getter;
12 @Getter 12 @Getter
13 public enum AiMusicStatusEnum { 13 public enum AiMusicStatusEnum {
14 14
15 - // @xin 文档中无失败这个返回值 15 + // @xin 文档中无失败这个返回值 TODO @xin:用 Integer 哈。另外个枚举类也是
16 STREAMING("10", "进行中"), 16 STREAMING("10", "进行中"),
17 COMPLETE("20", "完成"); 17 COMPLETE("20", "完成");
18 18
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiSunoGenerateReqVO.java
@@ -6,13 +6,11 @@ import lombok.Data; @@ -6,13 +6,11 @@ import lombok.Data;
6 6
7 import java.util.List; 7 import java.util.List;
8 8
9 -/**  
10 - * @author xiaoxin  
11 - */  
12 @Schema(description = "管理后台 - 音乐生成 Request VO") 9 @Schema(description = "管理后台 - 音乐生成 Request VO")
13 @Data 10 @Data
14 public class AiSunoGenerateReqVO { 11 public class AiSunoGenerateReqVO {
15 12
  13 + // TODO @xin:每个参数,必要的是否必填校验
16 @Schema(description = "用于生成音乐音频的提示", example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。") 14 @Schema(description = "用于生成音乐音频的提示", example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。")
17 private String prompt; 15 private String prompt;
18 16
@@ -20,7 +18,7 @@ public class AiSunoGenerateReqVO { @@ -20,7 +18,7 @@ public class AiSunoGenerateReqVO {
20 private Boolean makeInstrumental; 18 private Boolean makeInstrumental;
21 19
22 @Schema(description = "模型版本, 默认 chirp-v3.5", example = "chirp-v3.5") 20 @Schema(description = "模型版本, 默认 chirp-v3.5", example = "chirp-v3.5")
23 - private String modelVersion;// 参见 AiModelEnum 枚举 21 + private String modelVersion; // 参见 AiModelEnum 枚举
24 22
25 @Schema(description = "音乐风格", example = "[\"pop\",\"jazz\",\"punk\"]") 23 @Schema(description = "音乐风格", example = "[\"pop\",\"jazz\",\"punk\"]")
26 private List<String> tags; 24 private List<String> tags;
@@ -30,10 +28,10 @@ public class AiSunoGenerateReqVO { @@ -30,10 +28,10 @@ public class AiSunoGenerateReqVO {
30 28
31 @Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "Suno") 29 @Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "Suno")
32 @NotBlank(message = "平台不能为空") 30 @NotBlank(message = "平台不能为空")
33 - private String platform;// 参见 AiPlatformEnum 枚举 31 + private String platform; // 参见 AiPlatformEnum 枚举
34 32
35 - @Schema(description = "生成模式 1(歌词模式), 2(描述模式)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") 33 + @Schema(description = "生成模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
36 @NotBlank(message = "生成模式不能为空") 34 @NotBlank(message = "生成模式不能为空")
37 - private String generateMode;// 参见 AiMusicGenerateEnum 枚举 35 + private String generateMode; // 参见 AiMusicGenerateEnum 枚举
38 36
39 } 37 }
40 \ No newline at end of file 38 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/image/AiImageDO.java
@@ -68,7 +68,6 @@ public class AiImageDO extends BaseDO { @@ -68,7 +68,6 @@ public class AiImageDO extends BaseDO {
68 */ 68 */
69 private Integer height; 69 private Integer height;
70 70
71 - // TODO @fan:这种就注释绘画状态,然后枚举类关联下就好啦  
72 /** 71 /**
73 * 生成状态 72 * 生成状态
74 * 73 *
@@ -77,6 +76,11 @@ public class AiImageDO extends BaseDO { @@ -77,6 +76,11 @@ public class AiImageDO extends BaseDO {
77 private Integer status; 76 private Integer status;
78 77
79 /** 78 /**
  79 + * 绘画错误信息
  80 + */
  81 + private String errorMessage;
  82 +
  83 + /**
80 * 图片地址 84 * 图片地址
81 */ 85 */
82 private String picUrl; 86 private String picUrl;
@@ -101,15 +105,12 @@ public class AiImageDO extends BaseDO { @@ -101,15 +105,12 @@ public class AiImageDO extends BaseDO {
101 private List<MidjourneyApi.Button> buttons; 105 private List<MidjourneyApi.Button> buttons;
102 106
103 /** 107 /**
104 - * midjourney proxy 关联的 task id 108 + * 任务编号
  109 + *
  110 + * 1. midjourney proxy:关联的 task id
105 */ 111 */
106 private String taskId; 112 private String taskId;
107 113
108 - /**  
109 - * 绘画错误信息  
110 - */  
111 - private String errorMessage;  
112 -  
113 public static class ButtonTypeHandler extends AbstractJsonTypeHandler<Object> { 114 public static class ButtonTypeHandler extends AbstractJsonTypeHandler<Object> {
114 115
115 @Override 116 @Override
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java
@@ -11,7 +11,6 @@ import lombok.Data; @@ -11,7 +11,6 @@ import lombok.Data;
11 11
12 import java.util.List; 12 import java.util.List;
13 13
14 -  
15 /** 14 /**
16 * AI 音乐 DO 15 * AI 音乐 DO
17 * 16 *
@@ -29,6 +28,8 @@ public class AiMusicDO extends BaseDO { @@ -29,6 +28,8 @@ public class AiMusicDO extends BaseDO {
29 28
30 /** 29 /**
31 * 用户编号 30 * 用户编号
  31 + *
  32 + * 关联 AdminUserDO 的 userId 字段
32 */ 33 */
33 private Long userId; 34 private Long userId;
34 35
@@ -105,4 +106,5 @@ public class AiMusicDO extends BaseDO { @@ -105,4 +106,5 @@ public class AiMusicDO extends BaseDO {
105 * 任务编号 106 * 任务编号
106 */ 107 */
107 private String taskId; 108 private String taskId;
  109 +
108 } 110 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/job/image/MidjourneySyncJob.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/job/image/AiMidjourneySyncJob.java
@@ -13,7 +13,7 @@ import org.springframework.stereotype.Component; @@ -13,7 +13,7 @@ import org.springframework.stereotype.Component;
13 */ 13 */
14 @Component 14 @Component
15 @Slf4j 15 @Slf4j
16 -public class MidjourneySyncJob implements JobHandler { 16 +public class AiMidjourneySyncJob implements JobHandler {
17 17
18 @Resource 18 @Resource
19 private AiImageService imageService; 19 private AiImageService imageService;
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/job/SunoJob.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/job/sun/AiSunoSyncJob.java
1 -package cn.iocoder.yudao.module.ai.job; 1 +package cn.iocoder.yudao.module.ai.job.sun;
2 2
3 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; 3 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
4 import cn.iocoder.yudao.module.ai.service.music.AiMusicService; 4 import cn.iocoder.yudao.module.ai.service.music.AiMusicService;
@@ -14,15 +14,16 @@ import org.springframework.stereotype.Component; @@ -14,15 +14,16 @@ import org.springframework.stereotype.Component;
14 */ 14 */
15 @Component 15 @Component
16 @Slf4j 16 @Slf4j
17 -public class SunoJob implements JobHandler { 17 +public class AiSunoSyncJob implements JobHandler {
18 18
19 @Resource 19 @Resource
20 private AiMusicService musicService; 20 private AiMusicService musicService;
21 21
22 @Override 22 @Override
23 public String execute(String param) { 23 public String execute(String param) {
24 - Integer count = musicService.syncMusicTask();  
25 - log.info("[execute][Suno 同步任务数量 [{}] 个]", count);  
26 - return String.format("Suno 同步 - [%s] 个任务", count); 24 + Integer count = musicService.syncMusic();
  25 + log.info("[execute][同步 Suno ({}) 个]", count);
  26 + return String.format("同步 Suno %s 个", count);
27 } 27 }
  28 +
28 } 29 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java
@@ -27,13 +27,12 @@ public interface AiMusicService { @@ -27,13 +27,12 @@ public interface AiMusicService {
27 */ 27 */
28 List<AiMusicDO> getUnCompletedTask(); 28 List<AiMusicDO> getUnCompletedTask();
29 29
30 -  
31 /** 30 /**
32 * 同步音乐任务 31 * 同步音乐任务
33 * 32 *
34 * @return 同步数量 33 * @return 同步数量
35 */ 34 */
36 - Integer syncMusicTask(); 35 + Integer syncMusic();
37 36
38 /** 37 /**
39 * 批量更新音乐信息 38 * 批量更新音乐信息
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java
@@ -39,51 +39,61 @@ public class AiMusicServiceImpl implements AiMusicService { @@ -39,51 +39,61 @@ public class AiMusicServiceImpl implements AiMusicService {
39 public List<Long> generateMusic(AiSunoGenerateReqVO reqVO) { 39 public List<Long> generateMusic(AiSunoGenerateReqVO reqVO) {
40 List<SunoApi.MusicData> musicDataList; 40 List<SunoApi.MusicData> musicDataList;
41 if (Objects.equals(AiMusicGenerateEnum.LYRIC.getMode(), reqVO.getGenerateMode())) { 41 if (Objects.equals(AiMusicGenerateEnum.LYRIC.getMode(), reqVO.getGenerateMode())) {
42 - //歌词模式  
43 - SunoApi.MusicGenerateRequest sunoReq = new SunoApi.MusicGenerateRequest(reqVO.getPrompt(), reqVO.getModelVersion(), CollUtil.join(reqVO.getTags(), StrPool.COMMA), reqVO.getTitle()); 42 + // 1.1 歌词模式
  43 + SunoApi.MusicGenerateRequest sunoReq = new SunoApi.MusicGenerateRequest(
  44 + reqVO.getPrompt(), reqVO.getModelVersion(), CollUtil.join(reqVO.getTags(), StrPool.COMMA), reqVO.getTitle());
44 musicDataList = sunoApi.customGenerate(sunoReq); 45 musicDataList = sunoApi.customGenerate(sunoReq);
45 } else if (Objects.equals(AiMusicGenerateEnum.DESCRIPTION.getMode(), reqVO.getGenerateMode())) { 46 } else if (Objects.equals(AiMusicGenerateEnum.DESCRIPTION.getMode(), reqVO.getGenerateMode())) {
46 - //描述模式  
47 - SunoApi.MusicGenerateRequest sunoReq = new SunoApi.MusicGenerateRequest(reqVO.getPrompt(), reqVO.getModelVersion(), reqVO.getMakeInstrumental()); 47 + // 1.2 描述模式
  48 + SunoApi.MusicGenerateRequest sunoReq = new SunoApi.MusicGenerateRequest(
  49 + reqVO.getPrompt(), reqVO.getModelVersion(), reqVO.getMakeInstrumental());
48 musicDataList = sunoApi.generate(sunoReq); 50 musicDataList = sunoApi.generate(sunoReq);
49 } else { 51 } else {
  52 + // TODO @xin:不用 log error,直接抛异常,吧 reqVO 呆进去,有全局处理的哈
50 log.error("未知的生成模式:{}", reqVO.getGenerateMode()); 53 log.error("未知的生成模式:{}", reqVO.getGenerateMode());
51 throw new IllegalArgumentException("未知的生成模式"); 54 throw new IllegalArgumentException("未知的生成模式");
52 } 55 }
  56 +
53 // 2. 插入数据库 57 // 2. 插入数据库
  58 + // TODO @xin:因为 insertMusicData 复用的比较少,所以不用愁单独的方法,直接写在这里就好啦
54 return insertMusicData(musicDataList, reqVO.getGenerateMode(), reqVO.getPlatform()); 59 return insertMusicData(musicDataList, reqVO.getGenerateMode(), reqVO.getPlatform());
55 } 60 }
56 61
  62 + // TODO @xin:1)service 里面,不要直接查询 db;2)不要用 ne,用 STREAMING 哈
57 @Override 63 @Override
58 public List<AiMusicDO> getUnCompletedTask() { 64 public List<AiMusicDO> getUnCompletedTask() {
59 return musicMapper.selectList(new LambdaQueryWrapper<AiMusicDO>().ne(AiMusicDO::getStatus, AiMusicStatusEnum.COMPLETE.getStatus())); 65 return musicMapper.selectList(new LambdaQueryWrapper<AiMusicDO>().ne(AiMusicDO::getStatus, AiMusicStatusEnum.COMPLETE.getStatus()));
60 } 66 }
61 67
62 @Override 68 @Override
63 - public Integer syncMusicTask() { 69 + public Integer syncMusic() {
64 List<AiMusicDO> unCompletedTask = this.getUnCompletedTask(); 70 List<AiMusicDO> unCompletedTask = this.getUnCompletedTask();
65 if (CollUtil.isEmpty(unCompletedTask)) { 71 if (CollUtil.isEmpty(unCompletedTask)) {
  72 + // TODO @xin:这里不用打,反正 Job 也打了
66 log.info("Suno 无进行中任务需要更新!"); 73 log.info("Suno 无进行中任务需要更新!");
67 return 0; 74 return 0;
68 } 75 }
69 - log.info("Suno 开始同步, 共 [{}] 个任务!", unCompletedTask.size());  
70 - //GET 请求,为避免参数过长,分批次处理  
71 - CollUtil.split(unCompletedTask, 4)  
72 - .forEach(chunk -> {  
73 - Map<String, Long> taskIdMap = CollUtil.toMap(chunk, new HashMap<>(), AiMusicDO::getTaskId, AiMusicDO::getId);  
74 - List<SunoApi.MusicData> musicTaskList = sunoApi.getMusicList(new ArrayList<>(taskIdMap.keySet()));  
75 - if (CollUtil.isNotEmpty(musicTaskList)) {  
76 - List<AiMusicDO> aiMusicDOS = buildMusicDOList(musicTaskList);  
77 - //回填id  
78 - aiMusicDOS.forEach(aiMusicDO -> aiMusicDO.setId(taskIdMap.get(aiMusicDO.getTaskId())));  
79 - this.updateBatch(aiMusicDOS);  
80 - } else {  
81 - log.warn("Suno 任务同步失败, 任务ID: [{}]", taskIdMap.keySet());  
82 - }  
83 - }); 76 + log.info("[syncMusic][Suno 开始同步, 共 ({}) 个任务]", unCompletedTask.size());
  77 + // GET 请求,为避免参数过长,分批次处理
  78 + // TODO @xin:建议批量更大一些。
  79 + CollUtil.split(unCompletedTask, 4).forEach(chunk -> {
  80 + // TODO @xin:可以使用 CollectionUtils 里的 map 转换
  81 + Map<String, Long> taskIdMap = CollUtil.toMap(chunk, new HashMap<>(), AiMusicDO::getTaskId, AiMusicDO::getId);
  82 + List<SunoApi.MusicData> musicTaskList = sunoApi.getMusicList(new ArrayList<>(taskIdMap.keySet()));
  83 + // TODO @xin:查询不到,直接 return;这样真正逻辑的 85 - 87 就不用多一层括号
  84 + if (CollUtil.isNotEmpty(musicTaskList)) {
  85 + List<AiMusicDO> aiMusicDOS = buildMusicDOList(musicTaskList);
  86 + //回填id
  87 + aiMusicDOS.forEach(aiMusicDO -> aiMusicDO.setId(taskIdMap.get(aiMusicDO.getTaskId())));
  88 + this.updateBatch(aiMusicDOS);
  89 + } else {
  90 + log.warn("Suno 任务同步失败, 任务ID: [{}]", taskIdMap.keySet());
  91 + }
  92 + });
84 return unCompletedTask.size(); 93 return unCompletedTask.size();
85 } 94 }
86 95
  96 + // TODO @xin:这个方法,看着不用啦
87 @Override 97 @Override
88 public Boolean updateBatch(List<AiMusicDO> musicDOS) { 98 public Boolean updateBatch(List<AiMusicDO> musicDOS) {
89 return musicMapper.updateBatch(musicDOS); 99 return musicMapper.updateBatch(musicDOS);
@@ -105,6 +115,7 @@ public class AiMusicServiceImpl implements AiMusicService { @@ -105,6 +115,7 @@ public class AiMusicServiceImpl implements AiMusicService {
105 .setPlatform(platform)) 115 .setPlatform(platform))
106 .toList(); 116 .toList();
107 musicMapper.insertBatch(aiMusicDOList); 117 musicMapper.insertBatch(aiMusicDOList);
  118 + // TODO @xin:用 CollectionUtils 简化操作
108 return aiMusicDOList.stream() 119 return aiMusicDOList.stream()
109 .map(AiMusicDO::getId) 120 .map(AiMusicDO::getId)
110 .collect(Collectors.toList()); 121 .collect(Collectors.toList());
@@ -117,6 +128,7 @@ public class AiMusicServiceImpl implements AiMusicService { @@ -117,6 +128,7 @@ public class AiMusicServiceImpl implements AiMusicService {
117 * @return AiMusicDO 集合 128 * @return AiMusicDO 集合
118 */ 129 */
119 private static List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicTaskList) { 130 private static List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicTaskList) {
  131 + // TODO @xin:想通的变量,放在同一行,避免过长。
120 return CollectionUtils.convertList(musicTaskList, musicData -> new AiMusicDO() 132 return CollectionUtils.convertList(musicTaskList, musicData -> new AiMusicDO()
121 .setTaskId(musicData.id()) 133 .setTaskId(musicData.id())
122 .setPrompt(musicData.prompt()) 134 .setPrompt(musicData.prompt())
@@ -128,6 +140,8 @@ public class AiMusicServiceImpl implements AiMusicService { @@ -128,6 +140,8 @@ public class AiMusicServiceImpl implements AiMusicService {
128 .setTitle(musicData.title()) 140 .setTitle(musicData.title())
129 .setStatus(Objects.equals("complete", musicData.status()) ? AiMusicStatusEnum.COMPLETE.getStatus() : AiMusicStatusEnum.STREAMING.getStatus()) 141 .setStatus(Objects.equals("complete", musicData.status()) ? AiMusicStatusEnum.COMPLETE.getStatus() : AiMusicStatusEnum.STREAMING.getStatus())
130 .setModel(musicData.modelName()) 142 .setModel(musicData.modelName())
  143 + // TODO @xin:可以用 hutool 的 StrUtil 的 split 之类的
131 .setTags(StrUtil.isNotBlank(musicData.tags()) ? List.of(musicData.tags().split(StrPool.COMMA)) : null)); 144 .setTags(StrUtil.isNotBlank(musicData.tags()) ? List.of(musicData.tags().split(StrPool.COMMA)) : null));
132 } 145 }
  146 +
133 } 147 }