mirror of
				https://github.com/YunaiV/ruoyi-vue-pro.git
				synced 2025-11-04 08:06:12 +08:00 
			
		
		
		
	Merge remote-tracking branch 'origin/master-jdk21-ai' into master-jdk21-ai
This commit is contained in:
		@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.ai.enums;
 | 
				
			|||||||
import lombok.AllArgsConstructor;
 | 
					import lombok.AllArgsConstructor;
 | 
				
			||||||
import lombok.Getter;
 | 
					import lombok.Getter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO @xin:这个类,挪到 enums/music 包下;
 | 
				
			||||||
 | 
					// TODO @xin:1)@author 这个是标准的 javadoc;2)@date 可以不要哈;3)可以加下枚举类的注释
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @Author xiaoxin
 | 
					 * @Author xiaoxin
 | 
				
			||||||
 * @Date 2024/6/5
 | 
					 * @Date 2024/6/5
 | 
				
			||||||
@ -11,6 +13,8 @@ import lombok.Getter;
 | 
				
			|||||||
@Getter
 | 
					@Getter
 | 
				
			||||||
public enum AiMusicStatusEnum {
 | 
					public enum AiMusicStatusEnum {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO @xin:是不是收敛成,只有 3 个:进行中,成功,失败;类似 AiImageStatusEnum
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SUBMITTED("submitted", "已提交"),
 | 
					    SUBMITTED("submitted", "已提交"),
 | 
				
			||||||
    QUEUED("queued", "排队中"),
 | 
					    QUEUED("queued", "排队中"),
 | 
				
			||||||
    STREAMING("streaming", "进行中"),
 | 
					    STREAMING("streaming", "进行中"),
 | 
				
			||||||
 | 
				
