Przeglądaj źródła

1、共享租户、补充租户权限逻辑
2、优化试题答案功能
3、加入图生图
4、加入图生视频、文生视频

liyanbo 7 miesięcy temu
rodzic
commit
d34722b9d5
53 zmienionych plików z 1090 dodań i 11 usunięć
  1. 1 1
      byzs-course/src/main/java/cn/iocoder/byzs/module/bjdx/controller/admin/coursequestoption/vo/CourseQuestOptionSaveReqVO.java
  2. 1 1
      byzs-course/src/main/java/cn/iocoder/byzs/module/bjdx/dal/dataobject/coursequestion/CourseQuestionDO.java
  3. 13 0
      byzs-course/src/main/java/cn/iocoder/byzs/module/bjdx/service/coursequestion/CourseQuestionServiceImpl.java
  4. 32 0
      byzs-course/src/main/java/cn/iocoder/byzs/module/bjdx/service/coursequestoption/CourseQuestOptionServiceImpl.java
  5. 1 1
      byzs-module-ai/pom.xml
  6. 6 2
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/image/vo/AiImageDrawReqVO.java
  7. 4 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/image/vo/AiImagePublicPageReqVO.java
  8. 3 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/image/vo/AiImageRespVO.java
  9. 3 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentRespVO.java
  10. 3 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeRespVO.java
  11. 3 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentRespVO.java
  12. 3 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/mindmap/vo/AiMindMapRespVO.java
  13. 3 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/model/vo/apikey/AiApiKeyRespVO.java
  14. 3 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/model/vo/chatRole/AiChatRoleRespVO.java
  15. 3 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/model/vo/model/AiModelRespVO.java
  16. 3 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/model/vo/tool/AiToolRespVO.java
  17. 3 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/music/vo/AiMusicRespVO.java
  18. 3 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/tts/vo/AiTtsRespVO.java
  19. 109 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/video/AiVideoController.java
  20. 47 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/video/vo/AiVideoDrawReqVO.java
  21. 38 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/video/vo/AiVideoPageReqVO.java
  22. 17 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/video/vo/AiVideoPublicPageReqVO.java
  23. 61 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/video/vo/AiVideoRespVO.java
  24. 18 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/video/vo/AiVideoUpdateReqVO.java
  25. 3 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/workflow/vo/AiWorkflowRespVO.java
  26. 3 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/write/vo/AiWriteRespVO.java
  27. 5 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/chat/AiChatConversationDO.java
  28. 5 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/chat/AiChatMessageDO.java
  29. 10 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/image/AiImageDO.java
  30. 5 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java
  31. 5 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java
  32. 5 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java
  33. 5 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/mindmap/AiMindMapDO.java
  34. 6 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/model/AiApiKeyDO.java
  35. 5 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/model/AiChatRoleDO.java
  36. 5 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/model/AiModelDO.java
  37. 5 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/model/AiToolDO.java
  38. 5 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/music/AiMusicDO.java
  39. 5 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/tts/AiTtsDO.java
  40. 131 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/video/AiVideoDO.java
  41. 5 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/workflow/AiWorkflowDO.java
  42. 5 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/write/AiWriteDO.java
  43. 57 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/mysql/video/AiVideoMapper.java
  44. 3 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/enums/ErrorCodeConstants.java
  45. 3 1
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/enums/model/AiModelTypeEnum.java
  46. 37 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/enums/video/AiVideoStatusEnum.java
  47. 6 1
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/service/image/AiImageServiceImpl.java
  48. 92 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/service/video/AiVideoService.java
  49. 265 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/service/video/AiVideoServiceImpl.java
  50. BIN
      byzs-module-infra/src/main/resources/file/erweima.jpg
  51. 3 3
      byzs-server/src/main/resources/application.yaml
  52. 1 1
      byzs-server/src/main/resources/logback-spring.xml
  53. 24 0
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/ai/WebAiController.java

+ 1 - 1
byzs-course/src/main/java/cn/iocoder/byzs/module/bjdx/controller/admin/coursequestoption/vo/CourseQuestOptionSaveReqVO.java

