Commit 949d5a18151f15d258dc3ef69dacbc7f23fc8585

Authored by YunaiV
1 parent 2e9915b7

【新增】AI:音乐接入 API KEY 管理

Showing 13 changed files with 108 additions and 48 deletions
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.http
@@ -5,9 +5,9 @@ Authorization: {{token}} @@ -5,9 +5,9 @@ Authorization: {{token}}
5 5
6 { 6 {
7 "platform": "Suno", 7 "platform": "Suno",
8 - "generateMode": 1,  
9 - "prompt": "来一首快乐的歌曲",  
10 - "modelVersion": "chirp-v3.5", 8 + "generateMode": 2,
  9 + "prompt": "周末啦!",
  10 + "model": "chirp-v3.5",
11 "tags": ["Happy"], 11 "tags": ["Happy"],
12 "title": "Happy Song" 12 "title": "Happy Song"
13 } 13 }
@@ -19,8 +19,8 @@ Authorization: {{token}} @@ -19,8 +19,8 @@ Authorization: {{token}}
19 19
20 { 20 {
21 "platform": "Suno", 21 "platform": "Suno",
22 - "generateMode": 2,  
23 - "prompt": "来一首快乐的歌曲",  
24 - "makeInstrumental": false,  
25 - "title": "Happy Song" 22 + "generateMode": 1,
  23 + "model": "chirp-v3.5",
  24 + "gptDescriptionPrompt": "今天是星球六,结果是个下雨天,希望心情很美丽",
  25 + "makeInstrumental": false
26 } 26 }
27 \ No newline at end of file 27 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java
@@ -35,6 +35,16 @@ public class AiMusicController { @@ -35,6 +35,16 @@ public class AiMusicController {
35 return success(BeanUtils.toBean(pageResult, AiMusicRespVO.class)); 35 return success(BeanUtils.toBean(pageResult, AiMusicRespVO.class));
36 } 36 }
37 37
  38 + @PostMapping("/generate")
  39 + @Operation(summary = "音乐生成")
  40 + public CommonResult<List<Long>> generateMusic(@RequestBody @Valid AiSunoGenerateReqVO reqVO) {
  41 + if (true) {
  42 + musicService.syncMusic();
  43 + return null;
  44 + }
  45 + return success(musicService.generateMusic(getLoginUserId(), reqVO));
  46 + }
  47 +
38 @Operation(summary = "删除【我的】音乐记录") 48 @Operation(summary = "删除【我的】音乐记录")
39 @DeleteMapping("/delete-my") 49 @DeleteMapping("/delete-my")
40 @Parameter(name = "id", required = true, description = "音乐编号", example = "1024") 50 @Parameter(name = "id", required = true, description = "音乐编号", example = "1024")
@@ -54,6 +64,7 @@ public class AiMusicController { @@ -54,6 +64,7 @@ public class AiMusicController {
54 return success(BeanUtils.toBean(music, AiMusicRespVO.class)); 64 return success(BeanUtils.toBean(music, AiMusicRespVO.class));
55 } 65 }
56 66
  67 + // TODO @xin:这个搞成 updateMy ,修改【我的】音乐。方便后续支持其它字段;另外,需要校验下,更新的音乐,是不是我的!
57 @PostMapping("/updateTitle-my") 68 @PostMapping("/updateTitle-my")
58 @Operation(summary = "修改【我的】音乐 目前只支持修改标题") 69 @Operation(summary = "修改【我的】音乐 目前只支持修改标题")
59 @Parameter(name = "title", required = true, description = "音乐名称", example = "夜空中最亮的星") 70 @Parameter(name = "title", required = true, description = "音乐名称", example = "夜空中最亮的星")
@@ -62,12 +73,6 @@ public class AiMusicController { @@ -62,12 +73,6 @@ public class AiMusicController {
62 return success(true); 73 return success(true);
63 } 74 }
64 75
65 - @PostMapping("/generate")  
66 - @Operation(summary = "音乐生成")  
67 - public CommonResult<List<Long>> generateMusic(@RequestBody @Valid AiSunoGenerateReqVO reqVO) {  
68 - return success(musicService.generateMusic(getLoginUserId(), reqVO));  
69 - }  
70 -  
71 // ================ 音乐管理 ================ 76 // ================ 音乐管理 ================
72 77
73 @GetMapping("/page") 78 @GetMapping("/page")
@@ -87,11 +92,11 @@ public class AiMusicController { @@ -87,11 +92,11 @@ public class AiMusicController {
87 return success(true); 92 return success(true);
88 } 93 }
89 94
90 - @PutMapping("/update-public-status")  
91 - @Operation(summary = "更新音乐发布状态") 95 + @PutMapping("/update")
  96 + @Operation(summary = "更新音乐")
