Commit 9d6b615f10cc644a8de61a68e74abcc69998d07c

Authored by YunaiV
1 parent 671c3016

【功能新增】AI:音乐管理 50%

Showing 19 changed files with 326 additions and 43 deletions
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java
... ... @@ -29,13 +29,16 @@ public interface ErrorCodeConstants {
29 29  
30 30 // ========== API 聊天消息 1-040-004-000 ==========
31 31  
32   - ErrorCode AI_CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "消息不存在!");
33   - ErrorCode AI_CHAT_STREAM_ERROR = new ErrorCode(1_040_004_001, "Stream 对话异常!");
  32 + ErrorCode CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "消息不存在!");
  33 + ErrorCode CHAT_STREAM_ERROR = new ErrorCode(1_040_004_001, "Stream 对话异常!");
34 34  
35 35 // ========== API 绘画 1-040-005-000 ==========
36 36  
37   - ErrorCode AI_IMAGE_NOT_EXISTS = new ErrorCode(1_022_005_000, "图片不存在!");
38   - ErrorCode AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL = new ErrorCode(1_022_005_001, "Midjourney 提交失败!原因:{}");
39   - ErrorCode AI_IMAGE_CUSTOM_ID_NOT_EXISTS = new ErrorCode(1_022_005_002, "Midjourney 按钮 customId 不存在! {}");
  37 + ErrorCode IMAGE_NOT_EXISTS = new ErrorCode(1_022_005_000, "图片不存在!");
  38 + ErrorCode IMAGE_MIDJOURNEY_SUBMIT_FAIL = new ErrorCode(1_022_005_001, "Midjourney 提交失败!原因:{}");
  39 + ErrorCode IMAGE_CUSTOM_ID_NOT_EXISTS = new ErrorCode(1_022_005_002, "Midjourney 按钮 customId 不存在! {}");
  40 +
  41 + // ========== API 音乐 1-040-006-000 ==========
  42 + ErrorCode MUSIC_NOT_EXISTS = new ErrorCode(1_022_006_000, "音乐不存在!");
40 43  
41 44 }
... ...
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateModeEnum.java
1 1 package cn.iocoder.yudao.module.ai.enums.music;
2 2  
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
3 4 import lombok.AllArgsConstructor;
4 5 import lombok.Getter;
5 6  
  7 +import java.util.Arrays;
  8 +
