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 19  
20 20 @Override
21 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 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 137 }
138 138  
139 139 @Data
140   - @Accessors(chain = true) // TODO @xiaoxin:可以去掉这个,默认全局已经开启
141 140 public static class SunoProperties {
142 141  
143 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 3 import lombok.AllArgsConstructor;
4 4 import lombok.Data;
5 5 import lombok.NoArgsConstructor;
6   -import lombok.experimental.Accessors;
7 6  
8 7 /**
9 8 * @Author xiaoxin
10 9 * @Date 2024/5/29
11 10 */
12 11 @Data
13   -@Accessors(chain = true)
14 12 @NoArgsConstructor
15 13 @AllArgsConstructor
16 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 1 package cn.iocoder.yudao.framework.ai.core.model.suno.api;
2 2  
3 3 import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig;
4   -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
5 4 import com.fasterxml.jackson.annotation.JsonInclude;
6 5 import com.fasterxml.jackson.annotation.JsonProperty;
7 6 import lombok.Data;
8   -import lombok.experimental.Accessors;
9 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 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 19 * @Author xiaoxin
19 20 * @Date 2024/5/27
20 21 */
21 22 @Slf4j
22 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 32 .build();
45 33 }
46 34  
47 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 51 // TODO @xiaoxin:看看是不是使用 record 特性,简化下;
... ... @@ -68,9 +54,8 @@ public class SunoApi {
68 54 * 请求数据对象,用于生成音乐音频
69 55 */
70 56 @Data
71   - @Accessors(chain = true)
72 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 93 * SunoAPI 响应的数据
109 94 */
110 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 15  
16 16 @Before
17 17 public void setup() {
18   - String token = "13f13540dd3f4ae9885f63ac9f5d0b9f";
  18 + String token = "16b4356581984d538652354b60d69ff0";
19 19 this.sunoConfig = new SunoConfig(token);
20 20 }
21 21  
22 22 @Test
23 23 public void generateMusic() {
24 24 SunoApi sunoApi = new SunoApi(sunoConfig);
25   - SunoApi.SunoRequest sunoRequest = new SunoApi
26   - .SunoRequest()
  25 + SunoApi.SunoReq sunoReq = new SunoApi.SunoReq()
27 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 }
... ...