92 @PreAuthorize("@ss.hasPermission('ai:music:update')") 97 @PreAuthorize("@ss.hasPermission('ai:music:update')")
93 - public CommonResult<Boolean> updateMusicPublicStatus(@Valid @RequestBody AiMusicUpdatePublicStatusReqVO updateReqVO) {  
94 - musicService.updateMusicPublicStatus(updateReqVO); 98 + public CommonResult<Boolean> updateMusic(@Valid @RequestBody AiMusicUpdateReqVO updateReqVO) {
  99 + musicService.updateMusic(updateReqVO);
95 return success(true); 100 return success(true);
96 } 101 }
97 102
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicRespVO.java
@@ -52,6 +52,9 @@ public class AiMusicRespVO { @@ -52,6 +52,9 @@ public class AiMusicRespVO {
52 @Schema(description = "音乐风格标签") 52 @Schema(description = "音乐风格标签")
53 private List<String> tags; 53 private List<String> tags;
54 54
  55 + @Schema(description = "音乐时长", example = "[\"pop\",\"jazz\",\"punk\"]")
  56 + private Double duration;
  57 +
55 @Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") 58 @Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
56 private Boolean publicStatus; 59 private Boolean publicStatus;
57 60
@@ -61,9 +64,6 @@ public class AiMusicRespVO { @@ -61,9 +64,6 @@ public class AiMusicRespVO {
61 @Schema(description = "错误信息") 64 @Schema(description = "错误信息")
62 private String errorMessage; 65 private String errorMessage;
63 66
64 - @Schema(description = "音乐时长")  
65 - private Double duration;  
66 -  
67 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) 67 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
68 private LocalDateTime createTime; 68 private LocalDateTime createTime;
69 69
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdatePublicStatusReqVO.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdateReqVO.java
@@ -4,15 +4,15 @@ import io.swagger.v3.oas.annotations.media.Schema; @@ -4,15 +4,15 @@ import io.swagger.v3.oas.annotations.media.Schema;
4 import jakarta.validation.constraints.NotNull; 4 import jakarta.validation.constraints.NotNull;
5 import lombok.Data; 5 import lombok.Data;
6 6
7 -@Schema(description = "管理后台 - AI 音乐修改发布状态 Request VO") 7 +@Schema(description = "管理后台 - AI 音乐修改 Request VO")
8 @Data 8 @Data
9 -public class AiMusicUpdatePublicStatusReqVO { 9 +public class AiMusicUpdateReqVO {
10 10
11 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583") 11 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
  12 + @NotNull(message = "编号不能为空")
12 private Long id; 13 private Long id;
13 14
14 - @Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")  
15 - @NotNull(message = "是否发布不能为空") 15 + @Schema(description = "是否发布", example = "true")
16 private Boolean publicStatus; 16 private Boolean publicStatus;
17 17
18 } 18 }
19 \ No newline at end of file 19 \ 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/AiSunoGenerateReqVO.java
@@ -20,11 +20,13 @@ public class AiSunoGenerateReqVO { @@ -20,11 +20,13 @@ public class AiSunoGenerateReqVO {
20 * 1. 描述模式:描述词 + 是否纯音乐 + 模型 20 * 1. 描述模式:描述词 + 是否纯音乐 + 模型
21 * 2. 歌词模式:歌词 + 音乐风格 + 标题 + 模型 21 * 2. 歌词模式:歌词 + 音乐风格 + 标题 + 模型
22 */ 22 */
23 - @Schema(description = "生成模式 1.描述模式 2. 歌词模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") 23 + @Schema(description = "生成模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
24 @NotNull(message = "生成模式不能为空") 24 @NotNull(message = "生成模式不能为空")
25 private Integer generateMode; // 参见 AiMusicGenerateModeEnum 枚举 25 private Integer generateMode; // 参见 AiMusicGenerateModeEnum 枚举
26 26
27 - @Schema(description = "歌词模式用:用于生成音乐音频的歌词提示", requiredMode = Schema.RequiredMode.NOT_REQUIRED, 27 + // TODO @xin:方案一:prompt => lyric 歌词;gptDescriptionPrompt => description 描述(db 那字段也改下,避免和 gpt 直接耦合);这样搞完后,会更统一好理解一点
  28 + // TODO @xin:方案二:还是之前的做法,都用 prompt;不过最终 gptDescriptionPrompt 还是存储 description 算描述。可以微信一起讨论下。
  29 + @Schema(description = "用于生成音乐音频的歌词提示",
28 example = """ 30 example = """
29 [Verse] 31 [Verse]
30 阳光下奔跑 多么欢快 32 阳光下奔跑 多么欢快
@@ -37,23 +39,23 @@ public class AiSunoGenerateReqVO { @@ -37,23 +39,23 @@ public class AiSunoGenerateReqVO {
37 日子太短暂 别再等待 39 日子太短暂 别再等待
38 马上放假了 梦想起飞 40 马上放假了 梦想起飞
39 """) 41 """)
40 - private String prompt; 42 + private String prompt; // 歌词模式用
41 43
42 - @Schema(description = "描述模式用:用于生成音乐音频的描述", requiredMode = Schema.RequiredMode.NOT_REQUIRED, 44 + @Schema(description = "用于生成音乐音频的描述",
43 example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。") 45 example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。")
44 - private String gptDescriptionPrompt; 46 + private String gptDescriptionPrompt; // 描述模式用
45 47
46 - @Schema(description = "是否纯音乐", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "true") 48 + @Schema(description = "是否纯音乐", example = "true")
47 private Boolean makeInstrumental; 49 private Boolean makeInstrumental;
48 50
49 @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5") 51 @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5")
50 @NotEmpty(message = "模型不能为空") 52 @NotEmpty(message = "模型不能为空")
51 private String model; // 参见 AiModelEnum 枚举 53 private String model; // 参见 AiModelEnum 枚举
52 54
53 - @Schema(description = "音乐风格", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "[\"pop\",\"jazz\",\"punk\"]") 55 + @Schema(description = "音乐风格", example = "[\"pop\",\"jazz\",\"punk\"]")
54 private List<String> tags; 56 private List<String> tags;
55 57
56 - @Schema(description = "音乐/歌曲名称", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "夜空中最亮的星") 58 + @Schema(description = "音乐/歌曲名称", example = "夜空中最亮的星")
57 private String title; 59 private String title;
58 60
59 } 61 }
60 \ No newline at end of file 62 \ 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
@@ -99,6 +99,11 @@ public class AiMusicDO extends BaseDO { @@ -99,6 +99,11 @@ public class AiMusicDO extends BaseDO {
99 private List<String> tags; 99 private List<String> tags;
100 100
101 /** 101 /**
  102 + * 音乐时长
  103 + */
  104 + private Double duration;
  105 +
  106 + /**
102 * 是否公开 107 * 是否公开
103 */ 108 */
104 private Boolean publicStatus; 109 private Boolean publicStatus;
@@ -113,10 +118,4 @@ public class AiMusicDO extends BaseDO { @@ -113,10 +118,4 @@ public class AiMusicDO extends BaseDO {
113 */ 118 */
114 private String errorMessage; 119 private String errorMessage;
115 120
116 -  
117 - /**  
118 - * 音乐时长  
119 - */  
120 - private Double duration;  
121 -  
122 } 121 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyService.java
1 package cn.iocoder.yudao.module.ai.service.model; 1 package cn.iocoder.yudao.module.ai.service.model;
2 2
3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
  4 +import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO; 6 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO;
6 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO; 7 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO;
@@ -91,4 +92,13 @@ public interface AiApiKeyService { @@ -91,4 +92,13 @@ public interface AiApiKeyService {
91 */ 92 */
92 ImageClient getImageClient(AiPlatformEnum platform); 93 ImageClient getImageClient(AiPlatformEnum platform);
93 94
  95 + /**
  96 + * 获得 SunoApi 对象
  97 + *
  98 + * TODO 可优化点:目前默认获取 Suno 对应的第一个开启的配置用于音乐;后续可以支持配置选择
  99 + *
  100 + * @return SunoApi 对象
  101 + */
  102 + SunoApi getSunoApi();
  103 +
94 } 104 }
95 \ No newline at end of file 105 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.ai.service.model; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.ai.service.model;
2 2
3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
4 import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory; 4 import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory;
  5 +import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 6 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -111,4 +112,14 @@ public class AiApiKeyServiceImpl implements AiApiKeyService { @@ -111,4 +112,14 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
111 return clientFactory.getOrCreateImageClient(platform, apiKey.getApiKey(), apiKey.getUrl()); 112 return clientFactory.getOrCreateImageClient(platform, apiKey.getApiKey(), apiKey.getUrl());
112 } 113 }
113 114
  115 + @Override
  116 + public SunoApi getSunoApi() {
  117 + AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(
  118 + AiPlatformEnum.SUNO.getPlatform(), CommonStatusEnum.ENABLE.getStatus());
  119 + if (apiKey == null) {
  120 + return null;
  121 + }
  122 + return clientFactory.getOrCreateSunoApi(apiKey.getApiKey(), apiKey.getUrl());
  123 + }
  124 +
114 } 125 }
115 \ No newline at end of file 126 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java
@@ -35,7 +35,7 @@ public interface AiMusicService { @@ -35,7 +35,7 @@ public interface AiMusicService {
35 * 35 *
36 * @param updateReqVO 更新信息 36 * @param updateReqVO 更新信息
37 */ 37 */
38 - void updateMusicPublicStatus(@Valid AiMusicUpdatePublicStatusReqVO updateReqVO); 38 + void updateMusic(@Valid AiMusicUpdateReqVO updateReqVO);
39 39
40 /** 40 /**
41 * 更新音乐名称 41 * 更新音乐名称
@@ -83,4 +83,5 @@ public interface AiMusicService { @@ -83,4 +83,5 @@ public interface AiMusicService {
83 * @return 音乐分页 83 * @return 音乐分页
84 */ 84 */
85 PageResult<AiMusicDO> getMusicMyPage(AiMusicPageReqVO pageReqVO, Long userId); 85 PageResult<AiMusicDO> getMusicMyPage(AiMusicPageReqVO pageReqVO, Long userId);
  86 +
86 } 87 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java
@@ -12,6 +12,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; @@ -12,6 +12,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO;
12 import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; 12 import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper;
13 import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum; 13 import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum;
14 import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum; 14 import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum;
  15 +import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
15 import cn.iocoder.yudao.module.infra.api.file.FileApi; 16 import cn.iocoder.yudao.module.infra.api.file.FileApi;
16 import jakarta.annotation.Resource; 17 import jakarta.annotation.Resource;
17 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
@@ -35,7 +36,7 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MUSIC_NOT_EXIS @@ -35,7 +36,7 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MUSIC_NOT_EXIS
35 public class AiMusicServiceImpl implements AiMusicService { 36 public class AiMusicServiceImpl implements AiMusicService {
36 37
37 @Resource 38 @Resource
38 - private SunoApi sunoApi; 39 + private AiApiKeyService apiKeyService;
39 40
40 @Resource 41 @Resource
41 private AiMusicMapper musicMapper; 42 private AiMusicMapper musicMapper;
@@ -46,6 +47,8 @@ public class AiMusicServiceImpl implements AiMusicService { @@ -46,6 +47,8 @@ public class AiMusicServiceImpl implements AiMusicService {
46 @Override 47 @Override
47 public List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO) { 48 public List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO) {
48 // 1. 调用 Suno 生成音乐 49 // 1. 调用 Suno 生成音乐
  50 + SunoApi sunoApi = apiKeyService.getSunoApi();
  51 + // TODO @xin:这两个貌似一直没跑成功,你那可以么?用的请求是 AiMusicController.http 的
49 List<SunoApi.MusicData> musicDataList; 52 List<SunoApi.MusicData> musicDataList;
50 if (Objects.equals(AiMusicGenerateModeEnum.LYRIC.getMode(), reqVO.getGenerateMode())) { 53 if (Objects.equals(AiMusicGenerateModeEnum.LYRIC.getMode(), reqVO.getGenerateMode())) {
51 // 1.1 歌词模式 54 // 1.1 歌词模式
@@ -80,6 +83,7 @@ public class AiMusicServiceImpl implements AiMusicService { @@ -80,6 +83,7 @@ public class AiMusicServiceImpl implements AiMusicService {
80 log.info("[syncMusic][Suno 开始同步, 共 ({}) 个任务]", streamingTask.size()); 83 log.info("[syncMusic][Suno 开始同步, 共 ({}) 个任务]", streamingTask.size());
81 84
82 // GET 请求,为避免参数过长,分批次处理 85 // GET 请求,为避免参数过长,分批次处理
  86 + SunoApi sunoApi = apiKeyService.getSunoApi();
83 CollUtil.split(streamingTask, 36).forEach(chunkList -> { 87 CollUtil.split(streamingTask, 36).forEach(chunkList -> {
84 Map<String, Long> taskIdMap = convertMap(chunkList, AiMusicDO::getTaskId, AiMusicDO::getId); 88 Map<String, Long> taskIdMap = convertMap(chunkList, AiMusicDO::getTaskId, AiMusicDO::getId);
85 List<SunoApi.MusicData> musicTaskList = sunoApi.getMusicList(new ArrayList<>(taskIdMap.keySet())); 89 List<SunoApi.MusicData> musicTaskList = sunoApi.getMusicList(new ArrayList<>(taskIdMap.keySet()));
@@ -96,7 +100,7 @@ public class AiMusicServiceImpl implements AiMusicService { @@ -96,7 +100,7 @@ public class AiMusicServiceImpl implements AiMusicService {
96 } 100 }
97 101
98 @Override 102 @Override
99 - public void updateMusicPublicStatus(AiMusicUpdatePublicStatusReqVO updateReqVO) { 103 + public void updateMusic(AiMusicUpdateReqVO updateReqVO) {
100 // 校验存在 104 // 校验存在
101 validateMusicExists(updateReqVO.getId()); 105 validateMusicExists(updateReqVO.getId());
102 // 更新 106 // 更新
@@ -152,11 +156,16 @@ public class AiMusicServiceImpl implements AiMusicService { @@ -152,11 +156,16 @@ public class AiMusicServiceImpl implements AiMusicService {
152 * @return AiMusicDO 集合 156 * @return AiMusicDO 集合
153 */ 157 */
154 private List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) { 158 private List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) {
  159 + // TODO @xin:它有 status = error 状态,表示失败噢。
155 return convertList(musicList, musicData -> new AiMusicDO() 160 return convertList(musicList, musicData -> new AiMusicDO()
156 .setTaskId(musicData.id()).setModel(musicData.modelName()) 161 .setTaskId(musicData.id()).setModel(musicData.modelName())
157 .setPrompt(musicData.prompt()).setGptDescriptionPrompt(musicData.gptDescriptionPrompt()) 162 .setPrompt(musicData.prompt()).setGptDescriptionPrompt(musicData.gptDescriptionPrompt())
158 - .setAudioUrl(createFile(musicData.audioUrl())).setVideoUrl(createFile(musicData.videoUrl())).setImageUrl(createFile(musicData.imageUrl())).setDuration(musicData.duration())  
159 - .setTitle(musicData.title()).setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA)) 163 + // TODO @xin:只有在完成的状态,在下载文件
  164 + .setAudioUrl(downloadFile(musicData.audioUrl()))
  165 + .setVideoUrl(downloadFile(musicData.videoUrl()))
  166 + .setImageUrl(downloadFile(musicData.imageUrl()))
  167 + .setTitle(musicData.title()).setDuration(musicData.duration())
  168 + .setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA))
160 .setStatus(Objects.equals("complete", musicData.status()) ? 169 .setStatus(Objects.equals("complete", musicData.status()) ?
161 AiMusicStatusEnum.SUCCESS.getStatus() : AiMusicStatusEnum.IN_PROGRESS.getStatus())); 170 AiMusicStatusEnum.SUCCESS.getStatus() : AiMusicStatusEnum.IN_PROGRESS.getStatus()));
162 } 171 }
@@ -167,12 +176,17 @@ public class AiMusicServiceImpl implements AiMusicService { @@ -167,12 +176,17 @@ public class AiMusicServiceImpl implements AiMusicService {
167 * @param url 音频文件地址 176 * @param url 音频文件地址
168 * @return 内部文件地址 177 * @return 内部文件地址
169 */ 178 */
170 - private String createFile(String url) { 179 + private String downloadFile(String url) {
171 if (StrUtil.isBlank(url)) { 180 if (StrUtil.isBlank(url)) {
172 return null; 181 return null;
173 } 182 }
174 - byte[] bytes = HttpUtil.downloadBytes(url);  
175 - return fileApi.createFile(bytes); 183 + try {
  184 + byte[] bytes = HttpUtil.downloadBytes(url);
  185 + return fileApi.createFile(bytes);
  186 + } catch (Exception e) {
  187 + log.error("[downloadFile][url({}) 下载失败]", url, e);
  188 + return url;
  189 + }
176 } 190 }
177 191
178 /** 192 /**
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiClientFactory.java
1 package cn.iocoder.yudao.framework.ai.core.factory; 1 package cn.iocoder.yudao.framework.ai.core.factory;
2 2
3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
  4 +import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
4 import org.springframework.ai.chat.StreamingChatClient; 5 import org.springframework.ai.chat.StreamingChatClient;
5 import org.springframework.ai.image.ImageClient; 6 import org.springframework.ai.image.ImageClient;
6 7
@@ -55,4 +56,15 @@ public interface AiClientFactory { @@ -55,4 +56,15 @@ public interface AiClientFactory {
55 */ 56 */
56 ImageClient getOrCreateImageClient(AiPlatformEnum platform, String apiKey, String url); 57 ImageClient getOrCreateImageClient(AiPlatformEnum platform, String apiKey, String url);
57 58
  59 + /**
  60 + * 基于指定配置,获得 SunoApi 对象
  61 + *
  62 + * 如果不存在,则进行创建
  63 + *
  64 + * @param apiKey API KEY
  65 + * @param url API URL
  66 + * @return SunoApi 对象
  67 + */
  68 + SunoApi getOrCreateSunoApi(String apiKey, String url);
  69 +
58 } 70 }
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiClientFactoryImpl.java
@@ -9,6 +9,7 @@ import cn.hutool.extra.spring.SpringUtil; @@ -9,6 +9,7 @@ import cn.hutool.extra.spring.SpringUtil;
9 import cn.iocoder.yudao.framework.ai.config.YudaoAiAutoConfiguration; 9 import cn.iocoder.yudao.framework.ai.config.YudaoAiAutoConfiguration;
10 import cn.iocoder.yudao.framework.ai.config.YudaoAiProperties; 10 import cn.iocoder.yudao.framework.ai.config.YudaoAiProperties;
11 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 11 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
  12 +import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
12 import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatClient; 13 import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatClient;
13 import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatModal; 14 import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatModal;
14 import cn.iocoder.yudao.framework.ai.core.model.tongyi.api.QianWenApi; 15 import cn.iocoder.yudao.framework.ai.core.model.tongyi.api.QianWenApi;
@@ -109,6 +110,11 @@ public class AiClientFactoryImpl implements AiClientFactory { @@ -109,6 +110,11 @@ public class AiClientFactoryImpl implements AiClientFactory {
109 } 110 }
110 } 111 }
111 112
  113 + @Override
  114 + public SunoApi getOrCreateSunoApi(String apiKey, String url) {
  115 + return new SunoApi(url);
  116 + }
  117 +
112 private static String buildClientCacheKey(Class<?> clazz, Object... params) { 118 private static String buildClientCacheKey(Class<?> clazz, Object... params) {
113 if (ArrayUtil.isEmpty(params)) { 119 if (ArrayUtil.isEmpty(params)) {
114 return clazz.getName(); 120 return clazz.getName();
yudao-server/src/main/resources/application.yaml
@@ -201,8 +201,8 @@ yudao.ai: @@ -201,8 +201,8 @@ yudao.ai:
201 notify-url: http://java.nat300.top/admin-api/ai/image/midjourney/notify 201 notify-url: http://java.nat300.top/admin-api/ai/image/midjourney/notify
202 suno: 202 suno:
203 enable: true 203 enable: true
204 - base-url: https://suno-55ishh05u-status2xxs-projects.vercel.app  
205 -# base-url: http://127.0.0.1:3001 204 +# base-url: https://suno-55ishh05u-status2xxs-projects.vercel.app
  205 + base-url: http://127.0.0.1:3001
206 206
207 --- #################### 芋道相关配置 #################### 207 --- #################### 芋道相关配置 ####################
208 208