6 9 /**
7 10 * AI 音乐状态的枚举
8 11 *
... ... @@ -10,7 +13,7 @@ import lombok.Getter;
10 13 */
11 14 @AllArgsConstructor
12 15 @Getter
13   -public enum AiMusicGenerateModeEnum {
  16 +public enum AiMusicGenerateModeEnum implements IntArrayValuable {
14 17  
15 18 LYRIC(1, "歌词模式"),
16 19 DESCRIPTION(2, "描述模式");
... ... @@ -24,4 +27,11 @@ public enum AiMusicGenerateModeEnum {
24 27 */
25 28 private final String name;
26 29  
  30 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiMusicGenerateModeEnum::getMode).toArray();
  31 +
  32 + @Override
  33 + public int[] array() {
  34 + return ARRAYS;
  35 + }
  36 +
27 37 }
... ...
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java
1 1 package cn.iocoder.yudao.module.ai.enums.music;
2 2  
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
3 4 import lombok.AllArgsConstructor;
4 5 import lombok.Getter;
5 6  
  7 +import java.util.Arrays;
  8 +
6 9 /**
7 10 * AI 音乐状态的枚举
8 11 *
... ... @@ -10,7 +13,7 @@ import lombok.Getter;
10 13 */
11 14 @AllArgsConstructor
12 15 @Getter
13   -public enum AiMusicStatusEnum {
  16 +public enum AiMusicStatusEnum implements IntArrayValuable {
14 17  
15 18 IN_PROGRESS(10, "进行中"),
16 19 SUCCESS(20, "已完成");
... ... @@ -25,4 +28,11 @@ public enum AiMusicStatusEnum {
25 28 */
26 29 private final String name;
27 30  
  31 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiMusicStatusEnum::getStatus).toArray();
  32 +
  33 + @Override
  34 + public int[] array() {
  35 + return ARRAYS;
  36 + }
  37 +
28 38 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java
1 1 package cn.iocoder.yudao.module.ai.controller.admin.image;
2 2  
3   -import cn.hutool.core.collection.CollUtil;
4 3 import cn.hutool.core.util.ObjUtil;
5 4 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
6 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
... ... @@ -26,9 +25,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
26 25 import org.springframework.validation.annotation.Validated;
27 26 import org.springframework.web.bind.annotation.*;
28 27  
29   -import java.util.Collections;
30 28 import java.util.List;
31   -import java.util.Objects;
32 29  
33 30 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
34 31 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
... ... @@ -42,15 +39,16 @@ public class AiImageController {
42 39 @Resource
43 40 private AiImageService imageService;
44 41  
45   - @Operation(summary = "获取【我的】绘图分页")
46 42 @GetMapping("/my-page")
  43 + @Operation(summary = "获取【我的】绘图分页")
47 44 public CommonResult<PageResult<AiImageRespVO>> getImagePageMy(@Validated PageParam pageReqVO) {
48 45 PageResult<AiImageDO> pageResult = imageService.getImagePageMy(getLoginUserId(), pageReqVO);
49 46 return success(BeanUtils.toBean(pageResult, AiImageRespVO.class));
50 47 }
51 48  
52   - @Operation(summary = "获取【我的】绘图记录")
53 49 @GetMapping("/get-my")
  50 + @Operation(summary = "获取【我的】绘图记录")
  51 + @Parameter(name = "id", required = true, description = "绘画编号", example = "1024")
54 52 public CommonResult<AiImageRespVO> getImageMy(@RequestParam("id") Long id) {
55 53 AiImageDO image = imageService.getImage(id);
56 54 if (image == null || ObjUtil.notEqual(getLoginUserId(), image.getUserId())) {
... ... @@ -59,17 +57,13 @@ public class AiImageController {
59 57 return success(BeanUtils.toBean(image, AiImageRespVO.class));
60 58 }
61 59  
62   - @Operation(summary = "获取【我的】绘图记录 - ids")
63 60 @GetMapping("/get-my-ids")
  61 + @Operation(summary = "获取【我的】绘图记录列表")
  62 + @Parameter(name = "ids", required = true, description = "绘画编号数组", example = "1024,2048")
64 63 public CommonResult<List<AiImageRespVO>> getImageMyIds(@RequestParam("ids") List<Long> ids) {
65 64 List<AiImageDO> imageList = imageService.getImageByIds(ids);
66   - if (CollUtil.isEmpty(imageList)) {
67   - return success(Collections.emptyList());
68   - }
69   - List<AiImageDO> userImageList = imageList.stream()
70   - .map(item -> ObjUtil.equal(getLoginUserId(), item.getUserId()) ? item : null)
71   - .filter(Objects::nonNull).toList();
72   - return success(BeanUtils.toBean(userImageList, AiImageRespVO.class));
  65 + imageList.removeIf(item -> !ObjUtil.equal(getLoginUserId(), item.getUserId()));
  66 + return success(BeanUtils.toBean(imageList, AiImageRespVO.class));
73 67 }
74 68  
75 69 @Operation(summary = "生成图片")
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImagePageReqVO.java
... ... @@ -20,7 +20,7 @@ public class AiImagePageReqVO extends PageParam {
20 20 @Schema(description = "用户编号", example = "28987")
21 21 private Long userId;
22 22  
23   - @Schema(description = "平台")
  23 + @Schema(description = "平台", example = "OpenAI")
24 24 private String platform;
25 25  
26 26 @Schema(description = "绘画状态", example = "1")
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiImageController.http renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.http
1   -### 生成音乐:Suno +
  1 +### 生成音乐:Suno + 歌词模式
2 2 POST {{baseUrl}}/ai/music/generate
3 3 Content-Type: application/json
4 4 Authorization: {{token}}
... ... @@ -10,4 +10,17 @@ Authorization: {{token}}
10 10 "modelVersion": "chirp-v3.5",
11 11 "tags": ["Happy"],
12 12 "title": "Happy Song"
  13 +}
  14 +
  15 +### 生成音乐:Suno + 描述模式
  16 +POST {{baseUrl}}/ai/music/generate
  17 +Content-Type: application/json
  18 +Authorization: {{token}}
  19 +
  20 +{
  21 + "platform": "Suno",
  22 + "generateMode": 2,
  23 + "prompt": "来一首快乐的歌曲",
  24 + "makeInstrumental": false,
  25 + "title": "Happy Song"
13 26 }
14 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
1 1 package cn.iocoder.yudao.module.ai.controller.admin.music;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicRespVO;
  8 +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicUpdatePublicStatusReqVO;
4 9 import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO;
  10 +import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO;
5 11 import cn.iocoder.yudao.module.ai.service.music.AiMusicService;
6 12 import io.swagger.v3.oas.annotations.Operation;
  13 +import io.swagger.v3.oas.annotations.Parameter;
7 14 import io.swagger.v3.oas.annotations.tags.Tag;
8 15 import jakarta.annotation.Resource;
9 16 import jakarta.validation.Valid;
10   -import org.springframework.web.bind.annotation.PostMapping;
11   -import org.springframework.web.bind.annotation.RequestBody;
12   -import org.springframework.web.bind.annotation.RequestMapping;
13   -import org.springframework.web.bind.annotation.RestController;
  17 +import org.springframework.security.access.prepost.PreAuthorize;
  18 +import org.springframework.web.bind.annotation.*;
14 19  
15 20 import java.util.List;
16 21  
... ... @@ -31,4 +36,31 @@ public class AiMusicController {
31 36 return success(musicService.generateMusic(getLoginUserId(), reqVO));
32 37 }
33 38  
  39 + // ================ 绘图管理 ================
  40 +
  41 + @GetMapping("/page")
  42 + @Operation(summary = "获得音乐分页")
  43 + @PreAuthorize("@ss.hasPermission('ai:music:query')")
  44 + public CommonResult<PageResult<AiMusicRespVO>> getMusicPage(@Valid AiMusicPageReqVO pageReqVO) {
  45 + PageResult<AiMusicDO> pageResult = musicService.getMusicPage(pageReqVO);
  46 + return success(BeanUtils.toBean(pageResult, AiMusicRespVO.class));
  47 + }
  48 +
  49 + @DeleteMapping("/delete")
  50 + @Operation(summary = "删除音乐")
  51 + @Parameter(name = "id", description = "编号", required = true)
  52 + @PreAuthorize("@ss.hasPermission('ai:music:delete')")
  53 + public CommonResult<Boolean> deleteMusic(@RequestParam("id") Long id) {
  54 + musicService.deleteMusic(id);
  55 + return success(true);
  56 + }
  57 +
  58 + @PutMapping("/update")
  59 + @Operation(summary = "更新音乐发布状态")
  60 + @PreAuthorize("@ss.hasPermission('ai:music:update')")
  61 + public CommonResult<Boolean> updateMusicPublicStatus(@Valid @RequestBody AiMusicUpdatePublicStatusReqVO updateReqVO) {
  62 + musicService.updateMusicPublicStatus(updateReqVO);
  63 + return success(true);
  64 + }
  65 +
34 66 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.music.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum;
  6 +import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum;
  7 +import io.swagger.v3.oas.annotations.media.Schema;
  8 +import lombok.Data;
  9 +import lombok.EqualsAndHashCode;
  10 +import lombok.ToString;
  11 +import org.springframework.format.annotation.DateTimeFormat;
  12 +
  13 +import java.time.LocalDateTime;
  14 +
  15 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  16 +
  17 +@Schema(description = "管理后台 - AI 音乐分页 Request VO")
  18 +@Data
  19 +@EqualsAndHashCode(callSuper = true)
  20 +@ToString(callSuper = true)
  21 +public class AiMusicPageReqVO extends PageParam {
  22 +
  23 + @Schema(description = "用户编号", example = "12212")
  24 + private Long userId;
  25 +
  26 + @Schema(description = "音乐名称", example = "夜空中最亮的星")
  27 + private String title;
  28 +
  29 + @Schema(description = "音乐状态", example = "20")
  30 + @InEnum(AiMusicStatusEnum.class)
  31 + private Integer status;
  32 +
  33 + @Schema(description = "生成模式", example = "1")
  34 + @InEnum(AiMusicGenerateModeEnum.class)
  35 + private Integer generateMode;
  36 +
  37 + @Schema(description = "是否发布", example = "true")
  38 + private Boolean publicStatus;
  39 +
  40 + @Schema(description = "创建时间")
  41 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  42 + private LocalDateTime[] createTime;
  43 +
  44 +}
0 45 \ 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/AiMusicRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.music.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +import java.util.List;
  8 +
  9 +@Schema(description = "管理后台 - AI 音乐 Response VO")
  10 +@Data
  11 +public class AiMusicRespVO {
  12 +
  13 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
  14 + private Long id;
  15 +
  16 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12212")
  17 + private Long userId;
  18 +
  19 + @Schema(description = "音乐名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "夜空中最亮的星")
  20 + private String title;
  21 +
  22 + @Schema(description = "歌词", example = "oh~卖糕的")
  23 + private String lyric;
  24 +
  25 + @Schema(description = "图片地址", example = "https://www.iocoder.cn")
  26 + private String imageUrl;
  27 +
  28 + @Schema(description = "音频地址", example = "https://www.iocoder.cn")
  29 + private String audioUrl;
  30 +
  31 + @Schema(description = "视频地址", example = "https://www.iocoder.cn")
  32 + private String videoUrl;
  33 +
  34 + @Schema(description = "音乐状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
  35 + private Integer status;
  36 +
  37 + @Schema(description = "描述词", example = "一首轻快的歌曲")
  38 + private String gptDescriptionPrompt;
  39 +
  40 + @Schema(description = "提示词", example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。")
  41 + private String prompt;
  42 +
  43 + @Schema(description = "模型平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "Suno")
  44 + private String platform;
  45 +
  46 + @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5")
  47 + private String model;
  48 +
  49 + @Schema(description = "生成模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  50 + private Integer generateMode;
  51 +
  52 + @Schema(description = "音乐风格标签")
  53 + private List<String> tags;
  54 +
  55 + @Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
  56 + private Boolean publicStatus;
  57 +
  58 + @Schema(description = "任务编号", example = "11369")
  59 + private String taskId;
  60 +
  61 + @Schema(description = "错误信息")
  62 + private String errorMessage;
  63 +
  64 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  65 + private LocalDateTime createTime;
  66 +
  67 +}
0 68 \ 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/AiMusicUpdatePublicStatusReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.music.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - AI 音乐修改发布状态 Request VO")
  8 +@Data
  9 +public class AiMusicUpdatePublicStatusReqVO {
  10 +
  11 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
  12 + private Long id;
  13 +
  14 + @Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
  15 + @NotNull(message = "是否发布不能为空")
  16 + private Boolean publicStatus;
  17 +
  18 +}
0 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
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.music.vo;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import jakarta.validation.constraints.NotBlank;
  5 +import jakarta.validation.constraints.NotEmpty;
5 6 import jakarta.validation.constraints.NotNull;
6 7 import lombok.Data;
7 8  
... ... @@ -15,6 +16,10 @@ public class AiSunoGenerateReqVO {
15 16 @NotBlank(message = "平台不能为空")
16 17 private String platform; // 参见 AiPlatformEnum 枚举
17 18  
  19 + /**
  20 + * 1. 描述模式:描述词 + 是否纯音乐 + 模型 TODO @xin:目前貌似描述词没弄对?看着不是 prompt 字段(也可能我弄错了)。可以微信再沟通下哈
  21 + * 2. 歌词模式:歌词 + 音乐风格 + 标题 + 模型 TODO @xin:目前这块少传递了标题;
  22 + */
18 23 @Schema(description = "生成模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
19 24 @NotNull(message = "生成模式不能为空")
20 25 private Integer generateMode; // 参见 AiMusicGenerateModeEnum 枚举
... ... @@ -26,7 +31,9 @@ public class AiSunoGenerateReqVO {
26 31 @Schema(description = "是否纯音乐", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "true")
27 32 private Boolean makeInstrumental;
28 33  
29   - @Schema(description = "模型版本", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "chirp-v3.5")
  34 + // TODO @xin:看了下这个字段,发现最终还是 model 合适点;因为它其实是模型
  35 + @Schema(description = "模型版本", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5")
  36 + @NotEmpty(message = "模型不能为空")
30 37 private String modelVersion; // 参见 AiModelEnum 枚举
31 38  
32 39 @Schema(description = "音乐风格", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "[\"pop\",\"jazz\",\"punk\"]")
... ...
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 99 private List<String> tags;
100 100  
101 101 /**
  102 + * 是否公开
  103 + */
  104 + private Boolean publicStatus;
  105 +
  106 + /**
102 107 * 任务编号
103 108 */
104 109 private String taskId;
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/music/AiMusicMapper.java
1 1 package cn.iocoder.yudao.module.ai.dal.mysql.music;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
3 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO;
4 7 import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO;
5 8 import org.apache.ibatis.annotations.Mapper;
6 9  
... ... @@ -18,4 +21,15 @@ public interface AiMusicMapper extends BaseMapperX&lt;AiMusicDO&gt; {
18 21 return selectList(AiMusicDO::getStatus, status);
19 22 }
20 23  
  24 + default PageResult<AiMusicDO> selectPage(AiMusicPageReqVO reqVO) {
  25 + return selectPage(reqVO, new LambdaQueryWrapperX<AiMusicDO>()
  26 + .eqIfPresent(AiMusicDO::getUserId, reqVO.getUserId())
  27 + .eqIfPresent(AiMusicDO::getTitle, reqVO.getTitle())
  28 + .eqIfPresent(AiMusicDO::getStatus, reqVO.getStatus())
  29 + .eqIfPresent(AiMusicDO::getGenerateMode, reqVO.getGenerateMode())
  30 + .betweenIfPresent(AiMusicDO::getCreateTime, reqVO.getCreateTime())
  31 + .eqIfPresent(AiMusicDO::getPublicStatus, reqVO.getPublicStatus())
  32 + .orderByDesc(AiMusicDO::getId));
  33 + }
  34 +
21 35 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
... ... @@ -44,7 +44,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
44 44 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
45 45 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
46 46 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
47   -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.AI_CHAT_MESSAGE_NOT_EXIST;
  47 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_MESSAGE_NOT_EXIST;
48 48 import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS;
49 49  
50 50 /**
... ... @@ -150,7 +150,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
150 150 log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable);
151 151 chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage()));
152 152 }).onErrorResume(error -> {
153   - return Flux.just(error(ErrorCodeConstants.AI_CHAT_STREAM_ERROR));
  153 + return Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR));
154 154 });
155 155 }
156 156  
... ... @@ -257,7 +257,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
257 257 // 1. 校验消息存在
258 258 AiChatMessageDO message = chatMessageMapper.selectById(id);
259 259 if (message == null || ObjUtil.notEqual(message.getUserId(), userId)) {
260   - throw exception(AI_CHAT_MESSAGE_NOT_EXIST);
  260 + throw exception(CHAT_MESSAGE_NOT_EXIST);
261 261 }
262 262 // 2. 执行删除
263 263 chatMessageMapper.deleteById(id);
... ... @@ -268,7 +268,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
268 268 // 1. 校验消息存在
269 269 List<AiChatMessageDO> messages = chatMessageMapper.selectListByConversationId(conversationId);
270 270 if (CollUtil.isEmpty(messages) || ObjUtil.notEqual(messages.get(0).getUserId(), userId)) {
271   - throw exception(AI_CHAT_MESSAGE_NOT_EXIST);
  271 + throw exception(CHAT_MESSAGE_NOT_EXIST);
272 272 }
273 273 // 2. 执行删除
274 274 chatMessageMapper.deleteBatchIds(convertList(messages, AiChatMessageDO::getId));
... ... @@ -279,7 +279,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
279 279 // 1. 校验消息存在
280 280 AiChatMessageDO message = chatMessageMapper.selectById(id);
281 281 if (message == null) {
282   - throw exception(AI_CHAT_MESSAGE_NOT_EXIST);
  282 + throw exception(CHAT_MESSAGE_NOT_EXIST);
283 283 }
284 284 // 2. 执行删除
285 285 chatMessageMapper.deleteById(id);
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
... ... @@ -35,6 +35,7 @@ import org.springframework.scheduling.annotation.Async;
35 35 import org.springframework.stereotype.Service;
36 36 import org.springframework.transaction.annotation.Transactional;
37 37  
  38 +import java.util.Collections;
38 39 import java.util.List;
39 40 import java.util.Map;
40 41  
... ... @@ -76,6 +77,9 @@ public class AiImageServiceImpl implements AiImageService {
76 77  
77 78 @Override
78 79 public List<AiImageDO> getImageByIds(List<Long> ids) {
  80 + if (CollUtil.isEmpty(ids)) {
  81 + return Collections.emptyList();
  82 + }
79 83 return imageMapper.selectBatchIds(ids);
80 84 }
81 85  
... ... @@ -135,7 +139,7 @@ public class AiImageServiceImpl implements AiImageService {
135 139 // 1. 校验是否存在
136 140 AiImageDO image = validateImageExists(id);
137 141 if (ObjUtil.notEqual(image.getUserId(), userId)) {
138   - throw exception(AI_IMAGE_NOT_EXISTS);
  142 + throw exception(IMAGE_NOT_EXISTS);
139 143 }
140 144 // 2. 删除记录
141 145 imageMapper.deleteById(id);
... ... @@ -165,7 +169,7 @@ public class AiImageServiceImpl implements AiImageService {
165 169 private AiImageDO validateImageExists(Long id) {
166 170 AiImageDO image = imageMapper.selectById(id);
167 171 if (image == null) {
168   - throw exception(AI_IMAGE_NOT_EXISTS);
  172 + throw exception(IMAGE_NOT_EXISTS);
169 173 }
170 174 return image;
171 175 }
... ... @@ -191,7 +195,7 @@ public class AiImageServiceImpl implements AiImageService {
191 195 if (!MidjourneyApi.SubmitCodeEnum.SUCCESS_CODES.contains(imagineResponse.code())) {
192 196 String description = imagineResponse.description().contains("quota_not_enough") ?
193 197 "账户余额不足" : imagineResponse.description();
194   - throw exception(AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL, description);
  198 + throw exception(IMAGE_MIDJOURNEY_SUBMIT_FAIL, description);
195 199 }
196 200  
197 201 // 4. 情况二【成功】:更新 taskId 和参数
... ... @@ -271,13 +275,13 @@ public class AiImageServiceImpl implements AiImageService {
271 275 // 1.1 检查 image
272 276 AiImageDO image = validateImageExists(reqVO.getId());
273 277 if (ObjUtil.notEqual(userId, image.getUserId())) {
274   - throw exception(AI_IMAGE_NOT_EXISTS);
  278 + throw exception(IMAGE_NOT_EXISTS);
275 279 }
276 280 // 1.2 检查 customId
277 281 MidjourneyApi.Button button = CollUtil.findOne(image.getButtons(),
278 282 buttonX -> buttonX.customId().equals(reqVO.getCustomId()));
279 283 if (button == null) {
280   - throw exception(AI_IMAGE_CUSTOM_ID_NOT_EXISTS);
  284 + throw exception(IMAGE_CUSTOM_ID_NOT_EXISTS);
281 285 }
282 286  
283 287 // 2. 调用 Midjourney Proxy 提交任务
... ... @@ -286,7 +290,7 @@ public class AiImageServiceImpl implements AiImageService {
286 290 if (!MidjourneyApi.SubmitCodeEnum.SUCCESS_CODES.contains(actionResponse.code())) {
287 291 String description = actionResponse.description().contains("quota_not_enough") ?
288 292 "账户余额不足" : actionResponse.description();
289   - throw exception(AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL, description);
  293 + throw exception(IMAGE_MIDJOURNEY_SUBMIT_FAIL, description);
290 294 }
291 295  
292 296 // 3. 新增 image 记录
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java
1 1 package cn.iocoder.yudao.module.ai.service.music;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO;
  5 +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicUpdatePublicStatusReqVO;
3 6 import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO;
  7 +import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO;
  8 +import jakarta.validation.Valid;
4 9  
5 10 import java.util.List;
6 11  
... ... @@ -27,4 +32,26 @@ public interface AiMusicService {
27 32 */
28 33 Integer syncMusic();
29 34  
  35 + /**
  36 + * 更新音乐发布状态
  37 + *
  38 + * @param updateReqVO 更新信息
  39 + */
  40 + void updateMusicPublicStatus(@Valid AiMusicUpdatePublicStatusReqVO updateReqVO);
  41 +
  42 + /**
  43 + * 删除AI 音乐
  44 + *
  45 + * @param id 编号
  46 + */
  47 + void deleteMusic(Long id);
  48 +
  49 + /**
  50 + * 获得音乐分页
  51 + *
  52 + * @param pageReqVO 分页查询
  53 + * @return 音乐分页
  54 + */
  55 + PageResult<AiMusicDO> getMusicPage(AiMusicPageReqVO pageReqVO);
  56 +
30 57 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java
... ... @@ -4,6 +4,9 @@ import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.text.StrPool;
5 5 import cn.hutool.core.util.StrUtil;
6 6 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
  7 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  8 +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO;
  9 +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicUpdatePublicStatusReqVO;
7 10 import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO;
8 11 import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO;
9 12 import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper;
... ... @@ -15,8 +18,10 @@ import org.springframework.stereotype.Service;
15 18  
16 19 import java.util.*;
17 20  
  21 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
18 22 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
19 23 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  24 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MUSIC_NOT_EXISTS;
20 25  
21 26 /**
22 27 * AI 音乐 Service 实现类
... ... @@ -56,7 +61,7 @@ public class AiMusicServiceImpl implements AiMusicService {
56 61 return Collections.emptyList();
57 62 }
58 63 List<AiMusicDO> musicList = buildMusicDOList(musicDataList);
59   - musicList.forEach(music -> music.setUserId(userId).setPlatform(music.getPlatform()).setGenerateMode(reqVO.getGenerateMode()));
  64 + musicList.forEach(music -> music.setUserId(userId).setPlatform(reqVO.getPlatform()).setGenerateMode(reqVO.getGenerateMode()));
60 65 musicMapper.insertBatch(musicList);
61 66 return convertList(musicList, AiMusicDO::getId);
62 67 }
... ... @@ -92,12 +97,41 @@ public class AiMusicServiceImpl implements AiMusicService {
92 97 * @return AiMusicDO 集合
93 98 */
94 99 private static List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) {
  100 + // TODO @xin:成功的情况下,需要下载到自己的文件服务器。参考图片的处理
95 101 return convertList(musicList, musicData -> new AiMusicDO()
96 102 .setTaskId(musicData.id()).setModel(musicData.modelName())
97 103 .setPrompt(musicData.prompt()).setGptDescriptionPrompt(musicData.gptDescriptionPrompt())
98 104 .setAudioUrl(musicData.audioUrl()).setVideoUrl(musicData.videoUrl()).setImageUrl(musicData.imageUrl())
99 105 .setTitle(musicData.title()).setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA))
100   - .setStatus(Objects.equals("complete", musicData.status()) ? AiMusicStatusEnum.SUCCESS.getStatus() : AiMusicStatusEnum.IN_PROGRESS.getStatus()));
  106 + .setStatus(Objects.equals("complete", musicData.status()) ?
  107 + AiMusicStatusEnum.SUCCESS.getStatus() : AiMusicStatusEnum.IN_PROGRESS.getStatus()));
  108 + }
  109 +
  110 + @Override
  111 + public void updateMusicPublicStatus(AiMusicUpdatePublicStatusReqVO updateReqVO) {
  112 + // 校验存在
  113 + validateMusicExists(updateReqVO.getId());
  114 + // 更新
  115 + musicMapper.updateBatch(new AiMusicDO().setPublicStatus(updateReqVO.getPublicStatus()));
  116 + }
  117 +
  118 + @Override
  119 + public void deleteMusic(Long id) {
  120 + // 校验存在
  121 + validateMusicExists(id);
  122 + // 删除
  123 + musicMapper.deleteById(id);
  124 + }
  125 +
  126 + private void validateMusicExists(Long id) {
  127 + if (musicMapper.selectById(id) == null) {
  128 + throw exception(MUSIC_NOT_EXISTS);
  129 + }
  130 + }
101 131  
  132 + @Override
  133 + public PageResult<AiMusicDO> getMusicPage(AiMusicPageReqVO pageReqVO) {
  134 + return musicMapper.selectPage(pageReqVO);
102 135 }
  136 +
103 137 }
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/music/SunoTests.java
... ... @@ -17,7 +17,8 @@ public class SunoTests {
17 17  
18 18 @Before
19 19 public void setup() {
20   - String url = "https://suno-om0w1cy6e-status2xxs-projects.vercel.app";
  20 +// String url = "https://suno-om0w1cy6e-status2xxs-projects.vercel.app";
  21 + String url = "http://127.0.0.1:3001";
21 22 this.sunoApi = new SunoApi(url);
22 23 }
23 24  
... ... @@ -53,5 +54,4 @@ public class SunoTests {
53 54 System.out.println(limitUsageData);
54 55 }
55 56  
56   -
57 57 }
... ...
yudao-server/src/main/resources/application.yaml
... ... @@ -201,7 +201,8 @@ yudao.ai:
201 201 notify-url: http://java.nat300.top/admin-api/ai/image/midjourney/notify
202 202 suno:
203 203 enable: true
204   - base-url: https://suno-om0w1cy6e-status2xxs-projects.vercel.app
  204 +# base-url: https://suno-om0w1cy6e-status2xxs-projects.vercel.app
  205 + base-url: http://127.0.0.1:3001
205 206  
206 207 --- #################### 芋道相关配置 ####################
207 208  
... ...