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