mirror of
https://gitee.com/hhyykk/ipms-sjy.git
synced 2025-07-25 00:15:06 +08:00
Merge branch 'master-jdk21-ai' into master-jdk21-ai-write
This commit is contained in:
@ -9,7 +9,7 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePageReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageRespVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdatePublicStatusReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
|
||||
@ -25,6 +25,8 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
@ -37,15 +39,16 @@ public class AiImageController {
|
||||
@Resource
|
||||
private AiImageService imageService;
|
||||
|
||||
@Operation(summary = "获取【我的】绘图分页")
|
||||
@GetMapping("/my-page")
|
||||
@Operation(summary = "获取【我的】绘图分页")
|
||||
public CommonResult<PageResult<AiImageRespVO>> getImagePageMy(@Validated PageParam pageReqVO) {
|
||||
PageResult<AiImageDO> pageResult = imageService.getImagePageMy(getLoginUserId(), pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, AiImageRespVO.class));
|
||||
}
|
||||
|
||||
@Operation(summary = "获取【我的】绘图记录")
|
||||
@GetMapping("/get-my")
|
||||
@Operation(summary = "获取【我的】绘图记录")
|
||||
@Parameter(name = "id", required = true, description = "绘画编号", example = "1024")
|
||||
public CommonResult<AiImageRespVO> getImageMy(@RequestParam("id") Long id) {
|
||||
AiImageDO image = imageService.getImage(id);
|
||||
if (image == null || ObjUtil.notEqual(getLoginUserId(), image.getUserId())) {
|
||||
@ -54,6 +57,15 @@ public class AiImageController {
|
||||
return success(BeanUtils.toBean(image, AiImageRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/my-list-by-ids")
|
||||
@Operation(summary = "获取【我的】绘图记录列表")
|
||||
@Parameter(name = "ids", required = true, description = "绘画编号数组", example = "1024,2048")
|
||||
public CommonResult<List<AiImageRespVO>> getImageListMyByIds(@RequestParam("ids") List<Long> ids) {
|
||||
List<AiImageDO> imageList = imageService.getImageList(ids);
|
||||
imageList.removeIf(item -> !ObjUtil.equal(getLoginUserId(), item.getUserId()));
|
||||
return success(BeanUtils.toBean(imageList, AiImageRespVO.class));
|
||||
}
|
||||
|
||||
@Operation(summary = "生成图片")
|
||||
@PostMapping("/draw")
|
||||
public CommonResult<Long> drawImage(@Validated @RequestBody AiImageDrawReqVO drawReqVO) {
|
||||
@ -102,11 +114,11 @@ public class AiImageController {
|
||||
return success(BeanUtils.toBean(pageResult, AiImageRespVO.class));
|
||||
}
|
||||
|
||||
@PutMapping("/update-public-status")
|
||||
@Operation(summary = "更新绘画发布状态")
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新绘画")
|
||||
@PreAuthorize("@ss.hasPermission('ai:image:update')")
|
||||
public CommonResult<Boolean> updateImagePublicStatus(@Valid @RequestBody AiImageUpdatePublicStatusReqVO updateReqVO) {
|
||||
imageService.updateImagePublicStatus(updateReqVO);
|
||||
public CommonResult<Boolean> updateImage(@Valid @RequestBody AiImageUpdateReqVO updateReqVO) {
|
||||
imageService.updateImage(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ public class AiImagePageReqVO extends PageParam {
|
||||
@Schema(description = "用户编号", example = "28987")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "平台")
|
||||
@Schema(description = "平台", example = "OpenAI")
|
||||
private String platform;
|
||||
|
||||
@Schema(description = "绘画状态", example = "1")
|
||||
|
@ -4,15 +4,15 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - AI 绘画修改发布状态 Request VO")
|
||||
@Schema(description = "管理后台 - AI 绘画修改 Request VO")
|
||||
@Data
|
||||
public class AiImageUpdatePublicStatusReqVO {
|
||||
public class AiImageUpdateReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
|
||||
@NotNull(message = "编号不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
@NotNull(message = "是否发布不能为空")
|
||||
@Schema(description = "是否发布", example = "true")
|
||||
private Boolean publicStatus;
|
||||
|
||||
}
|
@ -31,8 +31,7 @@ public class AiMidjourneyImagineReqVO {
|
||||
@NotEmpty(message = "版本号不能为空")
|
||||
private String version;
|
||||
|
||||
// TODO @fan:参考图,建议用 referImageUrl。
|
||||
@Schema(description = "垫图(参考图)base64数组")
|
||||
private List<String> base64Array;
|
||||
@Schema(description = "参考图")
|
||||
private String referImageUrl;
|
||||
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
### 生成音乐:Suno +
|
||||
POST {{baseUrl}}/ai/music/generate
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
{
|
||||
"platform": "Suno",
|
||||
"generateMode": 1,
|
||||
"prompt": "来一首快乐的歌曲",
|
||||
"modelVersion": "chirp-v3.5",
|
||||
"tags": ["Happy"],
|
||||
"title": "Happy Song"
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
### 生成音乐:Suno + 歌词模式
|
||||
POST {{baseUrl}}/ai/music/generate
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
{
|
||||
"platform": "Suno",
|
||||
"generateMode": 2,
|
||||
"prompt": "周末啦!",
|
||||
"model": "chirp-v3.5",
|
||||
"tags": ["Happy"],
|
||||
"title": "Happy Song"
|
||||
}
|
||||
|
||||
### 生成音乐:Suno + 描述模式
|
||||
POST {{baseUrl}}/ai/music/generate
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
{
|
||||
"platform": "Suno",
|
||||
"generateMode": 1,
|
||||
"model": "chirp-v3.5",
|
||||
"gptDescriptionPrompt": "今天是星球六,结果是个下雨天,希望心情很美丽",
|
||||
"makeInstrumental": false
|
||||
}
|
@ -1,16 +1,19 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.music;
|
||||
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.music.vo.*;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO;
|
||||
import cn.iocoder.yudao.module.ai.service.music.AiMusicService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -25,10 +28,71 @@ public class AiMusicController {
|
||||
@Resource
|
||||
private AiMusicService musicService;
|
||||
|
||||
@GetMapping("/my-page")
|
||||
@Operation(summary = "获得【我的】音乐分页")
|
||||
public CommonResult<PageResult<AiMusicRespVO>> getMusicMyPage(@Valid AiMusicPageReqVO pageReqVO) {
|
||||
PageResult<AiMusicDO> pageResult = musicService.getMusicMyPage(pageReqVO, getLoginUserId());
|
||||
return success(BeanUtils.toBean(pageResult, AiMusicRespVO.class));
|
||||
}
|
||||
|
||||
@PostMapping("/generate")
|
||||
@Operation(summary = "音乐生成")
|
||||
public CommonResult<List<Long>> generateMusic(@RequestBody @Valid AiSunoGenerateReqVO reqVO) {
|
||||
return success(musicService.generateMusic(getLoginUserId(), reqVO));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除【我的】音乐记录")
|
||||
@DeleteMapping("/delete-my")
|
||||
@Parameter(name = "id", required = true, description = "音乐编号", example = "1024")
|
||||
public CommonResult<Boolean> deleteMusicMy(@RequestParam("id") Long id) {
|
||||
musicService.deleteMusicMy(id, getLoginUserId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get-my")
|
||||
@Operation(summary = "获取【我的】音乐")
|
||||
@Parameter(name = "id", required = true, description = "音乐编号", example = "1024")
|
||||
public CommonResult<AiMusicRespVO> getMusicMy(@RequestParam("id") Long id) {
|
||||
AiMusicDO music = musicService.getMusic(id);
|
||||
if (music == null || ObjUtil.notEqual(getLoginUserId(), music.getUserId())) {
|
||||
return success(null);
|
||||
}
|
||||
return success(BeanUtils.toBean(music, AiMusicRespVO.class));
|
||||
}
|
||||
|
||||
@PostMapping("/update-my")
|
||||
@Operation(summary = "修改【我的】音乐 目前只支持修改标题")
|
||||
@Parameter(name = "title", required = true, description = "音乐名称", example = "夜空中最亮的星")
|
||||
public CommonResult<Boolean> updateMy(AiMusicUpdateReqVO updateReqVO) {
|
||||
musicService.updateMyMusic(updateReqVO, getLoginUserId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// ================ 音乐管理 ================
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得音乐分页")
|
||||
@PreAuthorize("@ss.hasPermission('ai:music:query')")
|
||||
public CommonResult<PageResult<AiMusicRespVO>> getMusicPage(@Valid AiMusicPageReqVO pageReqVO) {
|
||||
PageResult<AiMusicDO> pageResult = musicService.getMusicPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, AiMusicRespVO.class));
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除音乐")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('ai:music:delete')")
|
||||
public CommonResult<Boolean> deleteMusic(@RequestParam("id") Long id) {
|
||||
musicService.deleteMusic(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新音乐")
|
||||
@PreAuthorize("@ss.hasPermission('ai:music:update')")
|
||||
public CommonResult<Boolean> updateMusic(@Valid @RequestBody AiMusicUpdateReqVO updateReqVO) {
|
||||
musicService.updateMusic(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,44 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.music.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum;
|
||||
import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - AI 音乐分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class AiMusicPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "用户编号", example = "12212")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "音乐名称", example = "夜空中最亮的星")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "音乐状态", example = "20")
|
||||
@InEnum(AiMusicStatusEnum.class)
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "生成模式", example = "1")
|
||||
@InEnum(AiMusicGenerateModeEnum.class)
|
||||
private Integer generateMode;
|
||||
|
||||
@Schema(description = "是否发布", example = "true")
|
||||
private Boolean publicStatus;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.music.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - AI 音乐 Response VO")
|
||||
@Data
|
||||
public class AiMusicRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12212")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "音乐名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "夜空中最亮的星")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "歌词", example = "oh~卖糕的")
|
||||
private String lyric;
|
||||
|
||||
@Schema(description = "图片地址", example = "https://www.iocoder.cn")
|
||||
private String imageUrl;
|
||||
|
||||
@Schema(description = "音频地址", example = "https://www.iocoder.cn")
|
||||
private String audioUrl;
|
||||
|
||||
@Schema(description = "视频地址", example = "https://www.iocoder.cn")
|
||||
private String videoUrl;
|
||||
|
||||
@Schema(description = "音乐状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "描述词", example = "一首轻快的歌曲")
|
||||
private String gptDescriptionPrompt;
|
||||
|
||||
@Schema(description = "提示词", example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。")
|
||||
private String prompt;
|
||||
|
||||
@Schema(description = "模型平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "Suno")
|
||||
private String platform;
|
||||
|
||||
@Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5")
|
||||
private String model;
|
||||
|
||||
@Schema(description = "生成模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer generateMode;
|
||||
|
||||
@Schema(description = "音乐风格标签")
|
||||
private List<String> tags;
|
||||
|
||||
@Schema(description = "音乐时长", example = "[\"pop\",\"jazz\",\"punk\"]")
|
||||
private Double duration;
|
||||
|
||||
@Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
private Boolean publicStatus;
|
||||
|
||||
@Schema(description = "任务编号", example = "11369")
|
||||
private String taskId;
|
||||
|
||||
@Schema(description = "错误信息")
|
||||
private String errorMessage;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.music.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - AI 音乐修改 Request VO")
|
||||
@Data
|
||||
public class AiMusicUpdateReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
|
||||
@NotNull(message = "编号不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "是否发布", example = "true")
|
||||
private Boolean publicStatus;
|
||||
|
||||
// TODO @xin:得单独一个 vo。因为万一。。。模拟请求,就可以改 publicStatus 拉
|
||||
@Schema(description = "音乐名称", example = "夜空中最亮的星")
|
||||
private String title;
|
||||
|
||||
}
|
@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.music.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@ -15,24 +16,42 @@ public class AiSunoGenerateReqVO {
|
||||
@NotBlank(message = "平台不能为空")
|
||||
private String platform; // 参见 AiPlatformEnum 枚举
|
||||
|
||||
/**
|
||||
* 1. 描述模式:描述词 + 是否纯音乐 + 模型
|
||||
* 2. 歌词模式:歌词 + 音乐风格 + 标题 + 模型
|
||||
*/
|
||||
@Schema(description = "生成模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
@NotNull(message = "生成模式不能为空")
|
||||
private Integer generateMode; // 参见 AiMusicGenerateModeEnum 枚举
|
||||
|
||||
@Schema(description = "用于生成音乐音频的提示", requiredMode = Schema.RequiredMode.REQUIRED,
|
||||
example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。")
|
||||
@Schema(description = "用于生成音乐音频的歌词提示",
|
||||
example = """
|
||||
1.描述模式:创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。
|
||||
2.歌词模式:
|
||||
[Verse]
|
||||
阳光下奔跑 多么欢快
|
||||
假期就要来 心都飞起来
|
||||
朋友在一旁 笑声又灿烂
|
||||
无忧无虑的 每一天甜蜜
|
||||
[Chorus]
|
||||
马上放假了 快来庆祝
|
||||
一起去旅行 快去冒险
|
||||
日子太短暂 别再等待
|
||||
马上放假了 梦想起飞
|
||||
""")
|
||||
private String prompt;
|
||||
|
||||
@Schema(description = "是否纯音乐", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "true")
|
||||
@Schema(description = "是否纯音乐", example = "true")
|
||||
private Boolean makeInstrumental;
|
||||
|
||||
@Schema(description = "模型版本", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "chirp-v3.5")
|
||||
private String modelVersion; // 参见 AiModelEnum 枚举
|
||||
@Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5")
|
||||
@NotEmpty(message = "模型不能为空")
|
||||
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;
|
||||
|
||||
@Schema(description = "音乐/歌曲名称", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "夜空中最亮的星")
|
||||
@Schema(description = "音乐/歌曲名称", example = "夜空中最亮的星")
|
||||
private String title;
|
||||
|
||||
}
|
@ -18,7 +18,7 @@ import java.util.List;
|
||||
*
|
||||
* @author xiaoxin
|
||||
*/
|
||||
@TableName("ai_music")
|
||||
@TableName(value = "ai_music", autoResultMap = true)
|
||||
@Data
|
||||
public class AiMusicDO extends BaseDO {
|
||||
|
||||
@ -30,7 +30,7 @@ public class AiMusicDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*
|
||||
* <p>
|
||||
* 关联 AdminUserDO 的 userId 字段
|
||||
*/
|
||||
private Long userId;
|
||||
@ -67,7 +67,7 @@ public class AiMusicDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 生成模式
|
||||
*
|
||||
* <p>
|
||||
* 枚举 {@link AiMusicGenerateModeEnum}
|
||||
*/
|
||||
private Integer generateMode;
|
||||
@ -75,11 +75,7 @@ public class AiMusicDO extends BaseDO {
|
||||
/**
|
||||
* 描述词
|
||||
*/
|
||||
private String gptDescriptionPrompt;
|
||||
/**
|
||||
* 提示词
|
||||
*/
|
||||
private String prompt;
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 平台
|
||||
@ -98,6 +94,16 @@ public class AiMusicDO extends BaseDO {
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private List<String> tags;
|
||||
|
||||
/**
|
||||
* 音乐时长
|
||||
*/
|
||||
private Double duration;
|
||||
|
||||
/**
|
||||
* 是否公开
|
||||
*/
|
||||
private Boolean publicStatus;
|
||||
|
||||
/**
|
||||
* 任务编号
|
||||
*/
|
||||
|
@ -1,6 +1,9 @@
|
||||
package cn.iocoder.yudao.module.ai.dal.mysql.music;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@ -18,4 +21,24 @@ public interface AiMusicMapper extends BaseMapperX<AiMusicDO> {
|
||||
return selectList(AiMusicDO::getStatus, status);
|
||||
}
|
||||
|
||||
default PageResult<AiMusicDO> selectPage(AiMusicPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<AiMusicDO>()
|
||||
.eqIfPresent(AiMusicDO::getUserId, reqVO.getUserId())
|
||||
.eqIfPresent(AiMusicDO::getTitle, reqVO.getTitle())
|
||||
.eqIfPresent(AiMusicDO::getStatus, reqVO.getStatus())
|
||||
.eqIfPresent(AiMusicDO::getGenerateMode, reqVO.getGenerateMode())
|
||||
.betweenIfPresent(AiMusicDO::getCreateTime, reqVO.getCreateTime())
|
||||
.eqIfPresent(AiMusicDO::getPublicStatus, reqVO.getPublicStatus())
|
||||
.orderByDesc(AiMusicDO::getId));
|
||||
}
|
||||
|
||||
default PageResult<AiMusicDO> selectPageByMy(AiMusicPageReqVO reqVO, Long userId) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<AiMusicDO>()
|
||||
// 情况一:公开
|
||||
.eq(Boolean.TRUE.equals(reqVO.getPublicStatus()), AiMusicDO::getPublicStatus, reqVO.getPublicStatus())
|
||||
// 情况二:私有
|
||||
.eq(Boolean.FALSE.equals(reqVO.getPublicStatus()), AiMusicDO::getUserId, userId)
|
||||
.orderByAsc(AiMusicDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,10 +4,8 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
|
||||
import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenOptions;
|
||||
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
|
||||
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoOptions;
|
||||
import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatOptions;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
@ -24,15 +22,17 @@ import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants;
|
||||
import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
|
||||
import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
|
||||
import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
|
||||
import com.alibaba.cloud.ai.tongyi.chat.TongYiChatOptions;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.ChatResponse;
|
||||
import org.springframework.ai.chat.StreamingChatClient;
|
||||
import org.springframework.ai.chat.messages.*;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.StreamingChatModel;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.ollama.api.OllamaOptions;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.qianfan.QianFanChatOptions;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import reactor.core.publisher.Flux;
|
||||
@ -44,8 +44,8 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.AI_CHAT_MESSAGE_NOT_EXIST;
|
||||
import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS;
|
||||
import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_MESSAGE_NOT_EXIST;
|
||||
|
||||
/**
|
||||
* AI 聊天消息 Service 实现类
|
||||
@ -117,7 +117,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
|
||||
List<AiChatMessageDO> historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId());
|
||||
// 1.2 校验模型
|
||||
AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId());
|
||||
StreamingChatClient chatClient = apiKeyService.getStreamingChatClient(model.getKeyId());
|
||||
StreamingChatModel chatClient = apiKeyService.getStreamingChatClient(model.getKeyId());
|
||||
// 1.3 获取用户头像、角色头像
|
||||
AiChatRoleDO role = conversation.getRoleId() != null ? chatRoleService.getChatRole(conversation.getRoleId()) : null;
|
||||
|
||||
@ -150,7 +150,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
|
||||
log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable);
|
||||
chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage()));
|
||||
}).onErrorResume(error -> {
|
||||
return Flux.just(error(ErrorCodeConstants.AI_CHAT_STREAM_ERROR));
|
||||
return Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR));
|
||||
});
|
||||
}
|
||||
|
||||
@ -164,7 +164,14 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
|
||||
}
|
||||
// 1.2 history message 历史消息
|
||||
List<AiChatMessageDO> contextMessages = filterContextMessages(messages, conversation, sendReqVO);
|
||||
contextMessages.forEach(message -> chatMessages.add(new ChatMessage(message.getType().toUpperCase(), message.getContent())));
|
||||
contextMessages.forEach(message -> {
|
||||
// TODO @芋艿:看看有没优化空间
|
||||
if (MessageType.USER.getValue().equals(message.getType())) {
|
||||
chatMessages.add(new UserMessage(message.getContent()));
|
||||
} else {
|
||||
chatMessages.add(new AssistantMessage(message.getContent()));
|
||||
}
|
||||
});
|
||||
// 1.3 user message 新发送消息
|
||||
chatMessages.add(new UserMessage(sendReqVO.getContent()));
|
||||
|
||||
@ -184,14 +191,14 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
|
||||
case OLLAMA:
|
||||
return OllamaOptions.create().withModel(model).withTemperature(temperatureF).withNumPredict(maxTokens);
|
||||
case YI_YAN:
|
||||
// TODO @fan:增加一个 model
|
||||
return new YiYanChatOptions().setTemperature(temperatureF).setMaxOutputTokens(maxTokens);
|
||||
// TODO 芋艿:貌似 model 只要一设置,就报错
|
||||
// return QianFanChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();
|
||||
return QianFanChatOptions.builder().withTemperature(temperatureF).withMaxTokens(maxTokens).build();
|
||||
case XING_HUO:
|
||||
return new XingHuoOptions().setChatModel(XingHuoChatModel.valueOfModel(model)).setTemperature(temperatureF)
|
||||
.setMaxTokens(maxTokens);
|
||||
case QIAN_WEN:
|
||||
// TODO @fan:增加 model、temperature 参数
|
||||
return new QianWenOptions().setMaxTokens(maxTokens);
|
||||
return TongYiChatOptions.builder().withModel(model).withTemperature(temperature).withMaxTokens(maxTokens).build();
|
||||
default:
|
||||
throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
|
||||
}
|
||||
@ -257,7 +264,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
|
||||
// 1. 校验消息存在
|
||||
AiChatMessageDO message = chatMessageMapper.selectById(id);
|
||||
if (message == null || ObjUtil.notEqual(message.getUserId(), userId)) {
|
||||
throw exception(AI_CHAT_MESSAGE_NOT_EXIST);
|
||||
throw exception(CHAT_MESSAGE_NOT_EXIST);
|
||||
}
|
||||
// 2. 执行删除
|
||||
chatMessageMapper.deleteById(id);
|
||||
@ -268,7 +275,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
|
||||
// 1. 校验消息存在
|
||||
List<AiChatMessageDO> messages = chatMessageMapper.selectListByConversationId(conversationId);
|
||||
if (CollUtil.isEmpty(messages) || ObjUtil.notEqual(messages.get(0).getUserId(), userId)) {
|
||||
throw exception(AI_CHAT_MESSAGE_NOT_EXIST);
|
||||
throw exception(CHAT_MESSAGE_NOT_EXIST);
|
||||
}
|
||||
// 2. 执行删除
|
||||
chatMessageMapper.deleteBatchIds(convertList(messages, AiChatMessageDO::getId));
|
||||
@ -279,7 +286,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
|
||||
// 1. 校验消息存在
|
||||
AiChatMessageDO message = chatMessageMapper.selectById(id);
|
||||
if (message == null) {
|
||||
throw exception(AI_CHAT_MESSAGE_NOT_EXIST);
|
||||
throw exception(CHAT_MESSAGE_NOT_EXIST);
|
||||
}
|
||||
// 2. 执行删除
|
||||
chatMessageMapper.deleteById(id);
|
||||
|
@ -5,12 +5,14 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePageReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdatePublicStatusReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI 绘图 Service 接口
|
||||
*
|
||||
@ -35,6 +37,14 @@ public interface AiImageService {
|
||||
*/
|
||||
AiImageDO getImage(Long id);
|
||||
|
||||
/**
|
||||
* 获得绘图列表
|
||||
*
|
||||
* @param ids 绘图编号数组
|
||||
* @return 绘图记录列表
|
||||
*/
|
||||
List<AiImageDO> getImageList(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 绘制图片
|
||||
*
|
||||
@ -61,11 +71,11 @@ public interface AiImageService {
|
||||
PageResult<AiImageDO> getImagePage(AiImagePageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 更新绘画发布状态
|
||||
* 更新绘画
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateImagePublicStatus(@Valid AiImageUpdatePublicStatusReqVO updateReqVO);
|
||||
void updateImage(@Valid AiImageUpdateReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除绘画
|
||||
|
@ -15,7 +15,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePageReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdatePublicStatusReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
|
||||
@ -25,7 +25,7 @@ import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
|
||||
import cn.iocoder.yudao.module.infra.api.file.FileApi;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.image.ImageClient;
|
||||
import org.springframework.ai.image.ImageModel;
|
||||
import org.springframework.ai.image.ImageOptions;
|
||||
import org.springframework.ai.image.ImagePrompt;
|
||||
import org.springframework.ai.image.ImageResponse;
|
||||
@ -35,6 +35,8 @@ import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -61,9 +63,6 @@ public class AiImageServiceImpl implements AiImageService {
|
||||
@Resource
|
||||
private AiApiKeyService apiKeyService;
|
||||
|
||||
@Resource
|
||||
private MidjourneyApi midjourneyApi;
|
||||
|
||||
@Override
|
||||
public PageResult<AiImageDO> getImagePageMy(Long userId, PageParam pageReqVO) {
|
||||
return imageMapper.selectPage(userId, pageReqVO);
|
||||
@ -74,6 +73,14 @@ public class AiImageServiceImpl implements AiImageService {
|
||||
return imageMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AiImageDO> getImageList(List<Long> ids) {
|
||||
if (CollUtil.isEmpty(ids)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return imageMapper.selectBatchIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long drawImage(Long userId, AiImageDrawReqVO drawReqVO) {
|
||||
// 1. 保存数据库
|
||||
@ -91,7 +98,7 @@ public class AiImageServiceImpl implements AiImageService {
|
||||
// 1.1 构建请求
|
||||
ImageOptions request = buildImageOptions(req);
|
||||
// 1.2 执行请求
|
||||
ImageClient imageClient = apiKeyService.getImageClient(AiPlatformEnum.validatePlatform(req.getPlatform()));
|
||||
ImageModel imageClient = apiKeyService.getImageClient(AiPlatformEnum.validatePlatform(req.getPlatform()));
|
||||
ImageResponse response = imageClient.call(new ImagePrompt(req.getPrompt(), request));
|
||||
|
||||
// 2. 上传到文件服务
|
||||
@ -117,9 +124,16 @@ public class AiImageServiceImpl implements AiImageService {
|
||||
.withResponseFormat("b64_json")
|
||||
.build();
|
||||
} else if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.STABLE_DIFFUSION.getPlatform())) {
|
||||
// https://platform.stability.ai/docs/api-reference#tag/SDXL-and-SD1.6/operation/textToImage
|
||||
// https://platform.stability.ai/docs/api-reference#tag/Text-to-Image/operation/textToImage
|
||||
return StabilityAiImageOptions.builder().withModel(draw.getModel())
|
||||
.withHeight(draw.getHeight()).withWidth(draw.getWidth()) // TODO @芋艿:各种参数
|
||||
.withHeight(draw.getHeight()).withWidth(draw.getWidth())
|
||||
.withSeed(Long.valueOf(draw.getOptions().get("seed")))
|
||||
.withCfgScale(Float.valueOf(draw.getOptions().get("scale")))
|
||||
.withSteps(Integer.valueOf(draw.getOptions().get("steps")))
|
||||
.withSampler(String.valueOf(draw.getOptions().get("sampler")))
|
||||
.withStylePreset(String.valueOf(draw.getOptions().get("stylePreset")))
|
||||
.withClipGuidancePreset(String.valueOf(draw.getOptions().get("clipGuidancePreset")))
|
||||
.build();
|
||||
}
|
||||
throw new IllegalArgumentException("不支持的 AI 平台:" + draw.getPlatform());
|
||||
@ -130,7 +144,7 @@ public class AiImageServiceImpl implements AiImageService {
|
||||
// 1. 校验是否存在
|
||||
AiImageDO image = validateImageExists(id);
|
||||
if (ObjUtil.notEqual(image.getUserId(), userId)) {
|
||||
throw exception(AI_IMAGE_NOT_EXISTS);
|
||||
throw exception(IMAGE_NOT_EXISTS);
|
||||
}
|
||||
// 2. 删除记录
|
||||
imageMapper.deleteById(id);
|
||||
@ -142,7 +156,7 @@ public class AiImageServiceImpl implements AiImageService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateImagePublicStatus(AiImageUpdatePublicStatusReqVO updateReqVO) {
|
||||
public void updateImage(AiImageUpdateReqVO updateReqVO) {
|
||||
// 1. 校验存在
|
||||
validateImageExists(updateReqVO.getId());
|
||||
// 2. 更新发布状态
|
||||
@ -160,7 +174,7 @@ public class AiImageServiceImpl implements AiImageService {
|
||||
private AiImageDO validateImageExists(Long id) {
|
||||
AiImageDO image = imageMapper.selectById(id);
|
||||
if (image == null) {
|
||||
throw exception(AI_IMAGE_NOT_EXISTS);
|
||||
throw exception(IMAGE_NOT_EXISTS);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
@ -170,6 +184,7 @@ public class AiImageServiceImpl implements AiImageService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long midjourneyImagine(Long userId, AiMidjourneyImagineReqVO reqVO) {
|
||||
MidjourneyApi midjourneyApi = apiKeyService.getMidjourneyApi();
|
||||
// 1. 保存数据库
|
||||
AiImageDO image = BeanUtils.toBean(reqVO, AiImageDO.class).setUserId(userId).setPublicStatus(false)
|
||||
.setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus())
|
||||
@ -177,16 +192,21 @@ public class AiImageServiceImpl implements AiImageService {
|
||||
imageMapper.insert(image);
|
||||
|
||||
// 2. 调用 Midjourney Proxy 提交任务
|
||||
List<String> base64Array = new ArrayList<>(8);
|
||||
if (StrUtil.isNotBlank(reqVO.getReferImageUrl())) {
|
||||
base64Array.add("data:image/jpeg;base64,".concat(Base64.encode(HttpUtil.downloadBytes(reqVO.getReferImageUrl()))));
|
||||
}
|
||||
MidjourneyApi.ImagineRequest imagineRequest = new MidjourneyApi.ImagineRequest(
|
||||
null, reqVO.getPrompt(),null,
|
||||
MidjourneyApi.ImagineRequest.buildState(reqVO.getWidth(), reqVO.getHeight(), reqVO.getVersion(), reqVO.getModel()));
|
||||
base64Array, reqVO.getPrompt(),null,
|
||||
MidjourneyApi.ImagineRequest.buildState(reqVO.getWidth(),
|
||||
reqVO.getHeight(), reqVO.getVersion(), reqVO.getModel()));
|
||||
MidjourneyApi.SubmitResponse imagineResponse = midjourneyApi.imagine(imagineRequest);
|
||||
|
||||
// 3. 情况一【失败】:抛出业务异常
|
||||
if (!MidjourneyApi.SubmitCodeEnum.SUCCESS_CODES.contains(imagineResponse.code())) {
|
||||
String description = imagineResponse.description().contains("quota_not_enough") ?
|
||||
"账户余额不足" : imagineResponse.description();
|
||||
throw exception(AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL, description);
|
||||
throw exception(IMAGE_MIDJOURNEY_SUBMIT_FAIL, description);
|
||||
}
|
||||
|
||||
// 4. 情况二【成功】:更新 taskId 和参数
|
||||
@ -197,6 +217,7 @@ public class AiImageServiceImpl implements AiImageService {
|
||||
|
||||
@Override
|
||||
public Integer midjourneySync() {
|
||||
MidjourneyApi midjourneyApi = apiKeyService.getMidjourneyApi();
|
||||
// 1.1 获取 Midjourney 平台,状态在 “进行中” 的 image
|
||||
List<AiImageDO> imageList = imageMapper.selectListByStatusAndPlatform(
|
||||
AiImageStatusEnum.IN_PROGRESS.getStatus(), AiPlatformEnum.MIDJOURNEY.getPlatform());
|
||||
@ -263,16 +284,17 @@ public class AiImageServiceImpl implements AiImageService {
|
||||
|
||||
@Override
|
||||
public Long midjourneyAction(Long userId, AiMidjourneyActionReqVO reqVO) {
|
||||
MidjourneyApi midjourneyApi = apiKeyService.getMidjourneyApi();
|
||||
// 1.1 检查 image
|
||||
AiImageDO image = validateImageExists(reqVO.getId());
|
||||
if (ObjUtil.notEqual(userId, image.getUserId())) {
|
||||
throw exception(AI_IMAGE_NOT_EXISTS);
|
||||
throw exception(IMAGE_NOT_EXISTS);
|
||||
}
|
||||
// 1.2 检查 customId
|
||||
MidjourneyApi.Button button = CollUtil.findOne(image.getButtons(),
|
||||
buttonX -> buttonX.customId().equals(reqVO.getCustomId()));
|
||||
if (button == null) {
|
||||
throw exception(AI_IMAGE_CUSTOM_ID_NOT_EXISTS);
|
||||
throw exception(IMAGE_CUSTOM_ID_NOT_EXISTS);
|
||||
}
|
||||
|
||||
// 2. 调用 Midjourney Proxy 提交任务
|
||||
@ -281,7 +303,7 @@ public class AiImageServiceImpl implements AiImageService {
|
||||
if (!MidjourneyApi.SubmitCodeEnum.SUCCESS_CODES.contains(actionResponse.code())) {
|
||||
String description = actionResponse.description().contains("quota_not_enough") ?
|
||||
"账户余额不足" : actionResponse.description();
|
||||
throw exception(AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL, description);
|
||||
throw exception(IMAGE_MIDJOURNEY_SUBMIT_FAIL, description);
|
||||
}
|
||||
|
||||
// 3. 新增 image 记录
|
||||
|
@ -1,58 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.service.image;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.ai.image.ImageOptions;
|
||||
|
||||
/**
|
||||
* @author fansili
|
||||
* @time 2024/6/5 10:34
|
||||
* @since 1.0
|
||||
*/
|
||||
@Data
|
||||
public class MidjourneyImageOptions implements ImageOptions {
|
||||
/**
|
||||
* 模型
|
||||
*/
|
||||
private String model;
|
||||
/**
|
||||
* 宽度
|
||||
*/
|
||||
private Integer width;
|
||||
/**
|
||||
* 高度
|
||||
*/
|
||||
private Integer height;
|
||||
/**
|
||||
* 版本
|
||||
*/
|
||||
private String version;
|
||||
/**
|
||||
* 参数
|
||||
*/
|
||||
private String state;
|
||||
|
||||
@Override
|
||||
public Integer getN() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponseFormat() {
|
||||
return "";
|
||||
}
|
||||
}
|
@ -1,13 +1,15 @@
|
||||
package cn.iocoder.yudao.module.ai.service.model;
|
||||
|
||||
import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
|
||||
import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
|
||||
import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
|
||||
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.AiApiKeySaveReqVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.ai.chat.StreamingChatClient;
|
||||
import org.springframework.ai.image.ImageClient;
|
||||
import org.springframework.ai.chat.model.StreamingChatModel;
|
||||
import org.springframework.ai.image.ImageModel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -79,7 +81,7 @@ public interface AiApiKeyService {
|
||||
* @param id 编号
|
||||
* @return StreamingChatClient 对象
|
||||
*/
|
||||
StreamingChatClient getStreamingChatClient(Long id);
|
||||
StreamingChatModel getStreamingChatClient(Long id);
|
||||
|
||||
/**
|
||||
* 获得 ImageClient 对象
|
||||
@ -89,6 +91,24 @@ public interface AiApiKeyService {
|
||||
* @param platform 平台
|
||||
* @return ImageClient 对象
|
||||
*/
|
||||
ImageClient getImageClient(AiPlatformEnum platform);
|
||||
ImageModel getImageClient(AiPlatformEnum platform);
|
||||
|
||||
/**
|
||||
* 获得 MidjourneyApi 对象
|
||||
*
|
||||
* TODO 可优化点:目前默认获取 Midjourney 对应的第一个开启的配置用于绘画;后续可以支持配置选择
|
||||
*
|
||||
* @return MidjourneyApi 对象
|
||||
*/
|
||||
MidjourneyApi getMidjourneyApi();
|
||||
|
||||
/**
|
||||
* 获得 SunoApi 对象
|
||||
*
|
||||
* TODO 可优化点:目前默认获取 Suno 对应的第一个开启的配置用于音乐;后续可以支持配置选择
|
||||
*
|
||||
* @return SunoApi 对象
|
||||
*/
|
||||
SunoApi getSunoApi();
|
||||
|
||||
}
|
@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.ai.service.model;
|
||||
|
||||
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.model.midjourney.api.MidjourneyApi;
|
||||
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.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
@ -10,8 +12,8 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveR
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
|
||||
import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.ai.chat.StreamingChatClient;
|
||||
import org.springframework.ai.image.ImageClient;
|
||||
import org.springframework.ai.chat.model.StreamingChatModel;
|
||||
import org.springframework.ai.image.ImageModel;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
@ -96,14 +98,14 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
|
||||
// ========== 与 spring-ai 集成 ==========
|
||||
|
||||
@Override
|
||||
public StreamingChatClient getStreamingChatClient(Long id) {
|
||||
public StreamingChatModel getStreamingChatClient(Long id) {
|
||||
AiApiKeyDO apiKey = validateApiKey(id);
|
||||
AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
|
||||
return clientFactory.getOrCreateStreamingChatClient(platform, apiKey.getApiKey(), apiKey.getUrl());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageClient getImageClient(AiPlatformEnum platform) {
|
||||
public ImageModel getImageClient(AiPlatformEnum platform) {
|
||||
AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(platform.getName(), CommonStatusEnum.ENABLE.getStatus());
|
||||
if (apiKey == null) {
|
||||
return null;
|
||||
@ -111,4 +113,25 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
|
||||
return clientFactory.getOrCreateImageClient(platform, apiKey.getApiKey(), apiKey.getUrl());
|
||||
}
|
||||
|
||||
@Override
|
||||
public MidjourneyApi getMidjourneyApi() {
|
||||
AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(
|
||||
AiPlatformEnum.MIDJOURNEY.getPlatform(), CommonStatusEnum.ENABLE.getStatus());
|
||||
// todo @芋艿 这些地方直接抛异常会好点,不然调用到的地方都需要做判断
|
||||
if (apiKey == null) {
|
||||
return null;
|
||||
}
|
||||
return clientFactory.getOrCreateMidjourneyApi(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());
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
package cn.iocoder.yudao.module.ai.service.music;
|
||||
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.music.vo.*;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -15,7 +18,7 @@ public interface AiMusicService {
|
||||
* 音乐生成
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param reqVO 请求参数
|
||||
* @param reqVO 请求参数
|
||||
* @return 生成的音乐ID
|
||||
*/
|
||||
List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO);
|
||||
@ -27,4 +30,58 @@ public interface AiMusicService {
|
||||
*/
|
||||
Integer syncMusic();
|
||||
|
||||
/**
|
||||
* 更新音乐发布状态
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateMusic(@Valid AiMusicUpdateReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 更新我的音乐
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateMyMusic(@Valid AiMusicUpdateReqVO updateReqVO, Long userId);
|
||||
|
||||
/**
|
||||
* 删除AI 音乐
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteMusic(Long id);
|
||||
|
||||
/**
|
||||
* 删除【我的】音乐记录
|
||||
*
|
||||
* @param id 音乐编号
|
||||
* @param userId 用户编号
|
||||
*/
|
||||
void deleteMusicMy(Long id, Long userId);
|
||||
|
||||
/**
|
||||
* 获得AI 音乐
|
||||
*
|
||||
* @param id 音乐编号
|
||||
* @return 音乐内容
|
||||
*/
|
||||
AiMusicDO getMusic(Long id);
|
||||
|
||||
/**
|
||||
* 获得音乐分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 音乐分页
|
||||
*/
|
||||
PageResult<AiMusicDO> getMusicPage(AiMusicPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 获得【我的】音乐分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @param userId 用户编号
|
||||
* @return 音乐分页
|
||||
*/
|
||||
PageResult<AiMusicDO> getMusicMyPage(AiMusicPageReqVO pageReqVO, Long userId);
|
||||
|
||||
}
|
||||
|
@ -2,21 +2,32 @@ package cn.iocoder.yudao.module.ai.service.music;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.text.StrPool;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO;
|
||||
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.enums.music.AiMusicGenerateModeEnum;
|
||||
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 jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.IMAGE_NOT_EXISTS;
|
||||
import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MUSIC_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* AI 音乐 Service 实现类
|
||||
@ -28,25 +39,30 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
|
||||
public class AiMusicServiceImpl implements AiMusicService {
|
||||
|
||||
@Resource
|
||||
private SunoApi sunoApi;
|
||||
private AiApiKeyService apiKeyService;
|
||||
|
||||
@Resource
|
||||
private AiMusicMapper musicMapper;
|
||||
|
||||
@Resource
|
||||
private FileApi fileApi;
|
||||
|
||||
@Override
|
||||
public List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO) {
|
||||
// 1. 调用 Suno 生成音乐
|
||||
SunoApi sunoApi = apiKeyService.getSunoApi();
|
||||
// TODO 芋艿:这两个貌似一直没跑成功,你那可以么?用的请求是 AiMusicController.http 的 --xin:大部分ok的,补充了error_message
|
||||
List<SunoApi.MusicData> musicDataList;
|
||||
if (Objects.equals(AiMusicGenerateModeEnum.LYRIC.getMode(), reqVO.getGenerateMode())) {
|
||||
// 1.1 歌词模式
|
||||
if (Objects.equals(AiMusicGenerateModeEnum.DESCRIPTION.getMode(), reqVO.getGenerateMode())) {
|
||||
// 1.1 描述模式
|
||||
SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest(
|
||||
reqVO.getPrompt(), reqVO.getModelVersion(), CollUtil.join(reqVO.getTags(), StrPool.COMMA), reqVO.getTitle());
|
||||
musicDataList = sunoApi.customGenerate(generateRequest);
|
||||
} else if (Objects.equals(AiMusicGenerateModeEnum.DESCRIPTION.getMode(), reqVO.getGenerateMode())) {
|
||||
// 1.2 描述模式
|
||||
SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest(
|
||||
reqVO.getPrompt(), reqVO.getModelVersion(), reqVO.getMakeInstrumental());
|
||||
reqVO.getPrompt(), reqVO.getModel(), reqVO.getMakeInstrumental());
|
||||
musicDataList = sunoApi.generate(generateRequest);
|
||||
} else if (Objects.equals(AiMusicGenerateModeEnum.LYRIC.getMode(), reqVO.getGenerateMode())) {
|
||||
// 1.2 歌词模式
|
||||
SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest(
|
||||
reqVO.getPrompt(), reqVO.getModel(), CollUtil.join(reqVO.getTags(), StrPool.COMMA), reqVO.getTitle());
|
||||
musicDataList = sunoApi.customGenerate(generateRequest);
|
||||
} else {
|
||||
throw new IllegalArgumentException(StrUtil.format("未知生成模式({})", reqVO));
|
||||
}
|
||||
@ -56,7 +72,7 @@ public class AiMusicServiceImpl implements AiMusicService {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<AiMusicDO> musicList = buildMusicDOList(musicDataList);
|
||||
musicList.forEach(music -> music.setUserId(userId).setPlatform(music.getPlatform()).setGenerateMode(reqVO.getGenerateMode()));
|
||||
musicList.forEach(music -> music.setUserId(userId).setPlatform(reqVO.getPlatform()).setGenerateMode(reqVO.getGenerateMode()));
|
||||
musicMapper.insertBatch(musicList);
|
||||
return convertList(musicList, AiMusicDO::getId);
|
||||
}
|
||||
@ -70,6 +86,7 @@ public class AiMusicServiceImpl implements AiMusicService {
|
||||
log.info("[syncMusic][Suno 开始同步, 共 ({}) 个任务]", streamingTask.size());
|
||||
|
||||
// GET 请求,为避免参数过长,分批次处理
|
||||
SunoApi sunoApi = apiKeyService.getSunoApi();
|
||||
CollUtil.split(streamingTask, 36).forEach(chunkList -> {
|
||||
Map<String, Long> taskIdMap = convertMap(chunkList, AiMusicDO::getTaskId, AiMusicDO::getId);
|
||||
List<SunoApi.MusicData> musicTaskList = sunoApi.getMusicList(new ArrayList<>(taskIdMap.keySet()));
|
||||
@ -85,19 +102,120 @@ public class AiMusicServiceImpl implements AiMusicService {
|
||||
return streamingTask.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMusic(AiMusicUpdateReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateMusicExists(updateReqVO.getId());
|
||||
// 更新
|
||||
musicMapper.updateById(new AiMusicDO().setId(updateReqVO.getId()).setPublicStatus(updateReqVO.getPublicStatus()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMyMusic(AiMusicUpdateReqVO updateReqVO, Long userId) {
|
||||
// 校验音乐是否存在
|
||||
AiMusicDO musicDO = validateMusicExists(updateReqVO.getId());
|
||||
if (ObjUtil.notEqual(musicDO.getUserId(), userId)) {
|
||||
throw exception(MUSIC_NOT_EXISTS);
|
||||
}
|
||||
// 更新
|
||||
musicMapper.updateById(new AiMusicDO().setId(updateReqVO.getId()).setTitle(updateReqVO.getTitle()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteMusic(Long id) {
|
||||
// 校验存在
|
||||
validateMusicExists(id);
|
||||
// 删除
|
||||
musicMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteMusicMy(Long id, Long userId) {
|
||||
// 1. 校验是否存在
|
||||
AiMusicDO music = validateMusicExists(id);
|
||||
if (ObjUtil.notEqual(music.getUserId(), userId)) {
|
||||
throw exception(IMAGE_NOT_EXISTS);
|
||||
}
|
||||
// 2. 删除记录
|
||||
musicMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AiMusicDO getMusic(Long id) {
|
||||
return musicMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<AiMusicDO> getMusicPage(AiMusicPageReqVO pageReqVO) {
|
||||
return musicMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<AiMusicDO> getMusicMyPage(AiMusicPageReqVO pageReqVO, Long userId) {
|
||||
return musicMapper.selectPageByMy(pageReqVO, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 AiMusicDO 集合
|
||||
*
|
||||
* @param musicList suno 音乐任务列表
|
||||
* @return AiMusicDO 集合
|
||||
*/
|
||||
private static List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) {
|
||||
return convertList(musicList, musicData -> new AiMusicDO()
|
||||
.setTaskId(musicData.id()).setModel(musicData.modelName())
|
||||
.setPrompt(musicData.prompt()).setGptDescriptionPrompt(musicData.gptDescriptionPrompt())
|
||||
.setAudioUrl(musicData.audioUrl()).setVideoUrl(musicData.videoUrl()).setImageUrl(musicData.imageUrl())
|
||||
.setTitle(musicData.title()).setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA))
|
||||
.setStatus(Objects.equals("complete", musicData.status()) ? AiMusicStatusEnum.SUCCESS.getStatus() : AiMusicStatusEnum.IN_PROGRESS.getStatus()));
|
||||
|
||||
private List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) {
|
||||
return convertList(musicList, musicData -> {
|
||||
Integer status;
|
||||
if (Objects.equals("complete", musicData.status())) {
|
||||
status = AiMusicStatusEnum.SUCCESS.getStatus();
|
||||
} else if (Objects.equals("error", musicData.status())) {
|
||||
status = AiMusicStatusEnum.FAIL.getStatus();
|
||||
} else {
|
||||
status = AiMusicStatusEnum.IN_PROGRESS.getStatus();
|
||||
}
|
||||
return new AiMusicDO()
|
||||
.setTaskId(musicData.id()).setModel(musicData.modelName())
|
||||
.setDescription(musicData.gptDescriptionPrompt())
|
||||
.setAudioUrl(downloadFile(status, musicData.audioUrl()))
|
||||
.setVideoUrl(downloadFile(status, musicData.videoUrl()))
|
||||
.setImageUrl(downloadFile(status, musicData.imageUrl()))
|
||||
.setTitle(musicData.title()).setDuration(musicData.duration())
|
||||
.setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA))
|
||||
.setErrorMessage(musicData.errorMessage())
|
||||
.setStatus(status);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 音乐生成好后,将音频文件上传到文件服务器
|
||||
*
|
||||
* @param status 音乐状态
|
||||
* @param url 音频文件地址
|
||||
* @return 内部文件地址
|
||||
*/
|
||||
private String downloadFile(Integer status, String url) {
|
||||
if (StrUtil.isBlank(url) || ObjectUtil.notEqual(status, AiMusicStatusEnum.SUCCESS.getStatus())) {
|
||||
return url;
|
||||
}
|
||||
try {
|
||||
byte[] bytes = HttpUtil.downloadBytes(url);
|
||||
return fileApi.createFile(bytes);
|
||||
} catch (Exception e) {
|
||||
log.error("[downloadFile][url({}) 下载失败]", url, e);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验音乐是否存在
|
||||
*
|
||||
* @param id 音乐编号
|
||||
* @return 音乐信息
|
||||
*/
|
||||
private AiMusicDO validateMusicExists(Long id) {
|
||||
AiMusicDO music = musicMapper.selectById(id);
|
||||
if (music == null) {
|
||||
throw exception(MUSIC_NOT_EXISTS);
|
||||
}
|
||||
return music;
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user