mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-30 09:48:43 +08:00 
			
		
		
		
	【新增】AI:音乐接入 API KEY 管理
This commit is contained in:
		| @@ -5,9 +5,9 @@ Authorization: {{token}} | |||||||
|  |  | ||||||
| { | { | ||||||
|   "platform": "Suno", |   "platform": "Suno", | ||||||
|   "generateMode": 1, |   "generateMode": 2, | ||||||
|   "prompt": "来一首快乐的歌曲", |   "prompt": "周末啦!", | ||||||
|   "modelVersion": "chirp-v3.5", |   "model": "chirp-v3.5", | ||||||
|   "tags": ["Happy"], |   "tags": ["Happy"], | ||||||
|   "title": "Happy Song" |   "title": "Happy Song" | ||||||
| } | } | ||||||
| @@ -19,8 +19,8 @@ Authorization: {{token}} | |||||||
|  |  | ||||||
| { | { | ||||||
|   "platform": "Suno", |   "platform": "Suno", | ||||||
|   "generateMode": 2, |   "generateMode": 1, | ||||||
|   "prompt": "来一首快乐的歌曲", |   "model": "chirp-v3.5", | ||||||
|   "makeInstrumental": false, |   "gptDescriptionPrompt": "今天是星球六,结果是个下雨天,希望心情很美丽", | ||||||
|   "title": "Happy Song" |   "makeInstrumental": false | ||||||
| } | } | ||||||
| @@ -35,6 +35,16 @@ public class AiMusicController { | |||||||
|         return success(BeanUtils.toBean(pageResult, AiMusicRespVO.class)); |         return success(BeanUtils.toBean(pageResult, AiMusicRespVO.class)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @PostMapping("/generate") | ||||||
|  |     @Operation(summary = "音乐生成") | ||||||
|  |     public CommonResult<List<Long>> generateMusic(@RequestBody @Valid AiSunoGenerateReqVO reqVO) { | ||||||
|  |         if (true) { | ||||||
|  |             musicService.syncMusic(); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         return success(musicService.generateMusic(getLoginUserId(), reqVO)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Operation(summary = "删除【我的】音乐记录") |     @Operation(summary = "删除【我的】音乐记录") | ||||||
|     @DeleteMapping("/delete-my") |     @DeleteMapping("/delete-my") | ||||||
|     @Parameter(name = "id", required = true, description = "音乐编号", example = "1024") |     @Parameter(name = "id", required = true, description = "音乐编号", example = "1024") | ||||||
| @@ -54,6 +64,7 @@ public class AiMusicController { | |||||||
|         return success(BeanUtils.toBean(music, AiMusicRespVO.class)); |         return success(BeanUtils.toBean(music, AiMusicRespVO.class)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // TODO @xin:这个搞成 updateMy ,修改【我的】音乐。方便后续支持其它字段;另外,需要校验下,更新的音乐,是不是我的! | ||||||
|     @PostMapping("/updateTitle-my") |     @PostMapping("/updateTitle-my") | ||||||
|     @Operation(summary = "修改【我的】音乐 目前只支持修改标题") |     @Operation(summary = "修改【我的】音乐 目前只支持修改标题") | ||||||
|     @Parameter(name = "title", required = true, description = "音乐名称", example = "夜空中最亮的星") |     @Parameter(name = "title", required = true, description = "音乐名称", example = "夜空中最亮的星") | ||||||
| @@ -62,12 +73,6 @@ public class AiMusicController { | |||||||
|         return success(true); |         return success(true); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @PostMapping("/generate") |  | ||||||
|     @Operation(summary = "音乐生成") |  | ||||||
|     public CommonResult<List<Long>> generateMusic(@RequestBody @Valid AiSunoGenerateReqVO reqVO) { |  | ||||||
|         return success(musicService.generateMusic(getLoginUserId(), reqVO)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // ================ 音乐管理 ================ |     // ================ 音乐管理 ================ | ||||||
|  |  | ||||||
|     @GetMapping("/page") |     @GetMapping("/page") | ||||||
| @@ -87,11 +92,11 @@ public class AiMusicController { | |||||||
|         return success(true); |         return success(true); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @PutMapping("/update-public-status") |     @PutMapping("/update") | ||||||
|     @Operation(summary = "更新音乐发布状态") |     @Operation(summary = "更新音乐") | ||||||
|     @PreAuthorize("@ss.hasPermission('ai:music:update')") |     @PreAuthorize("@ss.hasPermission('ai:music:update')") | ||||||
|     public CommonResult<Boolean> updateMusicPublicStatus(@Valid @RequestBody AiMusicUpdatePublicStatusReqVO updateReqVO) { |     public CommonResult<Boolean> updateMusic(@Valid @RequestBody AiMusicUpdateReqVO updateReqVO) { | ||||||
|         musicService.updateMusicPublicStatus(updateReqVO); |         musicService.updateMusic(updateReqVO); | ||||||
|         return success(true); |         return success(true); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -52,6 +52,9 @@ public class AiMusicRespVO { | |||||||
|     @Schema(description = "音乐风格标签") |     @Schema(description = "音乐风格标签") | ||||||
|     private List<String> tags; |     private List<String> tags; | ||||||
|  |  | ||||||
|  |     @Schema(description = "音乐时长", example = "[\"pop\",\"jazz\",\"punk\"]") | ||||||
|  |     private Double duration; | ||||||
|  |  | ||||||
|     @Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") |     @Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") | ||||||
|     private Boolean publicStatus; |     private Boolean publicStatus; | ||||||
|  |  | ||||||
| @@ -61,9 +64,6 @@ public class AiMusicRespVO { | |||||||
|     @Schema(description = "错误信息") |     @Schema(description = "错误信息") | ||||||
|     private String errorMessage; |     private String errorMessage; | ||||||
|  |  | ||||||
|     @Schema(description = "音乐时长") |  | ||||||
|     private Double duration; |  | ||||||
|  |  | ||||||
|     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) |     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) | ||||||
|     private LocalDateTime createTime; |     private LocalDateTime createTime; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,15 +4,15 @@ import io.swagger.v3.oas.annotations.media.Schema; | |||||||
| import jakarta.validation.constraints.NotNull; | import jakarta.validation.constraints.NotNull; | ||||||
| import lombok.Data; | import lombok.Data; | ||||||
| 
 | 
 | ||||||
| @Schema(description = "管理后台 - AI 音乐修改发布状态 Request VO") | @Schema(description = "管理后台 - AI 音乐修改 Request VO") | ||||||
| @Data | @Data | ||||||
| public class AiMusicUpdatePublicStatusReqVO { | public class AiMusicUpdateReqVO { | ||||||
| 
 | 
 | ||||||
|     @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583") |     @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583") | ||||||
|  |     @NotNull(message = "编号不能为空") | ||||||
|     private Long id; |     private Long id; | ||||||
| 
 | 
 | ||||||
|     @Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") |     @Schema(description = "是否发布", example = "true") | ||||||
|     @NotNull(message = "是否发布不能为空") |  | ||||||
|     private Boolean publicStatus; |     private Boolean publicStatus; | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| @@ -20,11 +20,13 @@ public class AiSunoGenerateReqVO { | |||||||
|      * 1. 描述模式:描述词 + 是否纯音乐 + 模型 |      * 1. 描述模式:描述词 + 是否纯音乐 + 模型 | ||||||
|      * 2. 歌词模式:歌词 + 音乐风格 + 标题 + 模型 |      * 2. 歌词模式:歌词 + 音乐风格 + 标题 + 模型 | ||||||
|      */ |      */ | ||||||
|     @Schema(description = "生成模式 1.描述模式 2. 歌词模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") |     @Schema(description = "生成模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") | ||||||
|     @NotNull(message = "生成模式不能为空") |     @NotNull(message = "生成模式不能为空") | ||||||
|     private Integer generateMode; // 参见 AiMusicGenerateModeEnum 枚举 |     private Integer generateMode; // 参见 AiMusicGenerateModeEnum 枚举 | ||||||
|  |  | ||||||
|     @Schema(description = "歌词模式用:用于生成音乐音频的歌词提示", requiredMode = Schema.RequiredMode.NOT_REQUIRED, |     // TODO @xin:方案一:prompt => lyric 歌词;gptDescriptionPrompt => description 描述(db 那字段也改下,避免和 gpt 直接耦合);这样搞完后,会更统一好理解一点 | ||||||
|  |     // TODO @xin:方案二:还是之前的做法,都用 prompt;不过最终 gptDescriptionPrompt 还是存储 description 算描述。可以微信一起讨论下。 | ||||||
|  |     @Schema(description = "用于生成音乐音频的歌词提示", | ||||||
|             example = """ |             example = """ | ||||||
|                     [Verse] |                     [Verse] | ||||||
|                     阳光下奔跑 多么欢快 |                     阳光下奔跑 多么欢快 | ||||||
| @@ -37,23 +39,23 @@ public class AiSunoGenerateReqVO { | |||||||
|                     日子太短暂 别再等待 |                     日子太短暂 别再等待 | ||||||
|                     马上放假了 梦想起飞 |                     马上放假了 梦想起飞 | ||||||
|                     """) |                     """) | ||||||
|     private String prompt; |     private String prompt; // 歌词模式用 | ||||||
|  |  | ||||||
|     @Schema(description = "描述模式用:用于生成音乐音频的描述", requiredMode = Schema.RequiredMode.NOT_REQUIRED, |     @Schema(description = "用于生成音乐音频的描述", | ||||||
|             example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。") |             example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。") | ||||||
|     private String gptDescriptionPrompt; |     private String gptDescriptionPrompt; // 描述模式用 | ||||||
|  |  | ||||||
|     @Schema(description = "是否纯音乐", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "true") |     @Schema(description = "是否纯音乐", example = "true") | ||||||
|     private Boolean makeInstrumental; |     private Boolean makeInstrumental; | ||||||
|  |  | ||||||
|     @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5") |     @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5") | ||||||
|     @NotEmpty(message = "模型不能为空") |     @NotEmpty(message = "模型不能为空") | ||||||
|     private String model; // 参见 AiModelEnum 枚举 |     private String model; // 参见 AiModelEnum 枚举 | ||||||
|  |  | ||||||
|     @Schema(description = "音乐风格", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "[\"pop\",\"jazz\",\"punk\"]") |     @Schema(description = "音乐风格", example = "[\"pop\",\"jazz\",\"punk\"]") | ||||||
|     private List<String> tags; |     private List<String> tags; | ||||||
|  |  | ||||||
|     @Schema(description = "音乐/歌曲名称", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "夜空中最亮的星") |     @Schema(description = "音乐/歌曲名称", example = "夜空中最亮的星") | ||||||
|     private String title; |     private String title; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -98,6 +98,11 @@ public class AiMusicDO extends BaseDO { | |||||||
|     @TableField(typeHandler = JacksonTypeHandler.class) |     @TableField(typeHandler = JacksonTypeHandler.class) | ||||||
|     private List<String> tags; |     private List<String> tags; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 音乐时长 | ||||||
|  |      */ | ||||||
|  |     private Double duration; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 是否公开 |      * 是否公开 | ||||||
|      */ |      */ | ||||||
| @@ -113,10 +118,4 @@ public class AiMusicDO extends BaseDO { | |||||||
|      */ |      */ | ||||||
|     private String errorMessage; |     private String errorMessage; | ||||||
|  |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 音乐时长 |  | ||||||
|      */ |  | ||||||
|     private Double duration; |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package cn.iocoder.yudao.module.ai.service.model; | package cn.iocoder.yudao.module.ai.service.model; | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; | import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; | ||||||
|  | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | ||||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||||
| import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO; | import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO; | ||||||
| import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO; | import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO; | ||||||
| @@ -91,4 +92,13 @@ public interface AiApiKeyService { | |||||||
|      */ |      */ | ||||||
|     ImageClient getImageClient(AiPlatformEnum platform); |     ImageClient getImageClient(AiPlatformEnum platform); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获得 SunoApi 对象 | ||||||
|  |      * | ||||||
|  |      * TODO 可优化点:目前默认获取 Suno 对应的第一个开启的配置用于音乐;后续可以支持配置选择 | ||||||
|  |      * | ||||||
|  |      * @return SunoApi 对象 | ||||||
|  |      */ | ||||||
|  |     SunoApi getSunoApi(); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.ai.service.model; | |||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; | import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; | ||||||
| import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory; | import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory; | ||||||
|  | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | ||||||
| import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||||
| import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | ||||||
| @@ -111,4 +112,14 @@ public class AiApiKeyServiceImpl implements AiApiKeyService { | |||||||
|         return clientFactory.getOrCreateImageClient(platform, apiKey.getApiKey(), apiKey.getUrl()); |         return clientFactory.getOrCreateImageClient(platform, apiKey.getApiKey(), apiKey.getUrl()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public SunoApi getSunoApi() { | ||||||
|  |         AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus( | ||||||
|  |                 AiPlatformEnum.SUNO.getPlatform(), CommonStatusEnum.ENABLE.getStatus()); | ||||||
|  |         if (apiKey == null) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         return clientFactory.getOrCreateSunoApi(apiKey.getApiKey(), apiKey.getUrl()); | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -35,7 +35,7 @@ public interface AiMusicService { | |||||||
|      * |      * | ||||||
|      * @param updateReqVO 更新信息 |      * @param updateReqVO 更新信息 | ||||||
|      */ |      */ | ||||||
|     void updateMusicPublicStatus(@Valid AiMusicUpdatePublicStatusReqVO updateReqVO); |     void updateMusic(@Valid AiMusicUpdateReqVO updateReqVO); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 更新音乐名称 |      * 更新音乐名称 | ||||||
| @@ -83,4 +83,5 @@ public interface AiMusicService { | |||||||
|      * @return 音乐分页 |      * @return 音乐分页 | ||||||
|      */ |      */ | ||||||
|     PageResult<AiMusicDO> getMusicMyPage(AiMusicPageReqVO pageReqVO, Long userId); |     PageResult<AiMusicDO> getMusicMyPage(AiMusicPageReqVO pageReqVO, Long userId); | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; | |||||||
| import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; | import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; | ||||||
| import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum; | import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum; | ||||||
| import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum; | import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum; | ||||||
|  | import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; | ||||||
| import cn.iocoder.yudao.module.infra.api.file.FileApi; | import cn.iocoder.yudao.module.infra.api.file.FileApi; | ||||||
| import jakarta.annotation.Resource; | import jakarta.annotation.Resource; | ||||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||||
| @@ -35,7 +36,7 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MUSIC_NOT_EXIS | |||||||
| public class AiMusicServiceImpl implements AiMusicService { | public class AiMusicServiceImpl implements AiMusicService { | ||||||
|  |  | ||||||
|     @Resource |     @Resource | ||||||
|     private SunoApi sunoApi; |     private AiApiKeyService apiKeyService; | ||||||
|  |  | ||||||
|     @Resource |     @Resource | ||||||
|     private AiMusicMapper musicMapper; |     private AiMusicMapper musicMapper; | ||||||
| @@ -46,6 +47,8 @@ public class AiMusicServiceImpl implements AiMusicService { | |||||||
|     @Override |     @Override | ||||||
|     public List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO) { |     public List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO) { | ||||||
|         // 1. 调用 Suno 生成音乐 |         // 1. 调用 Suno 生成音乐 | ||||||
|  |         SunoApi sunoApi = apiKeyService.getSunoApi(); | ||||||
|  |         // TODO @xin:这两个貌似一直没跑成功,你那可以么?用的请求是 AiMusicController.http 的 | ||||||
|         List<SunoApi.MusicData> musicDataList; |         List<SunoApi.MusicData> musicDataList; | ||||||
|         if (Objects.equals(AiMusicGenerateModeEnum.LYRIC.getMode(), reqVO.getGenerateMode())) { |         if (Objects.equals(AiMusicGenerateModeEnum.LYRIC.getMode(), reqVO.getGenerateMode())) { | ||||||
|             // 1.1 歌词模式 |             // 1.1 歌词模式 | ||||||
| @@ -80,6 +83,7 @@ public class AiMusicServiceImpl implements AiMusicService { | |||||||
|         log.info("[syncMusic][Suno 开始同步, 共 ({}) 个任务]", streamingTask.size()); |         log.info("[syncMusic][Suno 开始同步, 共 ({}) 个任务]", streamingTask.size()); | ||||||
|  |  | ||||||
|         // GET 请求,为避免参数过长,分批次处理 |         // GET 请求,为避免参数过长,分批次处理 | ||||||
|  |         SunoApi sunoApi = apiKeyService.getSunoApi(); | ||||||
|         CollUtil.split(streamingTask, 36).forEach(chunkList -> { |         CollUtil.split(streamingTask, 36).forEach(chunkList -> { | ||||||
|             Map<String, Long> taskIdMap = convertMap(chunkList, AiMusicDO::getTaskId, AiMusicDO::getId); |             Map<String, Long> taskIdMap = convertMap(chunkList, AiMusicDO::getTaskId, AiMusicDO::getId); | ||||||
|             List<SunoApi.MusicData> musicTaskList = sunoApi.getMusicList(new ArrayList<>(taskIdMap.keySet())); |             List<SunoApi.MusicData> musicTaskList = sunoApi.getMusicList(new ArrayList<>(taskIdMap.keySet())); | ||||||
| @@ -96,7 +100,7 @@ public class AiMusicServiceImpl implements AiMusicService { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void updateMusicPublicStatus(AiMusicUpdatePublicStatusReqVO updateReqVO) { |     public void updateMusic(AiMusicUpdateReqVO updateReqVO) { | ||||||
|         // 校验存在 |         // 校验存在 | ||||||
|         validateMusicExists(updateReqVO.getId()); |         validateMusicExists(updateReqVO.getId()); | ||||||
|         // 更新 |         // 更新 | ||||||
| @@ -152,11 +156,16 @@ public class AiMusicServiceImpl implements AiMusicService { | |||||||
|      * @return AiMusicDO 集合 |      * @return AiMusicDO 集合 | ||||||
|      */ |      */ | ||||||
|     private List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) { |     private List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) { | ||||||
|  |         // TODO @xin:它有 status = error 状态,表示失败噢。 | ||||||
|         return convertList(musicList, musicData -> new AiMusicDO() |         return convertList(musicList, musicData -> new AiMusicDO() | ||||||
|                 .setTaskId(musicData.id()).setModel(musicData.modelName()) |                 .setTaskId(musicData.id()).setModel(musicData.modelName()) | ||||||
|                 .setPrompt(musicData.prompt()).setGptDescriptionPrompt(musicData.gptDescriptionPrompt()) |                 .setPrompt(musicData.prompt()).setGptDescriptionPrompt(musicData.gptDescriptionPrompt()) | ||||||
|                 .setAudioUrl(createFile(musicData.audioUrl())).setVideoUrl(createFile(musicData.videoUrl())).setImageUrl(createFile(musicData.imageUrl())).setDuration(musicData.duration()) |                 // TODO @xin:只有在完成的状态,在下载文件 | ||||||
|                 .setTitle(musicData.title()).setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA)) |                 .setAudioUrl(downloadFile(musicData.audioUrl())) | ||||||
|  |                 .setVideoUrl(downloadFile(musicData.videoUrl())) | ||||||
|  |                 .setImageUrl(downloadFile(musicData.imageUrl())) | ||||||
|  |                 .setTitle(musicData.title()).setDuration(musicData.duration()) | ||||||
|  |                 .setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA)) | ||||||
|                 .setStatus(Objects.equals("complete", musicData.status()) ? |                 .setStatus(Objects.equals("complete", musicData.status()) ? | ||||||
|                         AiMusicStatusEnum.SUCCESS.getStatus() : AiMusicStatusEnum.IN_PROGRESS.getStatus())); |                         AiMusicStatusEnum.SUCCESS.getStatus() : AiMusicStatusEnum.IN_PROGRESS.getStatus())); | ||||||
|     } |     } | ||||||
| @@ -167,12 +176,17 @@ public class AiMusicServiceImpl implements AiMusicService { | |||||||
|      * @param url 音频文件地址 |      * @param url 音频文件地址 | ||||||
|      * @return 内部文件地址 |      * @return 内部文件地址 | ||||||
|      */ |      */ | ||||||
|     private String createFile(String url) { |     private String downloadFile(String url) { | ||||||
|         if (StrUtil.isBlank(url)) { |         if (StrUtil.isBlank(url)) { | ||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
|         byte[] bytes = HttpUtil.downloadBytes(url); |         try { | ||||||
|         return fileApi.createFile(bytes); |             byte[] bytes = HttpUtil.downloadBytes(url); | ||||||
|  |             return fileApi.createFile(bytes); | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             log.error("[downloadFile][url({}) 下载失败]", url, e); | ||||||
|  |             return url; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package cn.iocoder.yudao.framework.ai.core.factory; | package cn.iocoder.yudao.framework.ai.core.factory; | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; | import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; | ||||||
|  | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | ||||||
| import org.springframework.ai.chat.StreamingChatClient; | import org.springframework.ai.chat.StreamingChatClient; | ||||||
| import org.springframework.ai.image.ImageClient; | import org.springframework.ai.image.ImageClient; | ||||||
|  |  | ||||||
| @@ -55,4 +56,15 @@ public interface AiClientFactory { | |||||||
|      */ |      */ | ||||||
|     ImageClient getOrCreateImageClient(AiPlatformEnum platform, String apiKey, String url); |     ImageClient getOrCreateImageClient(AiPlatformEnum platform, String apiKey, String url); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 基于指定配置,获得 SunoApi 对象 | ||||||
|  |      * | ||||||
|  |      * 如果不存在,则进行创建 | ||||||
|  |      * | ||||||
|  |      * @param apiKey API KEY | ||||||
|  |      * @param url API URL | ||||||
|  |      * @return SunoApi 对象 | ||||||
|  |      */ | ||||||
|  |     SunoApi getOrCreateSunoApi(String apiKey, String url); | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import cn.hutool.extra.spring.SpringUtil; | |||||||
| import cn.iocoder.yudao.framework.ai.config.YudaoAiAutoConfiguration; | import cn.iocoder.yudao.framework.ai.config.YudaoAiAutoConfiguration; | ||||||
| import cn.iocoder.yudao.framework.ai.config.YudaoAiProperties; | import cn.iocoder.yudao.framework.ai.config.YudaoAiProperties; | ||||||
| import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; | import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; | ||||||
|  | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | ||||||
| import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatClient; | import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatClient; | ||||||
| import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatModal; | import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatModal; | ||||||
| import cn.iocoder.yudao.framework.ai.core.model.tongyi.api.QianWenApi; | import cn.iocoder.yudao.framework.ai.core.model.tongyi.api.QianWenApi; | ||||||
| @@ -109,6 +110,11 @@ public class AiClientFactoryImpl implements AiClientFactory { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public SunoApi getOrCreateSunoApi(String apiKey, String url) { | ||||||
|  |         return new SunoApi(url); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private static String buildClientCacheKey(Class<?> clazz, Object... params) { |     private static String buildClientCacheKey(Class<?> clazz, Object... params) { | ||||||
|         if (ArrayUtil.isEmpty(params)) { |         if (ArrayUtil.isEmpty(params)) { | ||||||
|             return clazz.getName(); |             return clazz.getName(); | ||||||
|   | |||||||
| @@ -201,8 +201,8 @@ yudao.ai: | |||||||
|     notify-url: http://java.nat300.top/admin-api/ai/image/midjourney/notify |     notify-url: http://java.nat300.top/admin-api/ai/image/midjourney/notify | ||||||
|   suno: |   suno: | ||||||
|     enable: true |     enable: true | ||||||
|     base-url: https://suno-55ishh05u-status2xxs-projects.vercel.app | #    base-url: https://suno-55ishh05u-status2xxs-projects.vercel.app | ||||||
| #    base-url: http://127.0.0.1:3001 |     base-url: http://127.0.0.1:3001 | ||||||
|  |  | ||||||
| --- #################### 芋道相关配置 #################### | --- #################### 芋道相关配置 #################### | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV