Commit a858eb84e5ade81a7b142e1c68fef153094134a5

Authored by xiaoxin
1 parent 0faba44a

【解决todo】Suno调用改为WebClient

yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicServiceImpl.java
@@ -19,7 +19,7 @@ public class MusicServiceImpl implements MusicService { @@ -19,7 +19,7 @@ public class MusicServiceImpl implements MusicService {
19 19
20 @Override 20 @Override
21 public SunoRespVO musicGen(SunoReqVO sunoReqVO) { 21 public SunoRespVO musicGen(SunoReqVO sunoReqVO) {
22 - SunoApi.SunoRequest req = BeanUtils.toBean(sunoReqVO, SunoApi.SunoRequest.class); 22 + SunoApi.SunoReq req = BeanUtils.toBean(sunoReqVO, SunoApi.SunoReq.class);
23 return BeanUtils.toBean(sunoApi.musicGen(req), SunoRespVO.class); 23 return BeanUtils.toBean(sunoApi.musicGen(req), SunoRespVO.class);
24 } 24 }
25 } 25 }
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java
@@ -137,7 +137,6 @@ public class YudaoAiProperties { @@ -137,7 +137,6 @@ public class YudaoAiProperties {
137 } 137 }
138 138
139 @Data 139 @Data
140 - @Accessors(chain = true) // TODO @xiaoxin:可以去掉这个,默认全局已经开启  
141 public static class SunoProperties { 140 public static class SunoProperties {
142 141
143 private boolean enable = false; 142 private boolean enable = false;
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/SunoConfig.java
@@ -3,14 +3,12 @@ package cn.iocoder.yudao.framework.ai.core.model.suno; @@ -3,14 +3,12 @@ package cn.iocoder.yudao.framework.ai.core.model.suno;
3 import lombok.AllArgsConstructor; 3 import lombok.AllArgsConstructor;
4 import lombok.Data; 4 import lombok.Data;
5 import lombok.NoArgsConstructor; 5 import lombok.NoArgsConstructor;
6 -import lombok.experimental.Accessors;  
7 6
8 /** 7 /**
9 * @Author xiaoxin 8 * @Author xiaoxin
10 * @Date 2024/5/29 9 * @Date 2024/5/29
11 */ 10 */
12 @Data 11 @Data
13 -@Accessors(chain = true)  
14 @NoArgsConstructor 12 @NoArgsConstructor
15 @AllArgsConstructor 13 @AllArgsConstructor
16 public class SunoConfig { 14 public class SunoConfig {
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java
1 package cn.iocoder.yudao.framework.ai.core.model.suno.api; 1 package cn.iocoder.yudao.framework.ai.core.model.suno.api;
2 2
3 import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig; 3 import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig;
4 -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;  
5 import com.fasterxml.jackson.annotation.JsonInclude; 4 import com.fasterxml.jackson.annotation.JsonInclude;
6 import com.fasterxml.jackson.annotation.JsonProperty; 5 import com.fasterxml.jackson.annotation.JsonProperty;
7 import lombok.Data; 6 import lombok.Data;
8 -import lombok.experimental.Accessors;  
9 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
10 -import okhttp3.*; 8 +import org.springframework.ai.openai.api.ApiUtils;
  9 +import org.springframework.web.reactive.function.client.WebClient;
  10 +import reactor.core.publisher.Mono;
11 11
12 -import java.io.IOException;  
13 import java.util.List; 12 import java.util.List;
14 -import java.util.concurrent.TimeUnit;  
15 13
16 -// TODO @xiaoxin:类注释  
17 /** 14 /**
  15 + * Suno API
  16 + * <br>
  17 + * 文档地址:https://platform.acedata.cloud/documents/d016ee3f-421b-4b6e-989a-8beba8701701
  18 + *
18 * @Author xiaoxin 19 * @Author xiaoxin
19 * @Date 2024/5/27 20 * @Date 2024/5/27
20 */ 21 */
21 @Slf4j 22 @Slf4j
22 public class SunoApi { 23 public class SunoApi {
23 24
24 - // TODO @xiaoxin:APPLICATION_JSON、TOKEN_PREFIX 看看 spring 有没自带的这 2 个枚举哈。变量越少越好  
25 - public static final String APPLICATION_JSON = "application/json";  
26 - public static final String TOKEN_PREFIX = "Bearer ";  
27 - public static final String API_URL = "https://api.acedata.cloud/suno/audios";  
28 -  
29 - private static final int READ_TIMEOUT = 160; // 连接超时时间(秒),音乐生成时间较长,设置为 160s,后续可做callback  
30 -  
31 - // TODO @xiaoxin:建议使用 webClient 对接。参考 https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java  
32 - private final OkHttpClient client;  
33 -  
34 - // TODO @xiaoxin:sunoConfig => config,简洁一点  
35 - public SunoApi(SunoConfig sunoConfig) {  
36 - this.client = new OkHttpClient().newBuilder().readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)  
37 - .addInterceptor(chain -> {  
38 - Request originalRequest = chain.request();  
39 - Request requestWithUserAgent = originalRequest.newBuilder()  
40 - .header("Authorization", TOKEN_PREFIX + sunoConfig.getToken())  
41 - .build();  
42 - return chain.proceed(requestWithUserAgent);  
43 - }) 25 + public static final String DEFAULT_BASE_URL = "https://api.acedata.cloud/suno";
  26 + private final WebClient webClient;
  27 +
  28 + public SunoApi(SunoConfig config) {
  29 + this.webClient = WebClient.builder()
  30 + .baseUrl(DEFAULT_BASE_URL)
  31 + .defaultHeaders(ApiUtils.getJsonContentHeaders(config.getToken()))
44 .build(); 32 .build();
45 } 33 }
46 34
47 // TODO @芋艿:方法名,要考虑下; 35 // TODO @芋艿:方法名,要考虑下;
48 - public SunoResponse musicGen(SunoRequest sunoRequest) {  
49 - Request request = new Request.Builder()  
50 - .url(API_URL)  
51 - .post(RequestBody.create(MediaType.parse(APPLICATION_JSON), JsonUtils.toJsonString(sunoRequest)))  
52 - .build();  
53 -  
54 - try (Response response = client.newCall(request).execute()) {  
55 - if (!response.isSuccessful()) {  
56 - log.error("suno调用失败! response: {}", response);  
57 - throw new IllegalStateException("suno调用失败!" + response);  
58 - }  
59 - return JsonUtils.parseObject(response.body().string(), SunoResponse.class);  
60 - } catch (IOException ioException) {  
61 - throw new RuntimeException(ioException);  
62 - } 36 + public SunoResp musicGen(SunoReq sunReq) {
  37 + return this.webClient.post()
  38 + .uri("/audios")
  39 + .body(Mono.just(sunReq), SunoReq.class)
  40 + .retrieve()
  41 + .onStatus(status -> !status.is2xxSuccessful(),
  42 + response -> response.bodyToMono(String.class)
  43 + .handle((respBody, sink) -> {
  44 + log.error("【Suno】调用失败!resp: 【{}】", respBody);
  45 + sink.error(new IllegalStateException("【Suno】调用失败!"));
  46 + }))
  47 + .bodyToMono(SunoResp.class)
  48 + .block();
63 } 49 }
64 50
65 // TODO @xiaoxin:看看是不是使用 record 特性,简化下; 51 // TODO @xiaoxin:看看是不是使用 record 特性,简化下;
@@ -68,9 +54,8 @@ public class SunoApi { @@ -68,9 +54,8 @@ public class SunoApi {
68 * 请求数据对象,用于生成音乐音频 54 * 请求数据对象,用于生成音乐音频
69 */ 55 */
70 @Data 56 @Data
71 - @Accessors(chain = true)  
72 @JsonInclude(value = JsonInclude.Include.NON_NULL) 57 @JsonInclude(value = JsonInclude.Include.NON_NULL)
73 - public static class SunoRequest { 58 + public static class SunoReq {
74 /** 59 /**
75 * 用于生成音乐音频的提示 60 * 用于生成音乐音频的提示
76 */ 61 */
@@ -108,7 +93,7 @@ public class SunoApi { @@ -108,7 +93,7 @@ public class SunoApi {
108 * SunoAPI 响应的数据 93 * SunoAPI 响应的数据
109 */ 94 */
110 @Data 95 @Data
111 - public static class SunoResponse { 96 + public static class SunoResp {
112 /** 97 /**
113 * 表示请求是否成功 98 * 表示请求是否成功
114 */ 99 */
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/suno/SunoTests.java
@@ -15,18 +15,17 @@ public class SunoTests { @@ -15,18 +15,17 @@ public class SunoTests {
15 15
16 @Before 16 @Before
17 public void setup() { 17 public void setup() {
18 - String token = "13f13540dd3f4ae9885f63ac9f5d0b9f"; 18 + String token = "16b4356581984d538652354b60d69ff0";
19 this.sunoConfig = new SunoConfig(token); 19 this.sunoConfig = new SunoConfig(token);
20 } 20 }
21 21
22 @Test 22 @Test
23 public void generateMusic() { 23 public void generateMusic() {
24 SunoApi sunoApi = new SunoApi(sunoConfig); 24 SunoApi sunoApi = new SunoApi(sunoConfig);
25 - SunoApi.SunoRequest sunoRequest = new SunoApi  
26 - .SunoRequest() 25 + SunoApi.SunoReq sunoReq = new SunoApi.SunoReq()
27 .setPrompt("创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。"); 26 .setPrompt("创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。");
28 - SunoApi.SunoResponse sunoResponse = sunoApi.musicGen(sunoRequest);  
29 - System.out.println(sunoResponse); 27 + SunoApi.SunoResp sunoResp = sunoApi.musicGen(sunoReq);
  28 + System.out.println(sunoResp);
30 } 29 }
31 30
32 } 31 }