			|||||||
@ -50,6 +50,7 @@ public enum AiModelEnum {
 | 
				
			|||||||
    XING_HUO_3_0("星火大模型3.0", "generalv3", "/v3.1/chat"),
 | 
					    XING_HUO_3_0("星火大模型3.0", "generalv3", "/v3.1/chat"),
 | 
				
			||||||
    XING_HUO_3_5("星火大模型3.5", "generalv3.5", "/v3.5/chat"),
 | 
					    XING_HUO_3_5("星火大模型3.5", "generalv3.5", "/v3.5/chat"),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO @xin:// Suno;中间加个空格,会更清晰一点。一般来说,不同类型的单词之间,最好有空格。例如说,// 新增一个;再例如说;// 这是 1 个 create 逻辑
 | 
				
			||||||
    //Suno
 | 
					    //Suno
 | 
				
			||||||
    SUNO_2( "SUNO-2", "chirp-v2-xxl-alpha",null),
 | 
					    SUNO_2( "SUNO-2", "chirp-v2-xxl-alpha",null),
 | 
				
			||||||
    SUNO_3_0( "SUNO-3.0", "chirp-v3-0",null),
 | 
					    SUNO_3_0( "SUNO-3.0", "chirp-v3-0",null),
 | 
				
			||||||
 | 
				
			|||||||
@ -17,6 +17,7 @@ import java.util.List;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 | 
					import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO @xin:AI 前缀;都要加下哈
 | 
				
			||||||
@Tag(name = "管理后台 - AI 音乐生成")
 | 
					@Tag(name = "管理后台 - AI 音乐生成")
 | 
				
			||||||
@RestController
 | 
					@RestController
 | 
				
			||||||
@RequestMapping("/ai/music")
 | 
					@RequestMapping("/ai/music")
 | 
				
			||||||
 | 
				
			|||||||
@ -4,12 +4,13 @@ import com.fasterxml.jackson.annotation.JsonInclude;
 | 
				
			|||||||
import lombok.Data;
 | 
					import lombok.Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Data
 | 
					@Data
 | 
				
			||||||
@JsonInclude(value = JsonInclude.Include.NON_NULL)
 | 
					@JsonInclude(value = JsonInclude.Include.NON_NULL) // TODO @xin:不用加这个哈
 | 
				
			||||||
public class SunoReqVO {
 | 
					public class SunoReqVO {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 用于生成音乐音频的提示
 | 
					     * 用于生成音乐音频的提示
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private String prompt;
 | 
					    private String prompt;
 | 
				
			||||||
 | 
					    // TODO @xin:Boolean,不使用基本类型。
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     *  是否纯音乐
 | 
					     *  是否纯音乐
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
 | 
				
			|||||||
@ -18,6 +18,8 @@ import java.util.stream.Collectors;
 | 
				
			|||||||
@TableName("ai_music")
 | 
					@TableName("ai_music")
 | 
				
			||||||
@Data
 | 
					@Data
 | 
				
			||||||
public class AiMusicDO extends BaseDO {
 | 
					public class AiMusicDO extends BaseDO {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO @xin:@Schema 只在 VO 里使用,这里还是使用标准的注释哈
 | 
				
			||||||
    @TableId(type = IdType.AUTO)
 | 
					    @TableId(type = IdType.AUTO)
 | 
				
			||||||
    @Schema(description = "编号")
 | 
					    @Schema(description = "编号")
 | 
				
			||||||
    private Long id;
 | 
					    private Long id;
 | 
				
			||||||
@ -40,6 +42,7 @@ public class AiMusicDO extends BaseDO {
 | 
				
			|||||||
    @Schema(description = "视频地址")
 | 
					    @Schema(description = "视频地址")
 | 
				
			||||||
    private String videoUrl;
 | 
					    private String videoUrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO @xin:需要关联下对应的枚举
 | 
				
			||||||
    @Schema(description = "音乐状态")
 | 
					    @Schema(description = "音乐状态")
 | 
				
			||||||
    private String status;
 | 
					    private String status;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -49,19 +52,24 @@ public class AiMusicDO extends BaseDO {
 | 
				
			|||||||
    @Schema(description = "提示词")
 | 
					    @Schema(description = "提示词")
 | 
				
			||||||
    private String prompt;
 | 
					    private String prompt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO @xin:生成模式,需要记录下;歌词、描述
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO @xin:多存储一个平台,platform;考虑未来可能有别的音乐接口
 | 
				
			||||||
    @Schema(description = "模型")
 | 
					    @Schema(description = "模型")
 | 
				
			||||||
    private String model;
 | 
					    private String model;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Schema(description = "错误信息")
 | 
					    @Schema(description = "错误信息")
 | 
				
			||||||
    private String errorMessage;
 | 
					    private String errorMessage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO @xin:tags 要不要使用 List<String>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Schema(description = "音乐风格标签")
 | 
					    @Schema(description = "音乐风格标签")
 | 
				
			||||||
    private String tags;
 | 
					    private String tags;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Schema(description = "任务id")
 | 
					    @Schema(description = "任务编号")
 | 
				
			||||||
    private String taskId;
 | 
					    private String taskId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO @xin:转换不放在 DO 里面哈。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static AiMusicDO convertFrom(SunoApi.MusicData musicData) {
 | 
					    public static AiMusicDO convertFrom(SunoApi.MusicData musicData) {
 | 
				
			||||||
        return new AiMusicDO()
 | 
					        return new AiMusicDO()
 | 
				
			||||||
@ -84,5 +92,4 @@ public class AiMusicDO extends BaseDO {
 | 
				
			|||||||
                .collect(Collectors.toList());
 | 
					                .collect(Collectors.toList());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -5,10 +5,9 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO;
 | 
				
			|||||||
import org.apache.ibatis.annotations.Mapper;
 | 
					import org.apache.ibatis.annotations.Mapper;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @Author xiaoxin
 | 
					 * AI 音乐 Mapper
 | 
				
			||||||
 * @Date 2024/6/5
 | 
					 * @author  xiaoxin
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@Mapper
 | 
					@Mapper
 | 
				
			||||||
public interface AiMusicMapper extends BaseMapperX<AiMusicDO> {
 | 
					public interface AiMusicMapper extends BaseMapperX<AiMusicDO> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -31,26 +31,29 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti
 | 
				
			|||||||
@Slf4j
 | 
					@Slf4j
 | 
				
			||||||
public class MusicServiceImpl implements MusicService {
 | 
					public class MusicServiceImpl implements MusicService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO @xin:使用 @Resource 注入,整个项目保持统一哈;
 | 
				
			||||||
    private final SunoApi sunoApi;
 | 
					    private final SunoApi sunoApi;
 | 
				
			||||||
    private final AiMusicMapper musicMapper;
 | 
					    private final AiMusicMapper musicMapper;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final Queue<String> taskQueue = new ConcurrentLinkedQueue<>();
 | 
					    private final Queue<String> taskQueue = new ConcurrentLinkedQueue<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO @xin:要不把 descriptionMode、lyricMode 合并,同一个 generateMusic 方法,然后根据传入的 mode 模式:歌词、描述来区分?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public List<Long> descriptionMode(SunoReqVO reqVO) {
 | 
					    public List<Long> descriptionMode(SunoReqVO reqVO) {
 | 
				
			||||||
        SunoApi.SunoReq sunoReq = new SunoApi.SunoReq(reqVO.getPrompt(), reqVO.getMv(), reqVO.isMakeInstrumental());
 | 
					        // 1. 异步生成
 | 
				
			||||||
        //默认异步
 | 
					        SunoApi.SunoRequest sunoReq = new SunoApi.SunoRequest(reqVO.getPrompt(), reqVO.getMv(), reqVO.isMakeInstrumental());
 | 
				
			||||||
        List<SunoApi.MusicData> musicDataList = sunoApi.generate(sunoReq);
 | 
					        List<SunoApi.MusicData> musicDataList = sunoApi.generate(sunoReq);
 | 
				
			||||||
 | 
					        // 2. 插入数据库
 | 
				
			||||||
        return insertMusicData(musicDataList);
 | 
					        return insertMusicData(musicDataList);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public List<Long> lyricMode(SunoLyricModeVO reqVO) {
 | 
					    public List<Long> lyricMode(SunoLyricModeVO reqVO) {
 | 
				
			||||||
        SunoApi.SunoReq sunoReq = new SunoApi.SunoReq(reqVO.getPrompt(), reqVO.getMv(), reqVO.getTags(), reqVO.getTitle());
 | 
					        // 1. 异步生成
 | 
				
			||||||
        //默认异步
 | 
					        SunoApi.SunoRequest sunoReq = new SunoApi.SunoRequest(reqVO.getPrompt(), reqVO.getMv(), reqVO.getTags(), reqVO.getTitle());
 | 
				
			||||||
        List<SunoApi.MusicData> musicDataList = sunoApi.customGenerate(sunoReq);
 | 
					        List<SunoApi.MusicData> musicDataList = sunoApi.customGenerate(sunoReq);
 | 
				
			||||||
 | 
					        // 2. 插入数据库
 | 
				
			||||||
        return insertMusicData(musicDataList);
 | 
					        return insertMusicData(musicDataList);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -64,6 +67,7 @@ public class MusicServiceImpl implements MusicService {
 | 
				
			|||||||
        if (CollUtil.isEmpty(musicDataList)) {
 | 
					        if (CollUtil.isEmpty(musicDataList)) {
 | 
				
			||||||
            return Collections.emptyList();
 | 
					            return Collections.emptyList();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        // TODO @xin:建议使用 insertBatch 方法,批量插入
 | 
				
			||||||
        return AiMusicDO.convertFrom(musicDataList).stream()
 | 
					        return AiMusicDO.convertFrom(musicDataList).stream()
 | 
				
			||||||
                .peek(musicDO -> musicMapper.insert(musicDO.setUserId(getLoginUserId())))
 | 
					                .peek(musicDO -> musicMapper.insert(musicDO.setUserId(getLoginUserId())))
 | 
				
			||||||
                .peek(e -> Optional.of(e.getTaskId()).ifPresent(taskQueue::add))
 | 
					                .peek(e -> Optional.of(e.getTaskId()).ifPresent(taskQueue::add))
 | 
				
			||||||
@ -71,6 +75,7 @@ public class MusicServiceImpl implements MusicService {
 | 
				
			|||||||
                .collect(Collectors.toList());
 | 
					                .collect(Collectors.toList());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO @xin:这个,改成标准的 job 来实现哈。从数据库加载任务,然后执行。
 | 
				
			||||||
    @Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)
 | 
					    @Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)
 | 
				
			||||||
    @Transactional
 | 
					    @Transactional
 | 
				
			||||||
    public void flushSunoTask() {
 | 
					    public void flushSunoTask() {
 | 
				
			||||||
 | 
				
			|||||||
@ -118,8 +118,9 @@ public class YudaoAiProperties {
 | 
				
			|||||||
    public static class SunoProperties {
 | 
					    public static class SunoProperties {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private boolean enable = false;
 | 
					        private boolean enable = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * suno-api 服务的基本地址
 | 
					         * API 服务的基本地址
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        private String baseUrl;
 | 
					        private String baseUrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -4,16 +4,20 @@ import lombok.AllArgsConstructor;
 | 
				
			|||||||
import lombok.Data;
 | 
					import lombok.Data;
 | 
				
			||||||
import lombok.NoArgsConstructor;
 | 
					import lombok.NoArgsConstructor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO @xin:不需要这个类哈,直接 SunoApi 传入 baseUrl 参数即可
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @Author xiaoxin
 | 
					 * Suno 配置类
 | 
				
			||||||
 * @Date 2024/5/29
 | 
					 *
 | 
				
			||||||
 | 
					 * @author  xiaoxin
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@Data
 | 
					@Data
 | 
				
			||||||
@NoArgsConstructor
 | 
					@NoArgsConstructor
 | 
				
			||||||
@AllArgsConstructor
 | 
					@AllArgsConstructor
 | 
				
			||||||
public class SunoConfig {
 | 
					public class SunoConfig {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * suno-api服务的基本路径
 | 
					     * suno-api服务的基本路径
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private String baseUrl;
 | 
					    private String baseUrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -27,14 +27,15 @@ import java.util.function.Predicate;
 | 
				
			|||||||
public class SunoApi {
 | 
					public class SunoApi {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final WebClient webClient;
 | 
					    private final WebClient webClient;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final Predicate<HttpStatusCode> STATUS_PREDICATE = status -> !status.is2xxSuccessful();
 | 
					    private final Predicate<HttpStatusCode> STATUS_PREDICATE = status -> !status.is2xxSuccessful();
 | 
				
			||||||
    private final Function<ClientResponse, Mono<? extends Throwable>> EXCEPTION_FUNCTION = response -> response.bodyToMono(String.class)
 | 
					    private final Function<ClientResponse, Mono<? extends Throwable>> EXCEPTION_FUNCTION = response -> response.bodyToMono(String.class)
 | 
				
			||||||
            .handle((respBody, sink) -> {
 | 
					            .handle((respBody, sink) -> {
 | 
				
			||||||
 | 
					                // TODO @xin:最好是 request、response 都有哈
 | 
				
			||||||
                log.error("【suno-api】调用失败!resp: 【{}】", respBody);
 | 
					                log.error("【suno-api】调用失败!resp: 【{}】", respBody);
 | 
				
			||||||
                sink.error(new IllegalStateException("【suno-api】调用失败!"));
 | 
					                sink.error(new IllegalStateException("【suno-api】调用失败!"));
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public SunoApi(SunoConfig config) {
 | 
					    public SunoApi(SunoConfig config) {
 | 
				
			||||||
        this.webClient = WebClient.builder()
 | 
					        this.webClient = WebClient.builder()
 | 
				
			||||||
                .baseUrl(config.getBaseUrl())
 | 
					                .baseUrl(config.getBaseUrl())
 | 
				
			||||||
@ -42,50 +43,49 @@ public class SunoApi {
 | 
				
			|||||||
                .build();
 | 
					                .build();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public List<MusicData> generate(SunoApi.SunoReq sunReq) {
 | 
					    public List<MusicData> generate(SunoRequest request) {
 | 
				
			||||||
        return this.webClient.post()
 | 
					        return this.webClient.post()
 | 
				
			||||||
                .uri("/api/generate")
 | 
					                .uri("/api/generate")
 | 
				
			||||||
                .body(Mono.just(sunReq), SunoApi.SunoReq.class)
 | 
					                .body(Mono.just(request), SunoRequest.class)
 | 
				
			||||||
                .retrieve()
 | 
					                .retrieve()
 | 
				
			||||||
                .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION)
 | 
					                .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION)
 | 
				
			||||||
                .bodyToMono(new ParameterizedTypeReference<List<MusicData>>() {
 | 
					                .bodyToMono(new ParameterizedTypeReference<List<MusicData>>() { })
 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                .block();
 | 
					                .block();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public List<MusicData> customGenerate(SunoApi.SunoReq sunReq) {
 | 
					    public List<MusicData> customGenerate(SunoRequest request) {
 | 
				
			||||||
        return this.webClient.post()
 | 
					        return this.webClient.post()
 | 
				
			||||||
                .uri("/api/custom_generate")
 | 
					                .uri("/api/custom_generate")
 | 
				
			||||||
                .body(Mono.just(sunReq), SunoApi.SunoReq.class)
 | 
					                .body(Mono.just(request), SunoRequest.class)
 | 
				
			||||||
                .retrieve()
 | 
					                .retrieve()
 | 
				
			||||||
                .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION)
 | 
					                .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION)
 | 
				
			||||||
                .bodyToMono(new ParameterizedTypeReference<List<MusicData>>() {
 | 
					                .bodyToMono(new ParameterizedTypeReference<List<MusicData>>() { })
 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                .block();
 | 
					                .block();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO @xin: 是不是叫 chatCompletion
 | 
				
			||||||
    public List<MusicData> doChatCompletion(String prompt) {
 | 
					    public List<MusicData> doChatCompletion(String prompt) {
 | 
				
			||||||
        return this.webClient.post()
 | 
					        return this.webClient.post()
 | 
				
			||||||
                .uri("/v1/chat/completions")
 | 
					                .uri("/v1/chat/completions")
 | 
				
			||||||
                .body(Mono.just(new SunoReq(prompt)), SunoApi.SunoReq.class)
 | 
					                .body(Mono.just(new SunoRequest(prompt)), SunoRequest.class)
 | 
				
			||||||
                .retrieve()
 | 
					                .retrieve()
 | 
				
			||||||
                .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION)
 | 
					                .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION)
 | 
				
			||||||
                .bodyToMono(new ParameterizedTypeReference<List<MusicData>>() {
 | 
					                .bodyToMono(new ParameterizedTypeReference<List<MusicData>>() { })
 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                .block();
 | 
					                .block();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public LyricsData generateLyrics(String prompt) {
 | 
					    public LyricsData generateLyrics(String prompt) {
 | 
				
			||||||
        return this.webClient.post()
 | 
					        return this.webClient.post()
 | 
				
			||||||
                .uri("/api/generate_lyrics")
 | 
					                .uri("/api/generate_lyrics")
 | 
				
			||||||
                .body(Mono.just(new SunoReq(prompt)), SunoApi.SunoReq.class)
 | 
					                .body(Mono.just(new SunoRequest(prompt)), SunoRequest.class)
 | 
				
			||||||
                .retrieve()
 | 
					                .retrieve()
 | 
				
			||||||
                .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION)
 | 
					                .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION)
 | 
				
			||||||
                .bodyToMono(LyricsData.class)
 | 
					                .bodyToMono(LyricsData.class)
 | 
				
			||||||
                .block();
 | 
					                .block();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO @xin:应该传入 List<String> ids
 | 
				
			||||||
 | 
					    // TODO @xin:方法名,建议使用 getMusicList
 | 
				
			||||||
    public List<MusicData> selectById(String ids) {
 | 
					    public List<MusicData> selectById(String ids) {
 | 
				
			||||||
        return this.webClient.get()
 | 
					        return this.webClient.get()
 | 
				
			||||||
                .uri(uriBuilder -> uriBuilder
 | 
					                .uri(uriBuilder -> uriBuilder
 | 
				
			||||||
@ -94,12 +94,11 @@ public class SunoApi {
 | 
				
			|||||||
                        .build())
 | 
					                        .build())
 | 
				
			||||||
                .retrieve()
 | 
					                .retrieve()
 | 
				
			||||||
                .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION)
 | 
					                .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION)
 | 
				
			||||||
                .bodyToMono(new ParameterizedTypeReference<List<MusicData>>() {
 | 
					                .bodyToMono(new ParameterizedTypeReference<List<MusicData>>() { })
 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                .block();
 | 
					                .block();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO @xin:方法名,建议使用 getLimitUsage
 | 
				
			||||||
    public LimitData selectLimit() {
 | 
					    public LimitData selectLimit() {
 | 
				
			||||||
        return this.webClient.get()
 | 
					        return this.webClient.get()
 | 
				
			||||||
                .uri("/api/get_limit")
 | 
					                .uri("/api/get_limit")
 | 
				
			||||||
@ -109,7 +108,7 @@ public class SunoApi {
 | 
				
			|||||||
                .block();
 | 
					                .block();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO @xin:可以改成 MusicGenerateRequest
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 根据提示生成音频
 | 
					     * 根据提示生成音频
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@ -117,12 +116,12 @@ public class SunoApi {
 | 
				
			|||||||
     * @param tags             音乐风格
 | 
					     * @param tags             音乐风格
 | 
				
			||||||
     * @param title            音乐名称
 | 
					     * @param title            音乐名称
 | 
				
			||||||
     * @param mv               模型
 | 
					     * @param mv               模型
 | 
				
			||||||
     * @param waitAudio        false表示后台模式,仅返回音频任务信息,需要调用get API获取详细的音频信息。
 | 
					     * @param waitAudio        false 表示后台模式,仅返回音频任务信息,需要调用 get API 获取详细的音频信息。
 | 
				
			||||||
     *                         true表示同步模式,API最多等待100s,音频生成完毕后直接返回音频链接等信息,建议在GPT等agent中使用。
 | 
					     *                         true 表示同步模式,API 最多等待 100s,音频生成完毕后直接返回音频链接等信息,建议在 GPT 等 agent 中使用。
 | 
				
			||||||
     * @param makeInstrumental 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成
 | 
					     * @param makeInstrumental 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    @JsonInclude(value = JsonInclude.Include.NON_NULL)
 | 
					    @JsonInclude(value = JsonInclude.Include.NON_NULL)
 | 
				
			||||||
    public record SunoReq(
 | 
					    public record SunoRequest(
 | 
				
			||||||
            String prompt,
 | 
					            String prompt,
 | 
				
			||||||
            String tags,
 | 
					            String tags,
 | 
				
			||||||
            String title,
 | 
					            String title,
 | 
				
			||||||
@ -130,23 +129,23 @@ public class SunoApi {
 | 
				
			|||||||
            @JsonProperty("wait_audio") boolean waitAudio,
 | 
					            @JsonProperty("wait_audio") boolean waitAudio,
 | 
				
			||||||
            @JsonProperty("make_instrumental") boolean makeInstrumental
 | 
					            @JsonProperty("make_instrumental") boolean makeInstrumental
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        public SunoReq(String prompt) {
 | 
					
 | 
				
			||||||
 | 
					        public SunoRequest(String prompt) {
 | 
				
			||||||
            this(prompt, null, null, null, false, false);
 | 
					            this(prompt, null, null, null, false, false);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public SunoReq(String prompt, String mv, boolean makeInstrumental) {
 | 
					        public SunoRequest(String prompt, String mv, boolean makeInstrumental) {
 | 
				
			||||||
            this(prompt, null, null, mv, false, makeInstrumental);
 | 
					            this(prompt, null, null, mv, false, makeInstrumental);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public SunoRequest(String prompt, String mv, String tags, String title) {
 | 
				
			||||||
        public SunoReq(String prompt, String mv, String tags, String title) {
 | 
					 | 
				
			||||||
            this(prompt, tags, title, mv, false, false);
 | 
					            this(prompt, tags, title, mv, false, false);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * SunoAPI 响应的音频数据。
 | 
					     * Suno API 响应的音频数据
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param id                   音乐数据的 ID
 | 
					     * @param id                   音乐数据的 ID
 | 
				
			||||||
     * @param title                音乐音频的标题
 | 
					     * @param title                音乐音频的标题
 | 
				
			||||||
@ -179,9 +178,8 @@ public class SunoApi {
 | 
				
			|||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * SunoAPI 响应的歌词数据。
 | 
					     * Suno API 响应的歌词数据。
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param text   歌词
 | 
					     * @param text   歌词
 | 
				
			||||||
     * @param title  标题
 | 
					     * @param title  标题
 | 
				
			||||||
@ -194,9 +192,8 @@ public class SunoApi {
 | 
				
			|||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * SunoAPI 响应的限额数据,目前每日免费50
 | 
					     * Suno API 响应的限额数据,目前每日免费50
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public record LimitData(
 | 
					    public record LimitData(
 | 
				
			||||||
            @JsonProperty("credits_left") Long creditsLeft,
 | 
					            @JsonProperty("credits_left") Long creditsLeft,
 | 
				
			||||||
@ -206,5 +203,4 @@ public class SunoApi {
 | 
				
			|||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -29,7 +29,7 @@ public class SunoTests {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    public void generate() {
 | 
					    public void generate() {
 | 
				
			||||||
        List<SunoApi.MusicData> generate = sunoApi.generate(new SunoApi.SunoReq("创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。"));
 | 
					        List<SunoApi.MusicData> generate = sunoApi.generate(new SunoApi.SunoRequest("创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。"));
 | 
				
			||||||
        System.out.println(generate);
 | 
					        System.out.println(generate);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user