@@ -13,7 +13,7 @@ public class CourseQuestOptionSaveReqVO {
     private Long id;
 
     @Schema(description = "试题id")
-    private Integer cqoQuestId;
+    private Long cqoQuestId;
 
     @Schema(description = "选项")
     private String cqoValue;

+ 1 - 1
byzs-course/src/main/java/cn/iocoder/byzs/module/bjdx/dal/dataobject/coursequestion/CourseQuestionDO.java

@@ -45,7 +45,7 @@ public class CourseQuestionDO extends BaseDO {
     /**
      * 试题答案id
      */
-    private Integer cqQuestAnswerId;
+    private Long cqQuestAnswerId;
     /**
      * 试题分数
      */

+ 13 - 0
byzs-course/src/main/java/cn/iocoder/byzs/module/bjdx/service/coursequestion/CourseQuestionServiceImpl.java

@@ -3,6 +3,8 @@ package cn.iocoder.byzs.module.bjdx.service.coursequestion;
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.byzs.module.bjdx.controller.admin.coursequestion.vo.CourseQuestionPageReqVO;
 import cn.iocoder.byzs.module.bjdx.controller.admin.coursequestion.vo.CourseQuestionSaveReqVO;
+import cn.iocoder.byzs.module.bjdx.dal.dataobject.coursequestoption.CourseQuestOptionDO;
+import cn.iocoder.byzs.module.bjdx.dal.mysql.coursequestoption.CourseQuestOptionMapper;
 import org.springframework.stereotype.Service;
 import jakarta.annotation.Resource;
 import org.springframework.validation.annotation.Validated;
@@ -30,6 +32,8 @@ public class CourseQuestionServiceImpl implements CourseQuestionService {
 
     @Resource
     private CourseQuestionMapper courseQuestionMapper;
+    @Resource
+    private CourseQuestOptionMapper courseQuestOptionMapper;
 
     @Override
     public Long createCourseQuestion(CourseQuestionSaveReqVO createReqVO) {
@@ -46,6 +50,15 @@ public class CourseQuestionServiceImpl implements CourseQuestionService {
         validateCourseQuestionExists(updateReqVO.getId());
         // 更新
         CourseQuestionDO updateObj = BeanUtils.toBean(updateReqVO, CourseQuestionDO.class);
+
+        if (updateObj.getId() != null) {
+            List<CourseQuestOptionDO> courseQuestOptionDOS = courseQuestOptionMapper.selectListByQuestId(Collections.singleton(updateObj.getId()));
+            courseQuestOptionDOS.forEach(item -> {
+                item.setCqoIsAnswer(Objects.equals(item.getId(), updateObj.getCqQuestAnswerId()) ? "true" : "false");
+                courseQuestOptionMapper.updateById(item);
+            });
+        }
+
         courseQuestionMapper.updateById(updateObj);
     }
 

+ 32 - 0
byzs-course/src/main/java/cn/iocoder/byzs/module/bjdx/service/coursequestoption/CourseQuestOptionServiceImpl.java

@@ -3,11 +3,15 @@ package cn.iocoder.byzs.module.bjdx.service.coursequestoption;
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.byzs.module.bjdx.controller.admin.coursequestoption.vo.CourseQuestOptionPageReqVO;
 import cn.iocoder.byzs.module.bjdx.controller.admin.coursequestoption.vo.CourseQuestOptionSaveReqVO;
+import cn.iocoder.byzs.module.bjdx.dal.dataobject.coursequestion.CourseQuestionDO;
+import cn.iocoder.byzs.module.bjdx.dal.mysql.coursequestion.CourseQuestionMapper;
 import org.springframework.stereotype.Service;
 import jakarta.annotation.Resource;
 import org.springframework.validation.annotation.Validated;
 
 import java.util.*;
+import java.util.stream.Collectors;
+
 import cn.iocoder.byzs.module.bjdx.controller.admin.coursequestoption.vo.*;
 import cn.iocoder.byzs.module.bjdx.dal.dataobject.coursequestoption.CourseQuestOptionDO;
 import cn.iocoder.byzs.framework.common.pojo.PageResult;
@@ -28,6 +32,8 @@ import static cn.iocoder.byzs.module.bjdx.enums.ErrorCodeConstants.*;
 @Validated
 public class CourseQuestOptionServiceImpl implements CourseQuestOptionService {
 
+    @Resource
+    private CourseQuestionMapper courseQuestionMapper;
     @Resource
     private CourseQuestOptionMapper courseQuestOptionMapper;
 
@@ -36,6 +42,19 @@ public class CourseQuestOptionServiceImpl implements CourseQuestOptionService {
         // 插入
         CourseQuestOptionDO courseQuestOption = BeanUtils.toBean(createReqVO, CourseQuestOptionDO.class);
         courseQuestOptionMapper.insert(courseQuestOption);
+
+        boolean isAnswer = createReqVO.getCqoIsAnswer().equals("true");
+        if (isAnswer){
+            List<CourseQuestOptionDO> list = courseQuestOptionMapper.selectListByQuestId(Collections.singleton(createReqVO.getCqoQuestId()));
+            list.forEach(item -> {
+                if (!item.getId().equals(courseQuestOption.getId())){
+                    item.setCqoIsAnswer("false");
+                    courseQuestOptionMapper.updateById(item);
+                }
+            });
+
+            courseQuestionMapper.updateById(new CourseQuestionDO().setId(courseQuestOption.getCqoQuestId()).setCqQuestAnswerId(courseQuestOption.getId()));
+        }
         // 返回
         return courseQuestOption.getId();
     }
@@ -46,6 +65,19 @@ public class CourseQuestOptionServiceImpl implements CourseQuestOptionService {
         validateCourseQuestOptionExists(updateReqVO.getId());
         // 更新
         CourseQuestOptionDO updateObj = BeanUtils.toBean(updateReqVO, CourseQuestOptionDO.class);
+
+        boolean isAnswer = updateReqVO.getCqoIsAnswer().equals("true");
+        if (isAnswer){
+            List<CourseQuestOptionDO> list = courseQuestOptionMapper.selectListByQuestId(Collections.singleton(updateReqVO.getCqoQuestId()));
+            list.forEach(item -> {
+                if (!item.getId().equals(updateReqVO.getId())){
+                    item.setCqoIsAnswer("false");
+                    courseQuestOptionMapper.updateById(item);
+                }
+            });
+
+            courseQuestionMapper.updateById(new CourseQuestionDO().setId(updateReqVO.getCqoQuestId()).setCqQuestAnswerId(updateReqVO.getId()));
+        }
         courseQuestOptionMapper.updateById(updateObj);
     }
 

+ 1 - 1
byzs-module-ai/pom.xml

@@ -122,7 +122,7 @@
             <version>${spring-ai.version}</version>
         </dependency>
 
-        <!-- 豆包 文生图 -->
+        <!-- 豆包  -->
         <!-- 火山引擎豆包 SDK(取消注释并添加) -->
         <dependency>
             <groupId>com.volcengine</groupId>

+ 6 - 2
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/image/vo/AiImageDrawReqVO.java

@@ -23,16 +23,20 @@ public class AiImageDrawReqVO {
     @Size(max = 1200, message = "提示词最大 1200")
     private String prompt;
 
+    @Schema(description = "参考图片", requiredMode = Schema.RequiredMode.REQUIRED, example = "画一个长城")
+//    @NotEmpty(message = "参考图片不能为空")
+    private String promptImage;
+
     /**
      * 1. dall-e-2 模型:256x256、512x512、1024x1024
      * 2. dall-e-3 模型:1024x1024, 1792x1024, 或 1024x1792
      */
     @Schema(description = "图片高度")
-    @NotNull(message = "图片高度不能为空")
+//    @NotNull(message = "图片高度不能为空")
     private Integer height;
 
     @Schema(description = "图片宽度")
-    @NotNull(message = "图片宽度不能为空")
+//    @NotNull(message = "图片宽度不能为空")
     private Integer width;
 
     // ========== 各平台绘画的拓展参数 ==========

+ 4 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/image/vo/AiImagePublicPageReqVO.java

@@ -2,6 +2,7 @@ package cn.iocoder.byzs.module.ai.controller.admin.image.vo;
 
 import cn.iocoder.byzs.framework.common.pojo.PageParam;
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
 import lombok.Data;
 
 @Schema(description = "管理后台 - AI 绘画公开的分页 Request VO")
@@ -11,4 +12,7 @@ public class AiImagePublicPageReqVO extends PageParam {
     @Schema(description = "提示词")
     private String prompt;
 
+    @Schema(description = "参考图片")
+    private String promptImage;
+
 }

+ 3 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/image/vo/AiImageRespVO.java

@@ -27,6 +27,9 @@ public class AiImageRespVO {
     @Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "南极的小企鹅")
     private String prompt;
 
+    @Schema(description = "参考图片")
+    private String promptImage;
+
     @Schema(description = "图片宽度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Integer width;
 

+ 3 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentRespVO.java

@@ -42,4 +42,7 @@ public class AiKnowledgeDocumentRespVO {
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 
+    @Schema(description = "租户id")
+    private Long tenantId;
+
 }

+ 3 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeRespVO.java

@@ -36,4 +36,7 @@ public class AiKnowledgeRespVO {
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 
+    @Schema(description = "租户id")
+    private Long tenantId;
+
 }

+ 3 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentRespVO.java

@@ -37,4 +37,7 @@ public class AiKnowledgeSegmentRespVO {
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private Long createTime;
 
+    @Schema(description = "租户id")
+    private Long tenantId;
+
 }

+ 3 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/mindmap/vo/AiMindMapRespVO.java

@@ -33,4 +33,7 @@ public class AiMindMapRespVO {
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 
+    @Schema(description = "租户id")
+    private Long tenantId;
+
 }

+ 3 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/model/vo/apikey/AiApiKeyRespVO.java

@@ -25,4 +25,7 @@ public class AiApiKeyRespVO {
     @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Integer status;
 
+    @Schema(description = "租户id")
+    private Long tenantId;
+
 }

+ 3 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/model/vo/chatRole/AiChatRoleRespVO.java

@@ -71,4 +71,7 @@ public class AiChatRoleRespVO implements VO {
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 
+    @Schema(description = "租户id")
+    private Long tenantId;
+
 }

+ 3 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/model/vo/model/AiModelRespVO.java

@@ -45,4 +45,7 @@ public class AiModelRespVO {
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 
+    @Schema(description = "租户id")
+    private Long tenantId;
+
 }

+ 3 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/model/vo/tool/AiToolRespVO.java

@@ -24,4 +24,7 @@ public class AiToolRespVO {
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 
+    @Schema(description = "租户id")
+    private Long tenantId;
+
 }

+ 3 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/music/vo/AiMusicRespVO.java

@@ -67,4 +67,7 @@ public class AiMusicRespVO {
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 
+    @Schema(description = "租户id")
+    private Long tenantId;
+
 }

+ 3 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/tts/vo/AiTtsRespVO.java

@@ -60,4 +60,7 @@ public class AiTtsRespVO {
     @ExcelProperty("创建时间")
     private LocalDateTime createTime;
 
+    @Schema(description = "租户id")
+    private Long tenantId;
+
 }

+ 109 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/video/AiVideoController.java

@@ -0,0 +1,109 @@
+package cn.iocoder.byzs.module.ai.controller.admin.video;
+
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.byzs.framework.common.pojo.CommonResult;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.module.ai.controller.admin.video.vo.*;
+import cn.iocoder.byzs.module.ai.dal.dataobject.video.AiVideoDO;
+import cn.iocoder.byzs.module.ai.service.video.AiVideoService;
+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 lombok.extern.slf4j.Slf4j;
+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.byzs.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.byzs.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - AI 视频")
+@RestController
+@RequestMapping("/ai/video")
+@Slf4j
+public class AiVideoController {
+
+    @Resource
+    private AiVideoService videoService;
+
+    @GetMapping("/my-page")
+    @Operation(summary = "获取【我的】视频分页")
+    public CommonResult<PageResult<AiVideoRespVO>> getVideoPageMy(@Validated AiVideoPageReqVO pageReqVO) {
+        PageResult<AiVideoDO> pageResult = videoService.getVideoPageMy(getLoginUserId(), pageReqVO);
+        return success(BeanUtils.toBean(pageResult, AiVideoRespVO.class));
+    }
+
+    @GetMapping("/public-page")
+    @Operation(summary = "获取公开的视频分页")
+    public CommonResult<PageResult<AiVideoRespVO>> getVideoPagePublic(AiVideoPublicPageReqVO pageReqVO) {
+        PageResult<AiVideoDO> pageResult = videoService.getVideoPagePublic(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, AiVideoRespVO.class));
+    }
+
+    @GetMapping("/get-my")
+    @Operation(summary = "获取【我的】视频记录")
+    @Parameter(name = "id", required = true, description = "视频编号", example = "1024")
+    public CommonResult<AiVideoRespVO> getVideoMy(@RequestParam("id") Long id) {
+        AiVideoDO video = videoService.getVideo(id);
+        if (video == null || ObjUtil.notEqual(getLoginUserId(), video.getUserId())) {
+            return success(null);
+        }
+        return success(BeanUtils.toBean(video, AiVideoRespVO.class));
+    }
+
+    @GetMapping("/my-list-by-ids")
+    @Operation(summary = "获取【我的】视频记录列表")
+    @Parameter(name = "ids", required = true, description = "视频编号数组", example = "1024,2048")
+    public CommonResult<List<AiVideoRespVO>> getVideoListMyByIds(@RequestParam("ids") List<Long> ids) {
+        List<AiVideoDO> videoList = videoService.getVideoList(ids);
+        videoList.removeIf(item -> !ObjUtil.equal(getLoginUserId(), item.getUserId()));
+        return success(BeanUtils.toBean(videoList, AiVideoRespVO.class));
+    }
+
+    @Operation(summary = "生成视频")
+    @PostMapping("/draw")
+    public CommonResult<Long> drawVideo(@Valid @RequestBody AiVideoDrawReqVO drawReqVO) {
+        return success(videoService.drawVideo(getLoginUserId(), drawReqVO));
+    }
+
+    @Operation(summary = "删除【我的】视频记录")
+    @DeleteMapping("/delete-my")
+    @Parameter(name = "id", required = true, description = "视频编号", example = "1024")
+    public CommonResult<Boolean> deleteVideoMy(@RequestParam("id") Long id) {
+        videoService.deleteVideoMy(id, getLoginUserId());
+        return success(true);
+    }
+
+    // ================ 视频管理 ================
+
+    @GetMapping("/page")
+    @Operation(summary = "获得视频分页")
+    @PreAuthorize("@ss.hasPermission('ai:video:query')")
+    public CommonResult<PageResult<AiVideoRespVO>> getVideoPage(@Valid AiVideoPageReqVO pageReqVO) {
+        PageResult<AiVideoDO> pageResult = videoService.getVideoPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, AiVideoRespVO.class));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新视频")
+    @PreAuthorize("@ss.hasPermission('ai:video:update')")
+    public CommonResult<Boolean> updateVideo(@Valid @RequestBody AiVideoUpdateReqVO updateReqVO) {
+        videoService.updateVideo(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除视频")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('ai:video:delete')")
+    public CommonResult<Boolean> deleteVideo(@RequestParam("id") Long id) {
+        videoService.deleteVideo(id);
+        return success(true);
+    }
+
+}

+ 47 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/video/vo/AiVideoDrawReqVO.java

@@ -0,0 +1,47 @@
+package cn.iocoder.byzs.module.ai.controller.admin.video.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import org.springframework.ai.openai.OpenAiImageOptions;
+import org.springframework.ai.stabilityai.api.StabilityAiImageOptions;
+
+import java.util.Map;
+
+@Schema(description = "管理后台 - AI 绘画 Request VO")
+@Data
+public class AiVideoDrawReqVO {
+
+    @Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "模型编号不能为空")
+    private Long modelId;
+
+    @Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "画一个长城")
+    @NotEmpty(message = "提示词不能为空")
+    @Size(max = 1200, message = "提示词最大 1200")
+    private String prompt;
+
+    @Schema(description = "第一帧图片")
+    private String promptImage;
+
+    @Schema(description = "分辨率")
+    private String resolution;
+
+    @Schema(description = "视频时长")
+    @NotNull(message = "视频时长不能为空")
+    private Integer duration;
+
+    // ========== 各平台绘画的拓展参数 ==========
+
+    /**
+     * 绘制参数,不同 platform 的不同参数
+     *
+     * 1. {@link OpenAiImageOptions}
+     * 2. {@link StabilityAiImageOptions}
+     */
+    @Schema(description = "绘制参数")
+    private Map<String, String> options;
+
+}

+ 38 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/video/vo/AiVideoPageReqVO.java

@@ -0,0 +1,38 @@
+package cn.iocoder.byzs.module.ai.controller.admin.video.vo;
+
+import cn.iocoder.byzs.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.byzs.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - AI 绘画分页 Request VO")
+@Data
+public class AiVideoPageReqVO extends PageParam {
+
+    @Schema(description = "用户编号", example = "28987")
+    private Long userId;
+
+    @Schema(description = "平台", example = "OpenAI")
+    private String platform;
+
+    @Schema(description = "提示词", example = "1")
+    private String prompt;
+
+    @Schema(description = "第一帧图片")
+    private String promptImage;
+
+    @Schema(description = "绘画状态", example = "1")
+    private Integer status;
+
+    @Schema(description = "是否发布", example = "1")
+    private Boolean publicStatus;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 17 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/video/vo/AiVideoPublicPageReqVO.java

@@ -0,0 +1,17 @@
+package cn.iocoder.byzs.module.ai.controller.admin.video.vo;
+
+import cn.iocoder.byzs.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - AI 绘画公开的分页 Request VO")
+@Data
+public class AiVideoPublicPageReqVO extends PageParam {
+
+    @Schema(description = "提示词")
+    private String prompt;
+
+    @Schema(description = "第一帧图片")
+    private String promptImage;
+
+}

+ 61 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/video/vo/AiVideoRespVO.java

@@ -0,0 +1,61 @@
+package cn.iocoder.byzs.module.ai.controller.admin.video.vo;
+
+import cn.iocoder.byzs.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+@Schema(description = "管理后台 - AI 绘画 Response VO")
+@Data
+public class AiVideoRespVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long userId;
+
+    @Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI")
+    private String platform;  // 参见 AiPlatformEnum 枚举
+
+    @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "stable-diffusion-v1-6")
+    private String model;
+
+    @Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "南极的小企鹅")
+    private String prompt;
+
+    @Schema(description = "第一帧图片")
+    private String promptImage;
+
+    @Schema(description = "分辨率")
+    private Integer resolution;
+
+    @Schema(description = "视频时长")
+    private Integer duration;
+
+    @Schema(description = "绘画状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Integer status;
+
+    @Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "public")
+    private Boolean publicStatus;
+
+    @Schema(description = "视频地址", example = "")
+    private String videoUrl;
+
+    @Schema(description = "绘画错误信息", example = "视频错误信息")
+    private String errorMessage;
+
+    @Schema(description = "绘制参数")
+    private Map<String, String> options;
+
+    @Schema(description = "完成时间")
+    private LocalDateTime finishTime;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 18 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/video/vo/AiVideoUpdateReqVO.java

@@ -0,0 +1,18 @@
+package cn.iocoder.byzs.module.ai.controller.admin.video.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 AiVideoUpdateReqVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
+    @NotNull(message = "编号不能为空")
+    private Long id;
+
+    @Schema(description = "是否发布", example = "true")
+    private Boolean publicStatus;
+
+}

+ 3 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/workflow/vo/AiWorkflowRespVO.java

@@ -30,4 +30,7 @@ public class AiWorkflowRespVO {
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "时间戳格式")
     private LocalDateTime createTime;
 
+    @Schema(description = "租户id")
+    private Long tenantId;
+
 }

+ 3 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/write/vo/AiWriteRespVO.java

@@ -51,4 +51,7 @@ public class AiWriteRespVO {
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 
+    @Schema(description = "租户id")
+    private Long tenantId;
+
 }

+ 5 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/chat/AiChatConversationDO.java

@@ -26,6 +26,11 @@ import java.time.LocalDateTime;
 @AllArgsConstructor
 public class AiChatConversationDO extends BaseDO {
 
+    /**
+     * 租户id
+     */
+    private Long tenantId;
+
     public static final String TITLE_DEFAULT = "新对话";
 
     /**

+ 5 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/chat/AiChatMessageDO.java

@@ -28,6 +28,11 @@ import java.util.List;
 @AllArgsConstructor
 public class AiChatMessageDO extends BaseDO {
 
+    /**
+     * 租户id
+     */
+    private Long tenantId;
+
     /**
      * 编号,作为每条聊天记录的唯一标识符
      */

+ 10 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/image/AiImageDO.java

@@ -47,6 +47,11 @@ public class AiImageDO extends BaseDO {
      */
     private String prompt;
 
+    /**
+     * 参考图片
+     */
+    private String promptImage;
+
     /**
      * 平台
      *
@@ -123,5 +128,10 @@ public class AiImageDO extends BaseDO {
      */
     private String taskId;
 
+    /**
+     * 租户id
+     */
+    private Long tenantId;
+
 }
 

+ 5 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java

@@ -18,6 +18,11 @@ import lombok.Data;
 @Data
 public class AiKnowledgeDO extends BaseDO {
 
+    /**
+     * 租户id
+     */
+    private Long tenantId;
+
     /**
      * 编号
      */

+ 5 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java

@@ -17,6 +17,11 @@ import lombok.Data;
 @Data
 public class AiKnowledgeDocumentDO extends BaseDO {
 
+    /**
+     * 租户id
+     */
+    private Long tenantId;
+
     /**
      * 编号
      */

+ 5 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java

@@ -17,6 +17,11 @@ import lombok.Data;
 @Data
 public class AiKnowledgeSegmentDO extends BaseDO {
 
+    /**
+     * 租户id
+     */
+    private Long tenantId;
+
     /**
      * 向量库的编号 - 空值
      */

+ 5 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/mindmap/AiMindMapDO.java

@@ -18,6 +18,11 @@ import lombok.Data;
 @Data
 public class AiMindMapDO extends BaseDO {
 
+    /**
+     * 租户id
+     */
+    private Long tenantId;
+
     /**
      * 编号
      */

+ 6 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/model/AiApiKeyDO.java

@@ -6,6 +6,7 @@ import cn.iocoder.byzs.framework.mybatis.core.dataobject.BaseDO;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;
 
 /**
@@ -51,4 +52,9 @@ public class AiApiKeyDO extends BaseDO {
      */
     private Integer status;
 
+    /**
+     * 租户id
+     */
+    private Long tenantId;
+
 }

+ 5 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/model/AiChatRoleDO.java

@@ -121,4 +121,9 @@ public class AiChatRoleDO extends BaseDO {
      */
     private Integer status;
 
+    /**
+     * 租户id
+     */
+    private Long tenantId;
+
 }

+ 5 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/model/AiModelDO.java

@@ -85,4 +85,9 @@ public class AiModelDO extends BaseDO {
      */
     private Integer maxContexts;
 
+    /**
+     * 租户id
+     */
+    private Long tenantId;
+
 }

+ 5 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/model/AiToolDO.java

@@ -46,4 +46,9 @@ public class AiToolDO extends BaseDO {
      */
     private Integer status;
 
+    /**
+     * 租户id
+     */
+    private Long tenantId;
+
 }

+ 5 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/music/AiMusicDO.java

@@ -23,6 +23,11 @@ import java.util.List;
 @Data
 public class AiMusicDO extends BaseDO {
 
+    /**
+     * 租户id
+     */
+    private Long tenantId;
+
     /**
      * 编号
      */

+ 5 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/tts/AiTtsDO.java

@@ -21,6 +21,11 @@ import lombok.*;
 @AllArgsConstructor
 public class AiTtsDO extends BaseDO {
 
+    /**
+     * 租户id
+     */
+    private Long tenantId;
+
     /**
      * 编号
      */

+ 131 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/video/AiVideoDO.java

@@ -0,0 +1,131 @@
+package cn.iocoder.byzs.module.ai.dal.dataobject.video;
+
+import cn.iocoder.byzs.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.byzs.module.ai.dal.dataobject.model.AiModelDO;
+import cn.iocoder.byzs.module.ai.enums.image.AiImageStatusEnum;
+import cn.iocoder.byzs.module.ai.enums.model.AiPlatformEnum;
+import cn.iocoder.byzs.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
+import cn.iocoder.byzs.module.system.api.user.dto.AdminUserRespDTO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.Data;
+import org.springframework.ai.openai.OpenAiImageOptions;
+import org.springframework.ai.stabilityai.api.StabilityAiImageOptions;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * AI 绘画 DO
+ *
+ * @author fansili
+ */
+@TableName(value = "ai_video", autoResultMap = true)
+@KeySequence("ai_video_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+public class AiVideoDO extends BaseDO {
+
+    /**
+     * 租户id
+     */
+    private Long tenantId;
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 用户编号
+     *
+     * 关联 {@link AdminUserRespDTO#getId()}
+     */
+    private Long userId;
+
+    /**
+     * 提示词
+     */
+    private String prompt;
+
+    /**
+     * 第一帧图片
+     */
+    private String promptImage;
+
+    /**
+     * 平台
+     *
+     * 枚举 {@link AiPlatformEnum}
+     */
+    private String platform;
+    /**
+     * 模型编号
+     *
+     * 关联 {@link AiModelDO#getId()}
+     */
+    private Long modelId;
+    /**
+     * 模型标识
+     *
+     * 冗余 {@link AiModelDO#getModel()}
+     */
+    private String model;
+
+    /**
+     * 分辨率
+     */
+    private String resolution;
+    /**
+     * 视频时长
+     */
+    private Integer duration;
+
+    /**
+     * 生成状态
+     *
+     * 枚举 {@link AiImageStatusEnum}
+     */
+    private Integer status;
+
+    /**
+     * 完成时间
+     */
+    private LocalDateTime finishTime;
+
+    /**
+     * 绘画错误信息
+     */
+    private String errorMessage;
+
+    /**
+     * 视频地址
+     */
+    private String videoUrl;
+    /**
+     * 是否公开
+     */
+    private Boolean publicStatus;
+
+    /**
+     * 绘制参数,不同 platform 的不同参数
+     *
+     * 1. {@link OpenAiImageOptions}
+     * 2. {@link StabilityAiImageOptions}
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private Map<String, Object> options;
+
+    /**
+     * 任务编号
+     *
+     * 1. midjourney proxy:关联的 task id
+     */
+    private String taskId;
+
+}
+

+ 5 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/workflow/AiWorkflowDO.java

@@ -17,6 +17,11 @@ import lombok.Data;
 @Data
 public class AiWorkflowDO extends BaseDO {
 
+    /**
+     * 租户id
+     */
+    private Long tenantId;
+
     /**
      * 编号
      */

+ 5 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/dataobject/write/AiWriteDO.java

@@ -20,6 +20,11 @@ import lombok.Data;
 @Data
 public class AiWriteDO extends BaseDO {
 
+    /**
+     * 租户id
+     */
+    private Long tenantId;
+
     /**
      * 编号
      */

+ 57 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/mysql/video/AiVideoMapper.java

@@ -0,0 +1,57 @@
+package cn.iocoder.byzs.module.ai.dal.mysql.video;
+
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.byzs.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.byzs.module.ai.controller.admin.video.vo.AiVideoPageReqVO;
+import cn.iocoder.byzs.module.ai.controller.admin.video.vo.AiVideoPublicPageReqVO;
+import cn.iocoder.byzs.module.ai.dal.dataobject.video.AiVideoDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * AI 绘图 Mapper
+ *
+ * @author fansili
+ */
+@Mapper
+public interface AiVideoMapper extends BaseMapperX<AiVideoDO> {
+
+    default AiVideoDO selectByTaskId(String taskId) {
+        return selectOne(AiVideoDO::getTaskId, taskId);
+    }
+
+    default PageResult<AiVideoDO> selectPage(AiVideoPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<AiVideoDO>()
+                .eqIfPresent(AiVideoDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(AiVideoDO::getPlatform, reqVO.getPlatform())
+                .eqIfPresent(AiVideoDO::getStatus, reqVO.getStatus())
+                .eqIfPresent(AiVideoDO::getPublicStatus, reqVO.getPublicStatus())
+                .betweenIfPresent(AiVideoDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(AiVideoDO::getId));
+    }
+
+    default PageResult<AiVideoDO> selectPageMy(Long userId, AiVideoPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<AiVideoDO>()
+                .likeIfPresent(AiVideoDO::getPrompt, reqVO.getPrompt())
+                // 情况一:公开
+                .eq(Boolean.TRUE.equals(reqVO.getPublicStatus()), AiVideoDO::getPublicStatus, reqVO.getPublicStatus())
+                // 情况二:私有
+                .eq(Boolean.FALSE.equals(reqVO.getPublicStatus()), AiVideoDO::getUserId, userId)
+                .orderByDesc(AiVideoDO::getId));
+    }
+
+    default PageResult<AiVideoDO> selectPage(AiVideoPublicPageReqVO pageReqVO) {
+        return selectPage(pageReqVO, new LambdaQueryWrapperX<AiVideoDO>()
+                .eqIfPresent(AiVideoDO::getPublicStatus, Boolean.TRUE)
+                .likeIfPresent(AiVideoDO::getPrompt, pageReqVO.getPrompt())
+                .orderByDesc(AiVideoDO::getId));
+    }
+
+    default List<AiVideoDO> selectListByStatusAndPlatform(Integer status, String platform) {
+        return selectList(AiVideoDO::getStatus, status,
+                AiVideoDO::getPlatform, platform);
+    }
+
+}

+ 3 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/enums/ErrorCodeConstants.java

@@ -69,4 +69,7 @@ public interface ErrorCodeConstants {
     // ========== AI tts 1-050-011-000 ==========
     ErrorCode TTS_NOT_EXISTS = new ErrorCode(1_050_011_001, "AI TTS文转音不存在");
 
+    // ========== API 视频 1-040-012-000 ==========
+    ErrorCode VIDEO_NOT_EXISTS = new ErrorCode(1_040_012_000, "视频不存在!");
+
 }

+ 3 - 1
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/enums/model/AiModelTypeEnum.java

@@ -20,7 +20,9 @@ public enum AiModelTypeEnum implements ArrayValuable<Integer> {
     VOICE(3, "语音"),
     VIDEO(4, "视频"),
     EMBEDDING(5, "向量"),
-    RERANK(6, "重排序");
+    RERANK(6, "重排序"),
+    IMAGE_EDIT(7, "图片编辑"),
+    VIDEO_IMAGE(8, "图生视频");
 
     /**
      * 类型

+ 37 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/enums/video/AiVideoStatusEnum.java

@@ -0,0 +1,37 @@
+package cn.iocoder.byzs.module.ai.enums.video;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * AI 视频状态的枚举
+ *
+ * @author fansili
+ */
+@AllArgsConstructor
+@Getter
+public enum AiVideoStatusEnum {
+
+    IN_PROGRESS(10, "进行中"),
+    SUCCESS(20, "已完成"),
+    FAIL(30, "已失败");
+
+    /**
+     * 状态
+     */
+    private final Integer status;
+    /**
+     * 状态名
+     */
+    private final String name;
+
+    public static AiVideoStatusEnum valueOfStatus(Integer status) {
+        for (AiVideoStatusEnum statusEnum : AiVideoStatusEnum.values()) {
+            if (statusEnum.getStatus().equals(status)) {
+                return statusEnum;
+            }
+        }
+        throw new IllegalArgumentException("未知会话状态: " + status);
+    }
+
+}

+ 6 - 1
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/service/image/AiImageServiceImpl.java

@@ -113,6 +113,7 @@ public class AiImageServiceImpl implements AiImageService {
 
         // 2. 保存数据库
         AiImageDO image = BeanUtils.toBean(drawReqVO, AiImageDO.class).setUserId(userId)
+                .setPrompt(drawReqVO.getPrompt()).setPromptImage(drawReqVO.getPromptImage())
                 .setPlatform(model.getPlatform()).setModelId(model.getId()).setModel(model.getModel())
                 .setPublicStatus(false).setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus());
         imageMapper.insert(image);
@@ -146,8 +147,12 @@ public class AiImageServiceImpl implements AiImageService {
                 GenerateImagesRequest generateRequest = GenerateImagesRequest.builder()
                         .model(model.getModel())
                         .prompt(reqVO.getPrompt())
-                        .size(reqVO.getWidth() + "x" + reqVO.getHeight())
                         .build();
+                if (reqVO.getPromptImage() != null) {
+                    generateRequest.setImage(reqVO.getPromptImage());
+                }else{
+                    generateRequest.setSize(reqVO.getWidth() + "x" + reqVO.getHeight());
+                }
                 // 调用豆包API生成图片
                 ImagesResponse imagesResponse = service.generateImages(generateRequest);
                 // 下载图片并上传到文件服务

+ 92 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/service/video/AiVideoService.java

@@ -0,0 +1,92 @@
+package cn.iocoder.byzs.module.ai.service.video;
+
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.module.ai.controller.admin.video.vo.AiVideoDrawReqVO;
+import cn.iocoder.byzs.module.ai.controller.admin.video.vo.AiVideoPageReqVO;
+import cn.iocoder.byzs.module.ai.controller.admin.video.vo.AiVideoPublicPageReqVO;
+import cn.iocoder.byzs.module.ai.controller.admin.video.vo.AiVideoUpdateReqVO;
+import cn.iocoder.byzs.module.ai.dal.dataobject.video.AiVideoDO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+
+/**
+ * AI 视频 Service 接口
+ *
+ * @author fansili
+ */
+public interface AiVideoService {
+
+    /**
+     * 获取【我的】绘图分页
+     *
+     * @param userId 用户编号
+     * @param pageReqVO 分页条件
+     * @return 绘图分页
+     */
+    PageResult<AiVideoDO> getVideoPageMy(Long userId, AiVideoPageReqVO pageReqVO);
+
+    /**
+     * 获取公开的绘图分页
+     *
+     * @param pageReqVO 分页条件
+     * @return 绘图分页
+     */
+    PageResult<AiVideoDO> getVideoPagePublic(AiVideoPublicPageReqVO pageReqVO);
+
+    /**
+     * 获得绘图记录
+     *
+     * @param id 绘图编号
+     * @return 绘图记录
+     */
+    AiVideoDO getVideo(Long id);
+
+    /**
+     * 获得绘图列表
+     *
+     * @param ids 绘图编号数组
+     * @return 绘图记录列表
+     */
+    List<AiVideoDO> getVideoList(List<Long> ids);
+
+    /**
+     * 绘制图片
+     *
+     * @param userId 用户编号
+     * @param drawReqVO 绘制请求
+     * @return 绘画编号
+     */
+    Long drawVideo(Long userId, AiVideoDrawReqVO drawReqVO);
+
+    /**
+     * 删除【我的】绘画记录
+     *
+     * @param id 绘画编号
+     * @param userId 用户编号
+     */
+    void deleteVideoMy(Long id, Long userId);
+
+    /**
+     * 获得绘画分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 绘画分页
+     */
+    PageResult<AiVideoDO> getVideoPage(AiVideoPageReqVO pageReqVO);
+
+    /**
+     * 更新绘画
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateVideo(@Valid AiVideoUpdateReqVO updateReqVO);
+
+    /**
+     * 删除绘画
+     *
+     * @param id 编号
+     */
+    void deleteVideo(Long id);
+
+}

+ 265 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/service/video/AiVideoServiceImpl.java

@@ -0,0 +1,265 @@
+package cn.iocoder.byzs.module.ai.service.video;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.hutool.http.HttpUtil;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.module.ai.controller.admin.video.vo.AiVideoDrawReqVO;
+import cn.iocoder.byzs.module.ai.controller.admin.video.vo.AiVideoPageReqVO;
+import cn.iocoder.byzs.module.ai.controller.admin.video.vo.AiVideoPublicPageReqVO;
+import cn.iocoder.byzs.module.ai.controller.admin.video.vo.AiVideoUpdateReqVO;
+import cn.iocoder.byzs.module.ai.dal.dataobject.model.AiApiKeyDO;
+import cn.iocoder.byzs.module.ai.dal.dataobject.model.AiModelDO;
+import cn.iocoder.byzs.module.ai.dal.dataobject.video.AiVideoDO;
+import cn.iocoder.byzs.module.ai.dal.mysql.video.AiVideoMapper;
+import cn.iocoder.byzs.module.ai.enums.video.AiVideoStatusEnum;
+import cn.iocoder.byzs.module.ai.service.model.AiApiKeyService;
+import cn.iocoder.byzs.module.ai.service.model.AiModelService;
+import cn.iocoder.byzs.module.ai.service.model.AiModelServiceImpl;
+import cn.iocoder.byzs.module.infra.api.file.FileApi;
+import com.volcengine.ark.runtime.model.content.generation.CreateContentGenerationTaskRequest;
+import com.volcengine.ark.runtime.model.content.generation.CreateContentGenerationTaskRequest.Content;
+import com.volcengine.ark.runtime.model.content.generation.CreateContentGenerationTaskResult;
+import com.volcengine.ark.runtime.model.content.generation.GetContentGenerationTaskRequest;
+import com.volcengine.ark.runtime.model.content.generation.GetContentGenerationTaskResponse;
+import com.volcengine.ark.runtime.service.ArkService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.ConnectionPool;
+import okhttp3.Dispatcher;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static cn.iocoder.byzs.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.byzs.module.ai.enums.ErrorCodeConstants.VIDEO_NOT_EXISTS;
+
+/**
+ * AI 视频 Service 实现类
+ *
+ * @author fansili
+ */
+@Service
+@Slf4j
+public class AiVideoServiceImpl implements AiVideoService {
+
+    @Resource
+    private AiModelService modelService;
+
+    @Resource
+    private AiVideoMapper videoMapper;
+
+    @Resource
+    private FileApi fileApi;
+    @Resource
+    private AiApiKeyService apiKeyService;
+    @Resource
+    private AiModelServiceImpl aiModelService;
+
+    @Override
+    public PageResult<AiVideoDO> getVideoPageMy(Long userId, AiVideoPageReqVO pageReqVO) {
+        return videoMapper.selectPageMy(userId, pageReqVO);
+    }
+
+    @Override
+    public PageResult<AiVideoDO> getVideoPagePublic(AiVideoPublicPageReqVO pageReqVO) {
+        return videoMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public AiVideoDO getVideo(Long id) {
+        return videoMapper.selectById(id);
+    }
+
+    @Override
+    public List<AiVideoDO> getVideoList(List<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
+        return videoMapper.selectBatchIds(ids);
+    }
+
+    @Override
+    public Long drawVideo(Long userId, AiVideoDrawReqVO drawReqVO) {
+        // 1. 校验模型
+        AiModelDO model = modelService.validateModel(drawReqVO.getModelId());
+
+        // 2. 保存数据库
+        AiVideoDO video = BeanUtils.toBean(drawReqVO, AiVideoDO.class).setUserId(userId)
+                .setPlatform(model.getPlatform()).setPromptImage(drawReqVO.getPromptImage())
+                .setModelId(model.getId()).setModel(model.getModel())
+                .setPublicStatus(false).setStatus(AiVideoStatusEnum.IN_PROGRESS.getStatus());
+        videoMapper.insert(video);
+
+        // 3. 异步绘制,后续前端通过返回的 id 进行轮询结果
+        executeDrawVideo(video, drawReqVO, model);
+        return video.getId();
+    }
+
+    @Async
+    public void executeDrawVideo(AiVideoDO video, AiVideoDrawReqVO reqVO, AiModelDO model) {
+
+        String filePath = "";
+
+        try {
+
+            AiApiKeyDO apiKeyDo = apiKeyService.validateApiKey(model.getKeyId());
+            String apiKey = apiKeyDo.getApiKey();
+
+            ConnectionPool connectionPool = new ConnectionPool(5, 1, TimeUnit.SECONDS);
+            Dispatcher dispatcher = new Dispatcher();
+            ArkService service = ArkService.builder()
+                        .dispatcher(dispatcher)
+                        .connectionPool(connectionPool)
+                        .apiKey(apiKey)
+                        .build();
+
+            List<Content> contents = new ArrayList<>();
+            // 图生视频功能
+            //文本提示词
+            String promptText = reqVO.getPrompt();
+            // 第一帧图片
+            String promptImage = reqVO.getPromptImage();
+            //视频分辨率
+            String resolution = reqVO.getResolution();
+            //视频时长
+            int duration = reqVO.getDuration();
+            //是否固定相机位置
+            boolean cameraFixed = false;
+            //是否添加水印
+            boolean watermark = true;
+
+            String formattedPrompt = String.format("%s  --resolution %s  --duration %d --camerafixed %s --watermark %s",
+                    promptText, resolution, duration, cameraFixed, watermark);
+            contents.add(Content.builder()
+                    .type("text")
+                    .text(formattedPrompt)
+                    .build());
+
+            // 首帧图片
+            if (StrUtil.isNotBlank(promptImage)) {
+                contents.add(Content.builder()
+                        .type("image_url")
+                        .imageUrl(CreateContentGenerationTaskRequest.ImageUrl.builder()
+                                .url(promptImage) // 请上传可以访问的图片URL
+                                .build())
+                        .build());
+            }
+
+            // 创建视频生成任务
+            CreateContentGenerationTaskRequest createRequest =
+                    CreateContentGenerationTaskRequest.builder()
+                            .model(model.getModel())
+                            .content(contents)
+                            .build();
+
+            CreateContentGenerationTaskResult createResult = service.createContentGenerationTask(createRequest);
+
+            // 获取任务详情
+            String taskId = createResult.getId();
+            GetContentGenerationTaskRequest getRequest = GetContentGenerationTaskRequest.builder()
+                    .taskId(taskId)
+                    .build();
+
+            Integer videoSstatus = AiVideoStatusEnum.SUCCESS.getStatus();
+            // 轮询查询部分
+            while (true) {
+                try {
+                    GetContentGenerationTaskResponse getResponse = service.getContentGenerationTask(getRequest);
+                    String status = getResponse.getStatus();
+                    if ("succeeded".equalsIgnoreCase(status)) {
+                        GetContentGenerationTaskResponse.Content content = getResponse.getContent();
+
+                        System.out.println("---------图片地址:" + content.getVideoUrl());
+                        // 下载图片并上传到文件服务
+                        byte[] fileContent = HttpUtil.downloadBytes(content.getVideoUrl());
+                        filePath = fileApi.createFile(fileContent);
+                        break;
+                    } else if ("failed".equalsIgnoreCase(status)) {
+                        filePath = "http://errr.png";
+                        videoSstatus = AiVideoStatusEnum.FAIL.getStatus();
+                        break;
+                    } else {
+                        TimeUnit.SECONDS.sleep(3);
+                        log.info("[executeDrawVideo][video({}) 定时生成查询, 状态({})]", video, getResponse);
+                    }
+                } catch (InterruptedException ie) {
+                    Thread.currentThread().interrupt();
+                    break;
+                }
+            }
+
+            // 3. 更新数据库
+            videoMapper.updateById(new AiVideoDO().setId(video.getId()).setStatus(videoSstatus)
+                    .setVideoUrl(filePath).setFinishTime(LocalDateTime.now()));
+
+            // shutdown service after all requests is finished
+            service.shutdownExecutor();
+
+        } catch (Exception ex) {
+            log.error("[executeDrawVideo][video({}) 生成异常]", video, ex);
+            videoMapper.updateById(new AiVideoDO().setId(video.getId())
+                    .setStatus(AiVideoStatusEnum.FAIL.getStatus())
+                    .setErrorMessage(ex.getMessage()).setFinishTime(LocalDateTime.now()));
+        }
+    }
+
+    @Override
+    public void deleteVideoMy(Long id, Long userId) {
+        // 1. 校验是否存在
+        AiVideoDO video = validateVideoExists(id);
+        if (ObjUtil.notEqual(video.getUserId(), userId)) {
+            throw exception(VIDEO_NOT_EXISTS);
+        }
+        // 2. 删除记录
+        videoMapper.deleteById(id);
+    }
+
+    @Override
+    public PageResult<AiVideoDO> getVideoPage(AiVideoPageReqVO pageReqVO) {
+        return videoMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public void updateVideo(AiVideoUpdateReqVO updateReqVO) {
+        // 1. 校验存在
+        validateVideoExists(updateReqVO.getId());
+        // 2. 更新发布状态
+        videoMapper.updateById(BeanUtils.toBean(updateReqVO, AiVideoDO.class));
+    }
+
+    @Override
+    public void deleteVideo(Long id) {
+        // 1. 校验存在
+        validateVideoExists(id);
+        // 2. 删除
+        videoMapper.deleteById(id);
+    }
+
+    private AiVideoDO validateVideoExists(Long id) {
+        AiVideoDO video = videoMapper.selectById(id);
+        if (video == null) {
+            throw exception(VIDEO_NOT_EXISTS);
+        }
+        return video;
+    }
+
+
+    /**
+     * 获得自身的代理对象,解决 AOP 生效问题
+     *
+     * @return 自己
+     */
+    private AiVideoServiceImpl getSelf() {
+        return SpringUtil.getBean(getClass());
+    }
+
+}

BIN
byzs-module-infra/src/main/resources/file/erweima.jpg


+ 3 - 3
byzs-server/src/main/resources/application.yaml

@@ -3,9 +3,9 @@ spring:
     name: byzs-bjdx
 
   profiles:
-    #    active: local
-#    active: localProd
-      active: prodDev
+#        active: local
+    active: localProd
+#      active: prodDev
 #      active: prod
 
   main:

+ 1 - 1
byzs-server/src/main/resources/logback-spring.xml

@@ -57,7 +57,7 @@
     </appender>
 
     <!-- 本地环境 -->
-    <springProfile name="local,localProd">
+    <springProfile name="local,localProd,prodDev">
         <root level="INFO">
             <appender-ref ref="STDOUT"/>
             <appender-ref ref="GRPC"/> <!-- 本地环境下,如果不想接入 SkyWalking 日志服务,可以注释掉本行 -->

+ 24 - 0
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/ai/WebAiController.java

@@ -12,12 +12,16 @@ import cn.iocoder.byzs.module.ai.controller.admin.image.vo.AiImageDrawReqVO;
 import cn.iocoder.byzs.module.ai.controller.admin.image.vo.AiImageRespVO;
 import cn.iocoder.byzs.module.ai.controller.admin.model.vo.chatRole.AiChatRolePageReqVO;
 import cn.iocoder.byzs.module.ai.controller.admin.model.vo.chatRole.AiChatRoleRespVO;
+import cn.iocoder.byzs.module.ai.controller.admin.video.vo.AiVideoDrawReqVO;
+import cn.iocoder.byzs.module.ai.controller.admin.video.vo.AiVideoRespVO;
 import cn.iocoder.byzs.module.ai.dal.dataobject.image.AiImageDO;
 import cn.iocoder.byzs.module.ai.dal.dataobject.model.AiChatRoleDO;
+import cn.iocoder.byzs.module.ai.dal.dataobject.video.AiVideoDO;
 import cn.iocoder.byzs.module.ai.service.chat.AiChatConversationService;
 import cn.iocoder.byzs.module.ai.service.chat.AiChatMessageService;
 import cn.iocoder.byzs.module.ai.service.image.AiImageService;
 import cn.iocoder.byzs.module.ai.service.model.AiChatRoleService;
+import cn.iocoder.byzs.module.ai.service.video.AiVideoService;
 import cn.iocoder.byzs.module.web.controller.admin.ai.vo.WebAiChatRoleVO;
 import cn.iocoder.byzs.module.web.service.ai.WebAiServiceImpl;
 import io.swagger.v3.oas.annotations.Operation;
@@ -50,6 +54,8 @@ public class WebAiController {
     @Resource
     private AiImageService imageService;
     @Resource
+    private AiVideoService videoService;
+    @Resource
     private AiChatRoleService chatRoleService;
     @Resource
     private WebAiServiceImpl webAiService;
@@ -113,4 +119,22 @@ public class WebAiController {
         PageResult<AiChatRoleDO> pageResult = chatRoleService.getChatRoleMyPage(pageReqVO, getLoginUserId());
         return success(BeanUtils.toBean(pageResult, WebAiChatRoleVO.class));
     }
+
+    // ================ 视频管理 ================
+
+    @Operation(summary = "生成视频")
+    @PostMapping("/create-video")
+    public CommonResult<Long> drawVideo(@Valid @RequestBody AiVideoDrawReqVO drawReqVO) {
+        return success(videoService.drawVideo(getLoginUserId(), drawReqVO));
+    }
+
+    @GetMapping("/video-get-my")
+    @Operation(summary = "绘画-获取绘图记录")
+    public CommonResult<AiVideoRespVO> getVideoMy(@RequestParam("id") Long id) {
+        AiVideoDO video = videoService.getVideo(id);
+        if (video == null || ObjUtil.notEqual(getLoginUserId(), video.getUserId())) {
+            return success(null);
+        }
+        return success(BeanUtils.toBean(video, AiVideoRespVO.class));
+    }
 }