Commit 5ba38dbdaae93cd321bc71b1255796a48c0ef8a5
1 parent
88142ed7
【解决todo】AI:音乐接入
Showing
10 changed files
with
144 additions
and
204 deletions
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateEnum.java
| ... | ... | @@ -12,9 +12,8 @@ import lombok.Getter; |
| 12 | 12 | @Getter |
| 13 | 13 | public enum AiMusicGenerateEnum { |
| 14 | 14 | |
| 15 | - // TODO @xin:用数字哈。项目目前枚举都是数字 | |
| 16 | - LYRIC("lyric", "歌词模式"), | |
| 17 | - DESCRIPTION("description", "描述模式"); | |
| 15 | + LYRIC("1", "歌词模式"), | |
| 16 | + DESCRIPTION("2", "描述模式"); | |
| 18 | 17 | |
| 19 | 18 | /** |
| 20 | 19 | * 模式 | ... | ... |
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java
| ... | ... | @@ -12,27 +12,18 @@ import lombok.Getter; |
| 12 | 12 | @Getter |
| 13 | 13 | public enum AiMusicStatusEnum { |
| 14 | 14 | |
| 15 | - // TODO @xin:用数字哈。项目目前枚举都是数字 | |
| 16 | 15 | // @xin 文档中无失败这个返回值 |
| 17 | - STREAMING("streaming", "进行中"), | |
| 18 | - COMPLETE("complete", "完成"); | |
| 16 | + STREAMING("10", "进行中"), | |
| 17 | + COMPLETE("20", "完成"); | |
| 19 | 18 | |
| 20 | 19 | /** |
| 21 | 20 | * 状态 |
| 22 | 21 | */ |
| 23 | 22 | private final String status; |
| 23 | + | |
| 24 | 24 | /** |
| 25 | 25 | * 状态名 |
| 26 | 26 | */ |
| 27 | 27 | private final String name; |
| 28 | 28 | |
| 29 | - public static AiMusicStatusEnum valueOfStatus(String status) { | |
| 30 | - for (AiMusicStatusEnum statusEnum : AiMusicStatusEnum.values()) { | |
| 31 | - if (statusEnum.getStatus().equals(status)) { | |
| 32 | - return statusEnum; | |
| 33 | - } | |
| 34 | - } | |
| 35 | - throw new IllegalArgumentException("未知会话状态: " + status); | |
| 36 | - } | |
| 37 | - | |
| 38 | 29 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java
| 1 | 1 | package cn.iocoder.yudao.module.ai.controller.admin.music; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO; | |
| 4 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO; | |
| 5 | 5 | import cn.iocoder.yudao.module.ai.service.music.AiMusicService; |
| 6 | 6 | import io.swagger.v3.oas.annotations.Operation; |
| 7 | 7 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 8 | +import jakarta.annotation.Resource; | |
| 8 | 9 | import jakarta.validation.Valid; |
| 9 | -import lombok.RequiredArgsConstructor; | |
| 10 | 10 | import org.springframework.web.bind.annotation.PostMapping; |
| 11 | 11 | import org.springframework.web.bind.annotation.RequestBody; |
| 12 | 12 | import org.springframework.web.bind.annotation.RequestMapping; |
| ... | ... | @@ -19,16 +19,15 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 19 | 19 | @Tag(name = "管理后台 - AI 音乐") |
| 20 | 20 | @RestController |
| 21 | 21 | @RequestMapping("/ai/music") |
| 22 | -@RequiredArgsConstructor // TODO @xin:通过 @Resource 注入哈 | |
| 23 | 22 | public class AiMusicController { |
| 24 | 23 | |
| 25 | - // TODO @xin:变量不用有 ai 前缀 | |
| 26 | - private final AiMusicService aiMusicService; | |
| 24 | + @Resource | |
| 25 | + private AiMusicService musicService; | |
| 27 | 26 | |
| 28 | 27 | @PostMapping("/generate") |
| 29 | 28 | @Operation(summary = "音乐生成") |
| 30 | - public CommonResult<List<Long>> generateMusic(@RequestBody @Valid SunoReqVO sunoReqVO) { | |
| 31 | - return success(aiMusicService.generateMusic(sunoReqVO)); | |
| 29 | + public CommonResult<List<Long>> generateMusic(@RequestBody @Valid AiSunoGenerateReqVO reqVO) { | |
| 30 | + return success(musicService.generateMusic(reqVO)); | |
| 32 | 31 | } |
| 33 | 32 | |
| 34 | 33 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiSunoGenerateReqVO.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.NotBlank; | |
| 5 | +import lombok.Data; | |
| 6 | + | |
| 7 | +import java.util.List; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * @author xiaoxin | |
| 11 | + */ | |
| 12 | +@Schema(description = "管理后台 - 音乐生成 Request VO") | |
| 13 | +@Data | |
| 14 | +public class AiSunoGenerateReqVO { | |
| 15 | + | |
| 16 | + @Schema(description = "用于生成音乐音频的提示", example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。") | |
| 17 | + private String prompt; | |
| 18 | + | |
| 19 | + @Schema(description = "是否纯音乐", example = "true") | |
| 20 | + private Boolean makeInstrumental; | |
| 21 | + | |
| 22 | + @Schema(description = "模型版本, 默认 chirp-v3.5", example = "chirp-v3.5") | |
| 23 | + private String modelVersion;// 参见 AiModelEnum 枚举 | |
| 24 | + | |
| 25 | + @Schema(description = "音乐风格", example = "[\"pop\",\"jazz\",\"punk\"]") | |
| 26 | + private List<String> tags; | |
| 27 | + | |
| 28 | + @Schema(description = "音乐/歌曲名称", example = "夜空中最亮的星") | |
| 29 | + private String title; | |
| 30 | + | |
| 31 | + @Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "Suno") | |
| 32 | + @NotBlank(message = "平台不能为空") | |
| 33 | + private String platform;// 参见 AiPlatformEnum 枚举 | |
| 34 | + | |
| 35 | + @Schema(description = "生成模式 1(歌词模式), 2(描述模式)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") | |
| 36 | + @NotBlank(message = "生成模式不能为空") | |
| 37 | + private String generateMode;// 参见 AiMusicGenerateEnum 枚举 | |
| 38 | + | |
| 39 | +} | |
| 0 | 40 | \ 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/SunoReqVO.java deleted
| 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.NotBlank; | |
| 5 | -import lombok.Data; | |
| 6 | - | |
| 7 | -import java.util.List; | |
| 8 | - | |
| 9 | -// TODO @xin:1)ai 前缀;2)AiSunoGenerateReqVO,要有生成哈。3)swaggger 缺少的属性,也最好加下,类似 example,类上的 swagger 等 | |
| 10 | -/** | |
| 11 | - * @author xiaoxin | |
| 12 | - */ | |
| 13 | -@Data | |
| 14 | -public class SunoReqVO { | |
| 15 | - | |
| 16 | - @Schema(description = "用于生成音乐音频的提示") | |
| 17 | - private String prompt; | |
| 18 | - | |
| 19 | - @Schema(description = "是否纯音乐") | |
| 20 | - private Boolean makeInstrumental; | |
| 21 | - | |
| 22 | - // TODO @xin:我们自己是不是用 modelVersion?还是什么梗精准,减少非必要的缩写 | |
| 23 | - @Schema(description = "模型版本") | |
| 24 | - private String mv; | |
| 25 | - | |
| 26 | - @Schema(description = "音乐风格") | |
| 27 | - private List<String> tags; | |
| 28 | - | |
| 29 | - @Schema(description = "音乐/歌曲名称") | |
| 30 | - private String title; | |
| 31 | - | |
| 32 | - @Schema(description = "平台") | |
| 33 | - @NotBlank(message = "平台不能为空") | |
| 34 | - private String platform; | |
| 35 | - | |
| 36 | - @Schema(description = "生成模式 lyric(歌词模式), description(描述模式)") | |
| 37 | - @NotBlank(message = "生成模式不能为空") | |
| 38 | - private String generateMode; | |
| 39 | - | |
| 40 | -} | |
| 41 | 0 | \ 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.common.util.json.JsonUtils; | |
| 4 | 3 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 5 | 4 | import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum; |
| 6 | 5 | import com.baomidou.mybatisplus.annotation.IdType; |
| 7 | 6 | import com.baomidou.mybatisplus.annotation.TableField; |
| 8 | 7 | import com.baomidou.mybatisplus.annotation.TableId; |
| 9 | 8 | import com.baomidou.mybatisplus.annotation.TableName; |
| 10 | -import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; | |
| 9 | +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; | |
| 11 | 10 | import lombok.Data; |
| 12 | 11 | |
| 13 | 12 | import java.util.List; |
| 14 | 13 | |
| 15 | -// TODO @xin:注释完善下 | |
| 14 | + | |
| 16 | 15 | /** |
| 17 | - * @Author xiaoxin | |
| 18 | - * @Date 2024/6/5 | |
| 16 | + * AI 音乐 DO | |
| 17 | + * | |
| 18 | + * @author xiaoxin | |
| 19 | 19 | */ |
| 20 | 20 | @TableName("ai_music") |
| 21 | 21 | @Data |
| ... | ... | @@ -98,25 +98,11 @@ public class AiMusicDO extends BaseDO { |
| 98 | 98 | /** |
| 99 | 99 | * 音乐风格标签 |
| 100 | 100 | */ |
| 101 | - @TableField(typeHandler = AiMusicTagsHandler.class) | |
| 101 | + @TableField(typeHandler = JacksonTypeHandler.class) | |
| 102 | 102 | private List<String> tags; |
| 103 | 103 | |
| 104 | 104 | /** |
| 105 | 105 | * 任务编号 |
| 106 | 106 | */ |
| 107 | 107 | private String taskId; |
| 108 | - | |
| 109 | - // TODO @xin:用 @TableField(typeHandler = JacksonTypeHandler.class) 替代即可 | |
| 110 | - public static class AiMusicTagsHandler extends AbstractJsonTypeHandler<Object> { | |
| 111 | - | |
| 112 | - @Override | |
| 113 | - protected Object parse(String json) { | |
| 114 | - return JsonUtils.parseArray(json, String.class); | |
| 115 | - } | |
| 116 | - | |
| 117 | - @Override | |
| 118 | - protected String toJson(Object obj) { | |
| 119 | - return JsonUtils.toJsonString(obj); | |
| 120 | - } | |
| 121 | - } | |
| 122 | 108 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/job/SunoJob.java
| 1 | 1 | package cn.iocoder.yudao.module.ai.job; |
| 2 | 2 | |
| 3 | -import cn.hutool.core.collection.CollUtil; | |
| 4 | -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | |
| 5 | 3 | import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; |
| 6 | -import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; | |
| 7 | -import cn.iocoder.yudao.module.ai.service.music.AiMusicConvert; | |
| 8 | 4 | import cn.iocoder.yudao.module.ai.service.music.AiMusicService; |
| 9 | 5 | import jakarta.annotation.Resource; |
| 10 | 6 | import lombok.extern.slf4j.Slf4j; |
| 11 | 7 | import org.springframework.stereotype.Component; |
| 12 | 8 | |
| 13 | -import java.util.ArrayList; | |
| 14 | -import java.util.HashMap; | |
| 15 | -import java.util.List; | |
| 16 | -import java.util.Map; | |
| 17 | 9 | |
| 18 | -// TODO @xin:不要直接叫这个名字哈,要有它的目的 | |
| 19 | 10 | /** |
| 20 | - * 处理 Suno Job | |
| 11 | + * 同步 Suno 任务状态以及回写对应的音乐信息 Job | |
| 12 | + * | |
| 21 | 13 | * @author xiaoxin |
| 22 | 14 | */ |
| 23 | 15 | @Component |
| ... | ... | @@ -25,37 +17,12 @@ import java.util.Map; |
| 25 | 17 | public class SunoJob implements JobHandler { |
| 26 | 18 | |
| 27 | 19 | @Resource |
| 28 | - private SunoApi sunoApi; | |
| 29 | - @Resource | |
| 30 | 20 | private AiMusicService musicService; |
| 31 | 21 | |
| 32 | 22 | @Override |
| 33 | 23 | public String execute(String param) { |
| 34 | - // TODO @xin:可以考虑,整个逻辑都下沉到 Service 里,有点类似 AccessLogCleanJob | |
| 35 | - List<AiMusicDO> unCompletedTask = musicService.getUnCompletedTask(); | |
| 36 | - | |
| 37 | - if (CollUtil.isEmpty(unCompletedTask)) { | |
| 38 | - log.info("Suno 无进行中任务需要更新!"); | |
| 39 | - return "Suno 无进行中任务需要更新!"; | |
| 40 | - } | |
| 41 | - | |
| 42 | - | |
| 43 | - log.info("Suno 开始同步, 共 [{}] 个任务!", unCompletedTask.size()); | |
| 44 | - //GET 请求,为避免参数过长,分批次处理 | |
| 45 | - CollUtil.split(unCompletedTask, 4) | |
| 46 | - .forEach(chunk -> { | |
| 47 | - Map<String, Long> taskIdMap = CollUtil.toMap(chunk, new HashMap<>(), AiMusicDO::getTaskId, AiMusicDO::getId); | |
| 48 | - List<SunoApi.MusicData> musicTaskList = sunoApi.getMusicList(new ArrayList<>(taskIdMap.keySet())); | |
| 49 | - if (CollUtil.isNotEmpty(musicTaskList)) { | |
| 50 | - List<AiMusicDO> aiMusicDOS = AiMusicConvert.convertFrom(musicTaskList); | |
| 51 | - //回填id | |
| 52 | - aiMusicDOS.forEach(aiMusicDO -> aiMusicDO.setId(taskIdMap.get(aiMusicDO.getTaskId()))); | |
| 53 | - musicService.updateBatch(aiMusicDOS); | |
| 54 | - } else { | |
| 55 | - log.warn("Suno 任务同步失败, 任务ID: [{}]", taskIdMap.keySet()); | |
| 56 | - } | |
| 57 | - }); | |
| 58 | - | |
| 59 | - return "Suno 同步 - ".concat(String.valueOf(unCompletedTask.size())).concat(" 个任务!"); | |
| 24 | + Integer count = musicService.syncMusicTask(); | |
| 25 | + log.info("[execute][Suno 同步任务数量 [{}] 个]", count); | |
| 26 | + return String.format("Suno 同步 - [%s] 个任务", count); | |
| 60 | 27 | } |
| 61 | 28 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicConvert.java deleted
| 1 | -package cn.iocoder.yudao.module.ai.service.music; | |
| 2 | - | |
| 3 | -import cn.hutool.core.text.StrPool; | |
| 4 | -import cn.hutool.core.util.StrUtil; | |
| 5 | -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | |
| 6 | -import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; | |
| 7 | - | |
| 8 | -import java.util.List; | |
| 9 | -import java.util.stream.Collectors; | |
| 10 | - | |
| 11 | -/** | |
| 12 | - * AI 音乐 Convert | |
| 13 | - * | |
| 14 | - * @author xiaoxin | |
| 15 | - */ | |
| 16 | -public class AiMusicConvert { | |
| 17 | - | |
| 18 | - public static AiMusicDO convertFrom(SunoApi.MusicData musicData) { | |
| 19 | - return new AiMusicDO() | |
| 20 | - .setTaskId(musicData.id()) | |
| 21 | - .setPrompt(musicData.prompt()) | |
| 22 | - .setGptDescriptionPrompt(musicData.gptDescriptionPrompt()) | |
| 23 | - .setAudioUrl(musicData.audioUrl()) | |
| 24 | - .setVideoUrl(musicData.videoUrl()) | |
| 25 | - .setImageUrl(musicData.imageUrl()) | |
| 26 | - .setLyric(musicData.lyric()) | |
| 27 | - .setTitle(musicData.title()) | |
| 28 | - .setStatus(musicData.status()) | |
| 29 | - .setModel(musicData.modelName()) | |
| 30 | - .setTags(StrUtil.isNotBlank(musicData.tags()) ? List.of(musicData.tags().split(StrPool.COMMA)) : null); | |
| 31 | - } | |
| 32 | - | |
| 33 | - // TODO @xin:一般情况下,不用 convert,直接逻辑里 convert 就好啦。 | |
| 34 | - public static List<AiMusicDO> convertFrom(List<SunoApi.MusicData> list) { | |
| 35 | - // TODO @xin:可以使用 CollectionUtils.convertList 简洁一点 | |
| 36 | - return list.stream() | |
| 37 | - .map(AiMusicConvert::convertFrom) | |
| 38 | - .collect(Collectors.toList()); | |
| 39 | - } | |
| 40 | - | |
| 41 | - | |
| 42 | -} |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java
| 1 | 1 | package cn.iocoder.yudao.module.ai.service.music; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO; | |
| 3 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO; | |
| 4 | 4 | import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; |
| 5 | 5 | |
| 6 | 6 | import java.util.List; |
| ... | ... | @@ -18,7 +18,7 @@ public interface AiMusicService { |
| 18 | 18 | * @param reqVO 请求参数 |
| 19 | 19 | * @return 生成的音乐ID |
| 20 | 20 | */ |
| 21 | - List<Long> generateMusic(SunoReqVO reqVO); | |
| 21 | + List<Long> generateMusic(AiSunoGenerateReqVO reqVO); | |
| 22 | 22 | |
| 23 | 23 | /** |
| 24 | 24 | * 获取未完成状态的任务 |
| ... | ... | @@ -27,6 +27,20 @@ public interface AiMusicService { |
| 27 | 27 | */ |
| 28 | 28 | List<AiMusicDO> getUnCompletedTask(); |
| 29 | 29 | |
| 30 | - Boolean updateBatch(List<AiMusicDO> aiMusicDOList); | |
| 30 | + | |
| 31 | + /** | |
| 32 | + * 同步音乐任务 | |
| 33 | + * | |
| 34 | + * @return 同步数量 | |
| 35 | + */ | |
| 36 | + Integer syncMusicTask(); | |
| 37 | + | |
| 38 | + /** | |
| 39 | + * 批量更新音乐信息 | |
| 40 | + * | |
| 41 | + * @param musicDOS 音乐信息 | |
| 42 | + * @return 是否成功 | |
| 43 | + */ | |
| 44 | + Boolean updateBatch(List<AiMusicDO> musicDOS); | |
| 31 | 45 | |
| 32 | 46 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java
| ... | ... | @@ -2,8 +2,10 @@ package cn.iocoder.yudao.module.ai.service.music; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.text.StrPool; |
| 5 | +import cn.hutool.core.util.StrUtil; | |
| 5 | 6 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; |
| 6 | -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO; | |
| 7 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 8 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO; | |
| 7 | 9 | import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; |
| 8 | 10 | import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; |
| 9 | 11 | import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateEnum; |
| ... | ... | @@ -13,8 +15,7 @@ import jakarta.annotation.Resource; |
| 13 | 15 | import lombok.extern.slf4j.Slf4j; |
| 14 | 16 | import org.springframework.stereotype.Service; |
| 15 | 17 | |
| 16 | -import java.util.Collections; | |
| 17 | -import java.util.List; | |
| 18 | +import java.util.*; | |
| 18 | 19 | import java.util.stream.Collectors; |
| 19 | 20 | |
| 20 | 21 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| ... | ... | @@ -35,13 +36,22 @@ public class AiMusicServiceImpl implements AiMusicService { |
| 35 | 36 | private AiMusicMapper musicMapper; |
| 36 | 37 | |
| 37 | 38 | @Override |
| 38 | - public List<Long> generateMusic(SunoReqVO reqVO) { | |
| 39 | - // TODO @xin:是不是可以 if else 调用对应的 API,然后 insertMusicData 逻辑 | |
| 40 | - AiMusicGenerateEnum generateEnum = AiMusicGenerateEnum.valueOfMode(reqVO.getGenerateMode()); | |
| 41 | - return switch (generateEnum) { | |
| 42 | - case DESCRIPTION -> descriptionMode(reqVO); | |
| 43 | - case LYRIC -> lyricMode(reqVO); | |
| 44 | - }; | |
| 39 | + public List<Long> generateMusic(AiSunoGenerateReqVO reqVO) { | |
| 40 | + List<SunoApi.MusicData> musicDataList; | |
| 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()); | |
| 44 | + musicDataList = sunoApi.customGenerate(sunoReq); | |
| 45 | + } else if (Objects.equals(AiMusicGenerateEnum.DESCRIPTION.getMode(), reqVO.getGenerateMode())) { | |
| 46 | + //描述模式 | |
| 47 | + SunoApi.MusicGenerateRequest sunoReq = new SunoApi.MusicGenerateRequest(reqVO.getPrompt(), reqVO.getModelVersion(), reqVO.getMakeInstrumental()); | |
| 48 | + musicDataList = sunoApi.generate(sunoReq); | |
| 49 | + } else { | |
| 50 | + log.error("未知的生成模式:{}", reqVO.getGenerateMode()); | |
| 51 | + throw new IllegalArgumentException("未知的生成模式"); | |
| 52 | + } | |
| 53 | + // 2. 插入数据库 | |
| 54 | + return insertMusicData(musicDataList, reqVO.getGenerateMode(), reqVO.getPlatform()); | |
| 45 | 55 | } |
| 46 | 56 | |
| 47 | 57 | @Override |
| ... | ... | @@ -50,36 +60,33 @@ public class AiMusicServiceImpl implements AiMusicService { |
| 50 | 60 | } |
| 51 | 61 | |
| 52 | 62 | @Override |
| 53 | - public Boolean updateBatch(List<AiMusicDO> aiMusicDOList) { | |
| 54 | - return musicMapper.updateBatch(aiMusicDOList); | |
| 55 | - } | |
| 56 | - | |
| 57 | - /** | |
| 58 | - * 描述模式生成音乐 | |
| 59 | - * | |
| 60 | - * @param reqVO 请求参数 | |
| 61 | - * @return 生成的音乐ID集合 | |
| 62 | - */ | |
| 63 | - public List<Long> descriptionMode(SunoReqVO reqVO) { | |
| 64 | - // 1. 异步生成 | |
| 65 | - SunoApi.MusicGenerateRequest sunoReq = new SunoApi.MusicGenerateRequest(reqVO.getPrompt(), reqVO.getMv(), reqVO.getMakeInstrumental()); | |
| 66 | - List<SunoApi.MusicData> musicDataList = sunoApi.generate(sunoReq); | |
| 67 | - // 2. 插入数据库 | |
| 68 | - return insertMusicData(musicDataList, reqVO.getGenerateMode(), reqVO.getPlatform()); | |
| 63 | + public Integer syncMusicTask() { | |
| 64 | + List<AiMusicDO> unCompletedTask = this.getUnCompletedTask(); | |
| 65 | + if (CollUtil.isEmpty(unCompletedTask)) { | |
| 66 | + log.info("Suno 无进行中任务需要更新!"); | |
| 67 | + return 0; | |
| 68 | + } | |
| 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 | + }); | |
| 84 | + return unCompletedTask.size(); | |
| 69 | 85 | } |
| 70 | 86 | |
| 71 | - /** | |
| 72 | - * 歌词模式生成音乐 | |
| 73 | - * | |
| 74 | - * @param reqVO 请求参数 | |
| 75 | - * @return 生成的音乐ID集合 | |
| 76 | - */ | |
| 77 | - public List<Long> lyricMode(SunoReqVO reqVO) { | |
| 78 | - // 1. 异步生成 | |
| 79 | - SunoApi.MusicGenerateRequest sunoReq = new SunoApi.MusicGenerateRequest(reqVO.getPrompt(), reqVO.getMv(), CollUtil.join(reqVO.getTags(), StrPool.COMMA), reqVO.getTitle()); | |
| 80 | - List<SunoApi.MusicData> musicDataList = sunoApi.customGenerate(sunoReq); | |
| 81 | - // 2. 插入数据库 | |
| 82 | - return insertMusicData(musicDataList, reqVO.getGenerateMode(), reqVO.getPlatform()); | |
| 87 | + @Override | |
| 88 | + public Boolean updateBatch(List<AiMusicDO> musicDOS) { | |
| 89 | + return musicMapper.updateBatch(musicDOS); | |
| 83 | 90 | } |
| 84 | 91 | |
| 85 | 92 | /** |
| ... | ... | @@ -92,7 +99,7 @@ public class AiMusicServiceImpl implements AiMusicService { |
| 92 | 99 | if (CollUtil.isEmpty(musicDataList)) { |
| 93 | 100 | return Collections.emptyList(); |
| 94 | 101 | } |
| 95 | - List<AiMusicDO> aiMusicDOList = AiMusicConvert.convertFrom(musicDataList).stream() | |
| 102 | + List<AiMusicDO> aiMusicDOList = buildMusicDOList(musicDataList).stream() | |
| 96 | 103 | .map(musicDO -> musicDO.setUserId(getLoginUserId()) |
| 97 | 104 | .setGenerateMode(generateMode) |
| 98 | 105 | .setPlatform(platform)) |
| ... | ... | @@ -103,4 +110,24 @@ public class AiMusicServiceImpl implements AiMusicService { |
| 103 | 110 | .collect(Collectors.toList()); |
| 104 | 111 | } |
| 105 | 112 | |
| 113 | + /** | |
| 114 | + * 构建 AiMusicDO 集合 | |
| 115 | + * | |
| 116 | + * @param musicTaskList suno 音乐任务列表 | |
| 117 | + * @return AiMusicDO 集合 | |
| 118 | + */ | |
| 119 | + private static List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicTaskList) { | |
| 120 | + return CollectionUtils.convertList(musicTaskList, musicData -> new AiMusicDO() | |
| 121 | + .setTaskId(musicData.id()) | |
| 122 | + .setPrompt(musicData.prompt()) | |
| 123 | + .setGptDescriptionPrompt(musicData.gptDescriptionPrompt()) | |
| 124 | + .setAudioUrl(musicData.audioUrl()) | |
| 125 | + .setVideoUrl(musicData.videoUrl()) | |
| 126 | + .setImageUrl(musicData.imageUrl()) | |
| 127 | + .setLyric(musicData.lyric()) | |
| 128 | + .setTitle(musicData.title()) | |
| 129 | + .setStatus(Objects.equals("complete", musicData.status()) ? AiMusicStatusEnum.COMPLETE.getStatus() : AiMusicStatusEnum.STREAMING.getStatus()) | |
| 130 | + .setModel(musicData.modelName()) | |
| 131 | + .setTags(StrUtil.isNotBlank(musicData.tags()) ? List.of(musicData.tags().split(StrPool.COMMA)) : null)); | |
| 132 | + } | |
| 106 | 133 | } | ... | ... |