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