1
0

3 Incheckningar ee0baa06d8 ... 04e3313d71

Upphovsman SHA1 Meddelande Datum
  xiang 04e3313d71 Merge remote-tracking branch 'origin/master' 1 vecka sedan
  xiang cabcb6a764 video黑屏 1 vecka sedan
  xiang d95f7647f8 Merge remote-tracking branch 'origin/master' 1 vecka sedan
45 ändrade filer med 1436 tillägg och 789 borttagningar
  1. 78 1
      content/content.sql
  2. 1 0
      content/pom.xml
  3. 78 0
      content/src/main/java/com/content/controller/CommentController.java
  4. 95 0
      content/src/main/java/com/content/controller/MvController.java
  5. 55 0
      content/src/main/java/com/content/controller/MvFavoriteController.java
  6. 12 0
      content/src/main/java/com/content/domain/dto/ADdCommentDto.java
  7. 14 0
      content/src/main/java/com/content/domain/dto/MvUploadDto.java
  8. 9 0
      content/src/main/java/com/content/domain/dto/getCommentListDto.java
  9. 68 0
      content/src/main/java/com/content/domain/po/Comment.java
  10. 103 0
      content/src/main/java/com/content/domain/po/Mv.java
  11. 44 0
      content/src/main/java/com/content/domain/po/MvFavorite.java
  12. 13 0
      content/src/main/java/com/content/domain/vo/CommentVO.java
  13. 23 0
      content/src/main/java/com/content/mapper/CommentMapper.java
  14. 22 0
      content/src/main/java/com/content/mapper/MvFavoriteMapper.java
  15. 18 0
      content/src/main/java/com/content/mapper/MvMapper.java
  16. 25 0
      content/src/main/java/com/content/service/ICommentService.java
  17. 20 0
      content/src/main/java/com/content/service/IMvFavoriteService.java
  18. 18 0
      content/src/main/java/com/content/service/IMvService.java
  19. 51 0
      content/src/main/java/com/content/service/impl/CommentServiceImpl.java
  20. 27 0
      content/src/main/java/com/content/service/impl/MvFavoriteServiceImpl.java
  21. 31 0
      content/src/main/java/com/content/service/impl/MvServiceImpl.java
  22. 1 1
      content/src/main/resources/mapper/CommentMapper.xml
  23. 14 0
      content/src/main/resources/mapper/MvFavoriteMapper.xml
  24. 5 0
      content/src/main/resources/mapper/MvMapper.xml
  25. 4 121
      media/media.sql
  26. 16 4
      media/pom.xml
  27. 41 0
      media/src/main/java/com/media/Listener/MergeFileListener.java
  28. 34 0
      media/src/main/java/com/media/config/ErrorMessageasConfiguration.java
  29. 18 0
      media/src/main/java/com/media/config/MqConfig.java
  30. 25 0
      media/src/main/java/com/media/config/RabbitConfig.java
  31. 67 161
      media/src/main/java/com/media/controller/FileUploadController.java
  32. 0 14
      media/src/main/java/com/media/domain/dto/MergeChunkDTO.java
  33. 15 0
      media/src/main/java/com/media/domain/dto/MergeDto.java
  34. 0 30
      media/src/main/java/com/media/domain/po/ChunkInfo.java
  35. 0 17
      media/src/main/java/com/media/domain/vo/UploadStatusVO.java
  36. 0 12
      media/src/main/java/com/media/service/FileUploadService.java
  37. 0 16
      media/src/main/java/com/media/service/IMediaFileService.java
  38. 0 282
      media/src/main/java/com/media/service/impl/ChunkUploadService.java
  39. 0 74
      media/src/main/java/com/media/service/impl/FileUploadServiceImpl.java
  40. 2 1
      media/src/main/java/com/media/util/enums/StorageBucket.java
  41. 237 55
      media/src/main/java/com/media/util/upload/MinioFileUploader.java
  42. 56 0
      media/src/test/java/com/media/RabbitMQBeanTest.java
  43. 29 0
      media/src/test/java/com/media/RabbitMQConnectionTest.java
  44. 60 0
      media/src/test/java/com/media/VideoToGifTest.java
  45. 7 0
      parent/pom.xml

+ 78 - 1
content/content.sql

@@ -219,4 +219,81 @@ CREATE TABLE `promotion_stat` (
                                   FOREIGN KEY (`promotion_id`) REFERENCES `song_promotion` (`id`) ON DELETE CASCADE,
                                   UNIQUE KEY `uk_promotion_date` (`promotion_id`, `stat_date`),
                                   INDEX `idx_stat_date` (`stat_date`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='推广效果统计表';
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='推广效果统计表';
+
+-- ------------------------------
+-- 13. MV表(关联artist服务的artist.id)
+-- ------------------------------
+CREATE TABLE `mv` (
+                      `id` INT AUTO_INCREMENT PRIMARY KEY,
+                      `mv_name` VARCHAR(100) NOT NULL COMMENT 'MV名称',
+                      `artist_id` INT NOT NULL COMMENT '关联artist服务的artist.id',
+                      `artist_name` VARCHAR(100) NOT NULL COMMENT '艺人名称',
+                      `cover_url` VARCHAR(255) NOT NULL COMMENT 'MV封面URL',
+                      `video_url` VARCHAR(255) NOT NULL COMMENT 'MV视频文件URL',
+                      `description` TEXT COMMENT 'MV描述',
+                      `tags` VARCHAR(255) COMMENT 'MV标签(多个用逗号分隔)',
+                      `duration` INT COMMENT '时长(秒)',
+                      `view_count` BIGINT DEFAULT 0 COMMENT '播放量',
+                      `like_count` BIGINT DEFAULT 0 COMMENT '点赞数',
+                      `comment_count` BIGINT DEFAULT 0 COMMENT '评论数',
+                      `share_count` BIGINT DEFAULT 0 COMMENT '分享数',
+                      `release_time` DATE COMMENT '发布时间',
+                      `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                      `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                      `status` TINYINT DEFAULT 0 COMMENT 'MV状态:0-审核中,1-审核失败,2-发布中,3-已上架,4-已下架',
+                      `audit_reason` VARCHAR(500) COMMENT '审核失败原因',
+                      `audit_time` DATETIME COMMENT '审核时间',
+                      `publish_time` DATETIME COMMENT '发布时间',
+                      `shelf_time` DATETIME COMMENT '上架时间',
+                      `off_shelf_time` DATETIME COMMENT '下架时间',
+                      `delete_flag` TINYINT DEFAULT 0 COMMENT '删除标志:0-未删除,1-已删除',
+                      `delete_time` DATETIME COMMENT '删除时间',
+                      INDEX `idx_mv_name` (`mv_name`),
+                      INDEX `idx_artist_id` (`artist_id`),
+                      INDEX `idx_status` (`status`),
+                      INDEX `idx_view_count` (`view_count`),
+                      INDEX `idx_delete_flag` (`delete_flag`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MV表';
+
+
+
+-- ------------------------------
+-- 14. MV收藏表(关联auth服务的user.id、本服务的mv.id)
+-- ------------------------------
+CREATE TABLE `mv_favorite` (
+                               `id` INT AUTO_INCREMENT PRIMARY KEY,
+                               `user_id` INT NOT NULL COMMENT '用户ID,关联auth服务的user.id',
+                               `mv_id` INT NOT NULL COMMENT 'MV ID,关联本服务的mv.id',
+                               `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '收藏时间',
+                               UNIQUE KEY `uk_user_mv` (`user_id`, `mv_id`),
+                               INDEX `idx_user_id` (`user_id`), -- 逻辑关联索引
+                               INDEX `idx_mv_id` (`mv_id`),
+                               FOREIGN KEY (`mv_id`) REFERENCES `mv` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MV收藏表';
+
+
+-- ------------------------------
+-- 15. 评论表(关联auth服务的user.id、本服务的song.id或mv.id)
+-- ------------------------------
+CREATE TABLE `comment` (
+                           `id` INT AUTO_INCREMENT PRIMARY KEY,
+                           `user_id` INT NOT NULL COMMENT '评论用户ID,关联auth服务的user.id',
+                           `content_type` TINYINT NOT NULL COMMENT '内容类型:1-歌曲,2-MV',
+                           `content_id` INT NOT NULL COMMENT '关联内容ID,根据content_type关联song.id或mv.id',
+                           `parent_id` INT DEFAULT NULL COMMENT '父评论ID,用于回复评论,NULL表示顶级评论',
+                           `content` TEXT NOT NULL COMMENT '评论内容',
+                           `like_count` INT DEFAULT 0 COMMENT '点赞数',
+                           `reply_count` INT DEFAULT 0 COMMENT '回复数',
+                           `status` TINYINT DEFAULT 1 COMMENT '评论状态:0-已删除,1-正常,2-审核中,3-审核不通过',
+                           `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                           `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    -- 普通索引(保留原逻辑,优化查询效率)
+                           INDEX `idx_user_id` (`user_id`), -- 逻辑关联索引,快速查询某用户的所有评论
+                           INDEX `idx_content_type` (`content_type`), -- 快速按内容类型筛选评论
+                           INDEX `idx_content_id` (`content_id`), -- 快速按内容ID筛选评论
+                           INDEX `idx_parent_id` (`parent_id`), -- 快速查询某评论的所有回复
+                           INDEX `idx_create_time` (`create_time`), -- 快速按创建时间排序/筛选评论
+    -- 复合索引(优化联合查询,比单独索引更高效)
+                           INDEX `idx_content_type_id` (`content_type`, `content_id`) -- 便于按内容类型+内容ID批量查询评论
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评论表(支持歌曲和MV评论)';

+ 1 - 0
content/pom.xml

@@ -78,6 +78,7 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-redis</artifactId>
         </dependency>
+
         <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-oauth2</artifactId>

+ 78 - 0
content/src/main/java/com/content/controller/CommentController.java

@@ -0,0 +1,78 @@
+package com.content.controller;
+
+
+import com.base.utils.Result;
+import com.content.config.SecurityUtil;
+import com.content.domain.dto.ADdCommentDto;
+import com.content.domain.dto.getCommentListDto;
+import com.content.domain.po.Comment;
+import com.content.domain.vo.CommentVO;
+import com.content.service.ICommentService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 评论表(支持歌曲和MV评论) 前端控制器
+ * </p>
+ *
+ * @author xiang
+ * @since 2026-01-03
+ */
+@RestController
+@RequestMapping("/comment")
+public class CommentController {
+    @Autowired
+    private ICommentService commentService;
+
+    @PostMapping("/add")
+    @PreAuthorize("hasRole('ROLE_USER')")
+    public Result add(@RequestBody ADdCommentDto aDdCommentDto) {
+
+        commentService.saveComent(aDdCommentDto);
+        return Result.success("添加成功");
+    }
+
+    @GetMapping("/delete")
+    @PreAuthorize("hasRole('ROLE_USER')")
+    public Result delete(@RequestParam Integer id) {
+        commentService.removeCommentByid(id);
+        return Result.success("删除成功");
+    }
+
+    @GetMapping("AddlikeCount")
+    @PreAuthorize("hasRole('ROLE_USER')")
+    public Result AddlikeCount(@RequestParam Integer id) {
+        Comment comment = commentService.getById(id);
+        comment.setLikeCount(comment.getLikeCount() + 1);
+        commentService.updateById(comment);
+        return Result.success("点赞成功");
+    }
+
+    //取消点赞
+    @GetMapping("CancelLikeCount")
+    @PreAuthorize("hasRole('ROLE_USER')")
+    public Result CancelLikeCount(@RequestParam Integer id) {
+        Comment comment = commentService.getById(id);
+        comment.setLikeCount(Math.max(0, comment.getLikeCount() - 1));
+        commentService.updateById(comment);
+        return Result.success("取消点赞成功");
+    }
+
+    @PostMapping("/getCommentList")
+    public Result getCommentList(@RequestBody getCommentListDto getCommentListDto) {
+        List<Comment> list = commentService.lambdaQuery()
+                .eq(Comment::getContentId, getCommentListDto.getContentId())
+                .eq(Comment::getContentType, getCommentListDto.getType()).isNull(Comment::getParentId).eq(Comment::getStatus, 1).list();
+        return Result.success(list);
+    }
+    @GetMapping("/listByPid")
+    public Result listByPid(@RequestParam Integer id) {
+        List<Comment> list = commentService.lambdaQuery()
+                .eq(Comment::getParentId, id).eq(Comment::getStatus, 1).list();
+        return Result.success(list);
+    }
+}

+ 95 - 0
content/src/main/java/com/content/controller/MvController.java

@@ -0,0 +1,95 @@
+package com.content.controller;
+
+import com.base.utils.Result;
+import com.content.domain.dto.ApplyDto;
+import com.content.domain.dto.MvUploadDto;
+import com.content.domain.dto.PageQueryDto;
+import com.content.domain.po.Mv;
+import com.content.service.IMvService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/Mv")
+@Slf4j
+public class MvController {
+    @Autowired
+    private IMvService mvService;
+
+    @PostMapping("/add")
+    public Result add(@RequestBody MvUploadDto mvUploadDto) {
+        Mv mv = new Mv();
+        BeanUtils.copyProperties(mvUploadDto, mv);
+        System.out.println("mv = " + mv);
+        boolean save = mvService.save(mv);
+        if (!save) {
+            return Result.fail("添加失败");
+        }
+        return Result.success("添加成功");
+    }
+
+    @PostMapping("/list")
+    public Result list(@RequestBody PageQueryDto pageQueryDto) {
+        List<Mv> list = mvService.lambdaQuery().eq(Mv::getArtistId, pageQueryDto.getId()).eq(Mv::getStatus, pageQueryDto.getStatus()).list();
+        return Result.success(list);
+    }
+
+    @PostMapping("/delete")
+    public Result delete(@RequestParam Integer id) {
+        boolean delete = mvService.removeById(id);
+        if (!delete) {
+            return Result.fail("删除失败");
+        }
+        return Result.success("删除成功");
+    }
+
+    @PostMapping("/audit")
+    @PreAuthorize("hasRole('ROLE_ADMIN')")
+    public Result audit(@RequestBody ApplyDto applyDto) {
+        mvService.updateApply(applyDto);
+        return Result.success("ok");
+    }
+
+    @PostMapping("/UserChangeStatus")
+    @PreAuthorize("hasRole('ROLE_USER')")
+    public Result UserChangeStatus(@RequestBody PageQueryDto pageQueryDto) {
+        Mv one = mvService.lambdaQuery().eq(Mv::getId, pageQueryDto.getId()).one();
+        if(one.getStatus()==0){
+            return Result.fail("内容审核中");
+        }
+        mvService.lambdaUpdate().eq(Mv::getId, pageQueryDto.getId()).set(Mv::getStatus, pageQueryDto.getStatus()).update();
+        return Result.success("ok");
+    }
+
+    // 添加根据状态获取 MV 列表接口
+    @GetMapping("/list")
+    @PreAuthorize("hasRole('ROLE_ADMIN')")
+    public Result list(@RequestParam Integer status) {
+        if (status == 6) {
+            // status不为0
+            List<Mv> list = mvService.lambdaQuery().ne(Mv::getStatus, 0).eq(Mv::getDeleteFlag, 0).list();
+            return Result.success(list);
+        }
+        List<Mv> list = mvService.lambdaQuery().eq(Mv::getStatus, status).eq(Mv::getDeleteFlag, 0).list();
+        return Result.success(list);
+    }
+
+    //    歌手页面获取Mv
+    @GetMapping("/Mvlist")
+    @PreAuthorize("hasRole('ROLE_USER')")
+    public Result Mvlist(@RequestParam Integer id) {
+        List<Mv> list = mvService.lambdaQuery().eq(Mv::getArtistId, id).eq(Mv::getDeleteFlag, 0).eq(Mv::getStatus, 3).list();
+        return Result.success(list);
+    }
+    @GetMapping("/getMvById")
+    @PreAuthorize("hasRole('ROLE_USER')")
+    public Result getMvById(@RequestParam Integer id) {
+        Mv one = mvService.lambdaQuery().eq(Mv::getId, id).eq(Mv::getDeleteFlag, 0).eq(Mv::getStatus, 3).one();
+        return Result.success(one);
+    }
+}

+ 55 - 0
content/src/main/java/com/content/controller/MvFavoriteController.java

@@ -0,0 +1,55 @@
+package com.content.controller;
+import com.base.utils.Result;
+import com.content.config.SecurityUtil;
+import com.content.domain.po.Mv;
+import com.content.domain.po.MvFavorite;
+import com.content.service.IMvFavoriteService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import java.util.List;
+/**
+ * <p>
+ * MV收藏表 前端控制器
+ * </p>
+ *
+ * @author xiang
+ * @since 2026-01-03
+ */
+@RestController
+@RequestMapping("/mv-favorite")
+public class MvFavoriteController {
+    @Autowired
+    private IMvFavoriteService mvFavoriteService;
+    @GetMapping("/add")
+    public Result add(@RequestParam Integer mvId) {
+        Integer id = SecurityUtil.getUser().getId();
+        List<MvFavorite> list = mvFavoriteService.lambdaQuery().eq(MvFavorite::getMvId, mvId).eq(MvFavorite::getUserId, id).list();
+        if (list.size() > 0) {
+            mvFavoriteService.removeById(list.get(0).getId());
+            return Result.success("已取消收藏");
+        }
+        MvFavorite mvFavorite = new MvFavorite();
+        mvFavorite.setMvId(mvId);
+        mvFavorite.setUserId(id);
+        mvFavoriteService.save(mvFavorite);
+        return Result.success("收藏成功");
+    }
+    @GetMapping("query")
+    public Result query(@RequestParam Integer mvId) {
+        Integer id = SecurityUtil.getUser().getId();
+        List<MvFavorite> list = mvFavoriteService.lambdaQuery().eq(MvFavorite::getMvId, mvId).eq(MvFavorite::getUserId, id).list();
+        if (list.size() > 0) {
+            return Result.success(1);
+        }
+        return Result.success(0);
+    }
+    @GetMapping("/list")
+    public Result list() {
+        Integer id = SecurityUtil.getUser().getId();
+        List<Mv> favoriteList = mvFavoriteService.getFavoriteList(id);
+        return Result.success(favoriteList);
+    }
+}

+ 12 - 0
content/src/main/java/com/content/domain/dto/ADdCommentDto.java

@@ -0,0 +1,12 @@
+package com.content.domain.dto;
+import lombok.Data;
+
+@Data
+public class ADdCommentDto {
+    private Integer type;
+    private String value;
+    private Integer contentId;
+    private Integer parentId;
+    private String src;
+    private String username;
+}

+ 14 - 0
content/src/main/java/com/content/domain/dto/MvUploadDto.java

@@ -0,0 +1,14 @@
+package com.content.domain.dto;
+
+import lombok.Data;
+
+@Data
+public class MvUploadDto {
+    private String mvName;      // MV名称
+    private String description; // 描述
+    private String tags;        // 标签
+    private String coverUrl;       // 封面URL
+    private String videoUrl;       // 视频URL
+    private Integer artistId;   // 艺人ID
+    private String artistName;  // 艺人名称
+}

+ 9 - 0
content/src/main/java/com/content/domain/dto/getCommentListDto.java

@@ -0,0 +1,9 @@
+package com.content.domain.dto;
+
+import lombok.Data;
+
+@Data
+public class getCommentListDto {
+    private Integer type;
+    private Integer contentId;
+}

+ 68 - 0
content/src/main/java/com/content/domain/po/Comment.java

@@ -0,0 +1,68 @@
+package com.content.domain.po;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * <p>
+ * 评论表(支持歌曲和MV评论)
+ * </p>
+ *
+ * @author xiang
+ * @since 2026-01-03
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@TableName("comment")
+@ApiModel(value="Comment对象", description="评论表(支持歌曲和MV评论)")
+public class Comment implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "评论用户ID,关联auth服务的user.id")
+    private Integer userId;
+
+    @ApiModelProperty(value = "内容类型:1-歌曲,2-MV")
+    private Integer contentType;
+    @ApiModelProperty(value = "头像")
+    private String src;
+    @ApiModelProperty(value = "姓名")
+    private String username;
+    @ApiModelProperty(value = "关联内容ID,根据content_type关联song.id或mv.id")
+    private Integer contentId;
+
+    @ApiModelProperty(value = "父评论ID,用于回复评论,NULL表示顶级评论")
+    private Integer parentId;
+
+    @ApiModelProperty(value = "评论内容")
+    private String content;
+
+    @ApiModelProperty(value = "点赞数")
+    private Integer likeCount;
+
+    @ApiModelProperty(value = "回复数")
+    private Integer replyCount;
+
+    @ApiModelProperty(value = "评论状态:0-已删除,1-正常")
+    private Integer status;
+
+    @ApiModelProperty(value = "创建时间")
+    private LocalDateTime createTime;
+
+    @ApiModelProperty(value = "更新时间")
+    private LocalDateTime updateTime;
+
+
+}

+ 103 - 0
content/src/main/java/com/content/domain/po/Mv.java

@@ -0,0 +1,103 @@
+package com.content.domain.po;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+import java.time.LocalDate;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+/**
+ * <p>
+ * MV表
+ * </p>
+ *
+ * @author xiang
+ * @since 2025-12-30
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@TableName("mv")
+@ApiModel(value="Mv对象", description="MV表")
+public class Mv implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "MV名称")
+    private String mvName;
+
+    @ApiModelProperty(value = "关联artist服务的artist.id")
+    private Integer artistId;
+
+    @ApiModelProperty(value = "艺人名称")
+    private String artistName;
+
+    @ApiModelProperty(value = "MV封面URL")
+    private String coverUrl;
+
+    @ApiModelProperty(value = "MV视频文件URL")
+    private String videoUrl;
+
+    @ApiModelProperty(value = "MV描述")
+    private String description;
+
+    @ApiModelProperty(value = "MV标签(多个用逗号分隔)")
+    private String tags;
+
+    @ApiModelProperty(value = "时长(秒)")
+    private Integer duration;
+
+    @ApiModelProperty(value = "播放量")
+    private Long viewCount;
+
+    @ApiModelProperty(value = "点赞数")
+    private Long likeCount;
+
+    @ApiModelProperty(value = "评论数")
+    private Long commentCount;
+
+    @ApiModelProperty(value = "分享数")
+    private Long shareCount;
+
+    @ApiModelProperty(value = "发布时间")
+    private LocalDate releaseTime;
+
+    @ApiModelProperty(value = "创建时间")
+    private LocalDateTime createTime;
+
+    @ApiModelProperty(value = "更新时间")
+    private LocalDateTime updateTime;
+
+    @ApiModelProperty(value = "MV状态:0-审核中,1-审核失败,2-发布中,3-已上架,4-已下架")
+    private Integer status;
+
+    @ApiModelProperty(value = "审核失败原因")
+    private String auditReason;
+
+    @ApiModelProperty(value = "审核时间")
+    private LocalDateTime auditTime;
+
+    @ApiModelProperty(value = "发布时间")
+    private LocalDateTime publishTime;
+
+    @ApiModelProperty(value = "上架时间")
+    private LocalDateTime shelfTime;
+
+    @ApiModelProperty(value = "下架时间")
+    private LocalDateTime offShelfTime;
+
+    @ApiModelProperty(value = "删除标志:0-未删除,1-已删除")
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "删除时间")
+    private LocalDateTime deleteTime;
+
+
+}

+ 44 - 0
content/src/main/java/com/content/domain/po/MvFavorite.java

@@ -0,0 +1,44 @@
+package com.content.domain.po;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * <p>
+ * MV收藏表
+ * </p>
+ *
+ * @author xiang
+ * @since 2026-01-03
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@TableName("mv_favorite")
+@ApiModel(value="MvFavorite对象", description="MV收藏表")
+public class MvFavorite implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "用户ID,关联auth服务的user.id")
+    private Integer userId;
+
+    @ApiModelProperty(value = "MV ID,关联本服务的mv.id")
+    private Integer mvId;
+
+    @ApiModelProperty(value = "收藏时间")
+    private LocalDateTime createTime;
+
+
+}

+ 13 - 0
content/src/main/java/com/content/domain/vo/CommentVO.java

@@ -0,0 +1,13 @@
+package com.content.domain.vo;
+
+import lombok.Data;
+
+@Data
+public class CommentVO {
+    private Integer id;
+    private String src;
+    private String username;
+    private String content;
+    private String time;
+    private Integer like;
+}

+ 23 - 0
content/src/main/java/com/content/mapper/CommentMapper.java

@@ -0,0 +1,23 @@
+package com.content.mapper;
+
+import com.content.domain.dto.getCommentListDto;
+import com.content.domain.po.Comment;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.content.domain.vo.CommentVO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 评论表(支持歌曲和MV评论) Mapper 接口
+ * </p>
+ *
+ * @author xiang
+ * @since 2026-01-03
+ */
+@Mapper
+public interface CommentMapper extends BaseMapper<Comment> {
+
+
+}

+ 22 - 0
content/src/main/java/com/content/mapper/MvFavoriteMapper.java

@@ -0,0 +1,22 @@
+package com.content.mapper;
+
+import com.content.domain.po.Mv;
+import com.content.domain.po.MvFavorite;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * <p>
+ * MV收藏表 Mapper 接口
+ * </p>
+ *
+ * @author xiang
+ * @since 2026-01-03
+ */
+@Mapper
+public interface MvFavoriteMapper extends BaseMapper<MvFavorite> {
+
+    List<Mv> getFavoriteList(Integer id);
+}

+ 18 - 0
content/src/main/java/com/content/mapper/MvMapper.java

@@ -0,0 +1,18 @@
+package com.content.mapper;
+
+import com.content.domain.po.Mv;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * MV表 Mapper 接口
+ * </p>
+ *
+ * @author xiang
+ * @since 2025-12-30
+ */
+@Mapper
+public interface MvMapper extends BaseMapper<Mv> {
+
+}

+ 25 - 0
content/src/main/java/com/content/service/ICommentService.java

@@ -0,0 +1,25 @@
+package com.content.service;
+
+import com.content.domain.dto.ADdCommentDto;
+import com.content.domain.dto.getCommentListDto;
+import com.content.domain.po.Comment;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.content.domain.vo.CommentVO;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 评论表(支持歌曲和MV评论) 服务类
+ * </p>
+ *
+ * @author xiang
+ * @since 2026-01-03
+ */
+public interface ICommentService extends IService<Comment> {
+
+    void removeCommentByid(Integer id);
+
+
+    void saveComent(ADdCommentDto aDdCommentDto);
+}

+ 20 - 0
content/src/main/java/com/content/service/IMvFavoriteService.java

@@ -0,0 +1,20 @@
+package com.content.service;
+
+import com.content.domain.po.Mv;
+import com.content.domain.po.MvFavorite;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+ * <p>
+ * MV收藏表 服务类
+ * </p>
+ *
+ * @author xiang
+ * @since 2026-01-03
+ */
+public interface IMvFavoriteService extends IService<MvFavorite> {
+
+    List<Mv> getFavoriteList(Integer id);
+}

+ 18 - 0
content/src/main/java/com/content/service/IMvService.java

@@ -0,0 +1,18 @@
+package com.content.service;
+
+import com.content.domain.dto.ApplyDto;
+import com.content.domain.po.Mv;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * MV表 服务类
+ * </p>
+ *
+ * @author xiang
+ * @since 2025-12-30
+ */
+public interface IMvService extends IService<Mv> {
+
+    void updateApply(ApplyDto applyDto);
+}

+ 51 - 0
content/src/main/java/com/content/service/impl/CommentServiceImpl.java

@@ -0,0 +1,51 @@
+package com.content.service.impl;
+
+import com.content.config.SecurityUtil;
+import com.content.domain.dto.ADdCommentDto;
+import com.content.domain.dto.getCommentListDto;
+import com.content.domain.po.Comment;
+import com.content.domain.vo.CommentVO;
+import com.content.mapper.CommentMapper;
+import com.content.service.ICommentService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 评论表(支持歌曲和MV评论) 服务实现类
+ * </p>
+ *
+ * @author xiang
+ * @since 2026-01-03
+ */
+@Service
+public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements ICommentService {
+    @Override
+    @Transactional
+    public void removeCommentByid(Integer id) {
+        this.removeById(id);
+        this.lambdaUpdate().eq(Comment::getParentId, id).remove();
+    }
+
+    @Override
+    @Transactional
+    public void saveComent(ADdCommentDto aDdCommentDto) {
+        Integer id = SecurityUtil.getUser().getId();
+        Comment comment = new Comment();
+        comment.setUserId(id);
+        comment.setContentType(aDdCommentDto.getType());
+        comment.setContentId(aDdCommentDto.getContentId());
+        comment.setParentId(aDdCommentDto.getParentId());
+        comment.setContent(aDdCommentDto.getValue());
+        comment.setSrc(aDdCommentDto.getSrc());
+        comment.setUsername(aDdCommentDto.getUsername());
+        if(aDdCommentDto.getParentId()!=null){
+            Comment parentComment = this.getById(aDdCommentDto.getParentId());
+            this.lambdaUpdate().eq(Comment::getId, aDdCommentDto.getParentId()).set(Comment::getReplyCount, parentComment.getReplyCount()+1).update();
+        }
+        this.save(comment);
+    }
+}

+ 27 - 0
content/src/main/java/com/content/service/impl/MvFavoriteServiceImpl.java

@@ -0,0 +1,27 @@
+package com.content.service.impl;
+
+import com.content.domain.po.Mv;
+import com.content.domain.po.MvFavorite;
+import com.content.mapper.MvFavoriteMapper;
+import com.content.service.IMvFavoriteService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * <p>
+ * MV收藏表 服务实现类
+ * </p>
+ *
+ * @author xiang
+ * @since 2026-01-03
+ */
+@Service
+public class MvFavoriteServiceImpl extends ServiceImpl<MvFavoriteMapper, MvFavorite> implements IMvFavoriteService {
+
+    @Override
+    public List<Mv> getFavoriteList(Integer id) {
+        return baseMapper.getFavoriteList(id);
+    }
+}

+ 31 - 0
content/src/main/java/com/content/service/impl/MvServiceImpl.java

@@ -0,0 +1,31 @@
+package com.content.service.impl;
+
+import com.content.domain.dto.ApplyDto;
+import com.content.domain.po.Mv;
+import com.content.mapper.MvMapper;
+import com.content.service.IMvService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * <p>
+ * MV表 服务实现类
+ * </p>
+ *
+ * @author xiang
+ * @since 2025-12-30
+ */
+@Service
+public class MvServiceImpl extends ServiceImpl<MvMapper, Mv> implements IMvService {
+
+    @Override
+    @Transactional
+    public void updateApply(ApplyDto applyDto) {
+        this.lambdaUpdate()
+                .eq(Mv::getId, applyDto.getId())
+                .set(Mv::getStatus, applyDto.getStatus())
+                .set(Mv::getAuditReason, applyDto.getValue())
+                .update();
+    }
+}

+ 1 - 1
media/src/main/resources/mapper/ArtistRealAuthMapper.xml → content/src/main/resources/mapper/CommentMapper.xml

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="com.media.mapper.ArtistRealAuthMapper">
+<mapper namespace="com.content.mapper.CommentMapper">
 
 </mapper>

+ 14 - 0
content/src/main/resources/mapper/MvFavoriteMapper.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.content.mapper.MvFavoriteMapper">
+
+    <select id="getFavoriteList" resultType="com.content.domain.po.Mv" parameterType="java.lang.Integer">
+        SELECT m.*
+        FROM mv m
+                 INNER JOIN mv_favorite mf ON m.id = mf.mv_id
+        WHERE mf.user_id = #{id}
+          AND m.delete_flag = 0
+          AND m.status = 3
+        ORDER BY mf.create_time DESC
+    </select>
+</mapper>

+ 5 - 0
content/src/main/resources/mapper/MvMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.content.mapper.MvMapper">
+
+</mapper>

+ 4 - 121
media/media.sql

@@ -12,7 +12,7 @@ CREATE TABLE `media_file` (
                               `file_size` BIGINT COMMENT '文件大小(字节)',
                               `bucket_name` VARCHAR(100) NOT NULL COMMENT '存储桶名称',
                               `file_url` VARCHAR(500) NOT NULL COMMENT '文件访问URL',
-                              `storage_type` VARCHAR(20) DEFAULT 'minio' COMMENT '存储类型(oss、local、minio等)',
+                              `storage_type` VARCHAR(20) DEFAULT 'oss' COMMENT '存储类型(oss、local、minio等)',
                               `status` TINYINT DEFAULT 0 COMMENT '文件状态:0-正常,1-已删除(逻辑删除)',
                               `md5` VARCHAR(32) COMMENT '文件MD5值',
                               `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
@@ -31,126 +31,9 @@ CREATE TABLE `artist_media_history` (
                                         `artist_id` INT NOT NULL COMMENT '关联artist服务的artist.id',
                                         `media_type` VARCHAR(20) NOT NULL COMMENT '媒体类型: avatar、header_image等',
                                         `media_file_id` BIGINT NOT NULL COMMENT '关联本服务的media_file.id',
-                                        `is_current` TINYINT DEFAULT 1 COMMENT '是否为当前使用版本:0-否,1-是',
+                                        `is_current` TINYINT DEFAULT 0 COMMENT '是否为当前使用版本:0-否,1-是',
                                         `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
                                         FOREIGN KEY (`media_file_id`) REFERENCES `media_file` (`id`) ON DELETE RESTRICT,
                                         UNIQUE KEY `uk_artist_media_current` (`artist_id`, `media_type`, `is_current`),
-                                        INDEX `idx_artist_id` (`artist_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='艺人媒体文件历史记录表';
-
--- ------------------------------
--- 3. 分块上传信息表
--- ------------------------------
-CREATE TABLE `chunk_info` (
-                              `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
-                              `file_md5` VARCHAR(32) NOT NULL COMMENT '文件MD5',
-                              `file_name` VARCHAR(255) NOT NULL COMMENT '原始文件名',
-                              `chunk_index` INT NOT NULL COMMENT '分块索引',
-                              `total_chunks` INT NOT NULL COMMENT '总分块数',
-                              `chunk_url` VARCHAR(500) COMMENT '分块文件URL',
-                              `status` TINYINT DEFAULT 0 COMMENT '状态:0-待上传,1-已上传',
-                              `uploaded_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
-                              `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-                              INDEX `idx_file_md5` (`file_md5`),
-                              INDEX `idx_chunk_index` (`chunk_index`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分块上传信息表';
-
--- ------------------------------
--- 4. 媒体处理任务表
--- ------------------------------
-CREATE TABLE `media_process` (
-                                 `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
-                                 `file_id` VARCHAR(32) NOT NULL COMMENT '文件ID',
-                                 `file_name` VARCHAR(255) NOT NULL COMMENT '文件名',
-                                 `bucket` VARCHAR(100) NOT NULL COMMENT '存储桶',
-                                 `file_path` VARCHAR(500) NOT NULL COMMENT '文件路径',
-                                 `status` VARCHAR(10) DEFAULT '0' COMMENT '任务状态: 0-待处理, 1-处理中, 2-处理成功, 3-处理失败',
-                                 `fail_count` INT DEFAULT 0 COMMENT '失败次数',
-                                 `errormsg` VARCHAR(1000) COMMENT '错误信息',
-                                 `url` VARCHAR(500) COMMENT '处理后文件URL',
-                                 `finish_date` DATETIME COMMENT '完成时间',
-                                 `create_date` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-                                 `process_definition_id` VARCHAR(50) COMMENT '流程定义ID',
-                                 `tag` VARCHAR(50) COMMENT '标签',
-                                 INDEX `idx_status` (`status`),
-                                 INDEX `idx_file_id` (`file_id`),
-                                 INDEX `idx_create_date` (`create_date`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='媒体处理任务表';
-
--- ------------------------------
--- 5. 媒体处理历史表
--- ------------------------------
-CREATE TABLE `media_process_history` (
-                                         `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
-                                         `process_id` BIGINT COMMENT '对应的任务ID',
-                                         `file_id` VARCHAR(32) NOT NULL COMMENT '文件ID',
-                                         `file_name` VARCHAR(255) NOT NULL COMMENT '文件名',
-                                         `bucket` VARCHAR(100) NOT NULL COMMENT '存储桶',
-                                         `file_path` VARCHAR(500) NOT NULL COMMENT '文件路径',
-                                         `status` VARCHAR(10) COMMENT '处理状态',
-                                         `errormsg` VARCHAR(1000) COMMENT '错误信息',
-                                         `url` VARCHAR(500) COMMENT '处理后URL',
-                                         `finish_date` DATETIME COMMENT '完成时间',
-                                         `create_date` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-                                         INDEX `idx_process_id` (`process_id`),
-                                         INDEX `idx_file_id` (`file_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='媒体处理历史表';
-
--- ------------------------------
--- 6. 视频文件表(用于存储视频相关信息)
--- ------------------------------
-CREATE TABLE `media_video` (
-                               `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
-                               `original_name` VARCHAR(255) NOT NULL COMMENT '原始文件名',
-                               `file_name` VARCHAR(255) NOT NULL COMMENT '存储文件名',
-                               `file_url` VARCHAR(500) COMMENT '视频文件URL',
-                               `preview_gif_url` VARCHAR(500) COMMENT '预览GIF URL',
-                               `file_size` BIGINT COMMENT '文件大小',
-                               `duration` VARCHAR(20) COMMENT '视频时长',
-                               `singer` VARCHAR(100) COMMENT '上传歌手',
-                               `bucket_name` VARCHAR(50) COMMENT '存储桶名',
-                               `status` TINYINT DEFAULT 0 COMMENT '状态:0-待处理,1-处理中,2-已完成,3-处理失败',
-                               `process_progress` DECIMAL(5,2) DEFAULT 0.00 COMMENT '处理进度(%)',
-                               `retry_count` TINYINT DEFAULT 0 COMMENT '重试次数',
-                               `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-                               `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-                               `processed_time` TIMESTAMP NULL COMMENT '处理完成时间',
-                               INDEX `idx_status` (`status`),
-                               INDEX `idx_created_time` (`created_time`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='视频文件表';
-
--- ------------------------------
--- 7. 视频处理队列表
--- ------------------------------
-CREATE TABLE `video_process_queue` (
-                                       `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
-                                       `video_id` BIGINT NOT NULL COMMENT '视频ID',
-                                       `priority` TINYINT DEFAULT 1 COMMENT '优先级:1-普通,2-高',
-                                       `status` TINYINT DEFAULT 0 COMMENT '0-待处理,1-已调度,2-已完成',
-                                       `scheduled_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '调度时间',
-                                       INDEX `idx_status_priority` (`status`, `priority`, `scheduled_time`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='视频处理队列表';
-
--- ------------------------------
--- 8. 系统配置表
--- ------------------------------
-CREATE TABLE `sys_config` (
-                              `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
-                              `config_key` VARCHAR(100) NOT NULL UNIQUE COMMENT '配置键',
-                              `config_value` TEXT COMMENT '配置值',
-                              `description` VARCHAR(255) COMMENT '配置描述',
-                              `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-                              `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-                              INDEX `idx_config_key` (`config_key`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统配置表';
-
--- ------------------------------
--- 9. 初始化系统配置
--- ------------------------------
-INSERT INTO `sys_config` (`config_key`, `config_value`, `description`) VALUES
-                                                                           ('ffmpeg.path', '/usr/local/bin/ffmpeg', 'FFmpeg可执行文件路径'),
-                                                                           ('video.max_size', '104857600', '视频最大上传大小(字节),100MB'),
-                                                                           ('chunk.size', '2097152', '分块大小(字节),2MB'),
-                                                                           ('max.concurrent.tasks', '5', '最大并发处理任务数'),
-                                                                           ('max.retry.count', '3', '最大重试次数'),
-                                                                           ('temp.dir', '/tmp/media_temp', '临时文件目录');
+                                        INDEX `idx_artist_id` (`artist_id`) -- 逻辑关联索引
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='艺人媒体文件历史记录表';

+ 16 - 4
media/pom.xml

@@ -57,14 +57,26 @@
             <artifactId>okhttp</artifactId>
             <version>4.8.1</version>
         </dependency>
-<!--        swagger         -->
         <dependency>
-            <groupId>com.spring4all</groupId>
-            <artifactId>swagger-spring-boot-starter</artifactId>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-validation</artifactId>
+            <artifactId>spring-boot-starter-amqp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.xuxueli</groupId>
+            <artifactId>xxl-job-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.bytedeco</groupId>
+            <artifactId>javacv</artifactId>
+            <version>1.5.8</version>
         </dependency>
     </dependencies>
 </project>

+ 41 - 0
media/src/main/java/com/media/Listener/MergeFileListener.java

@@ -0,0 +1,41 @@
+package com.media.Listener;
+
+import com.media.domain.dto.MergeDto;
+import com.media.service.FileUploadService;
+import com.media.util.upload.MinioFileUploader;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.amqp.core.Message;
+import org.springframework.amqp.rabbit.annotation.RabbitHandler;
+import org.springframework.amqp.rabbit.annotation.RabbitListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import com.rabbitmq.client.Channel;
+
+import java.io.IOException;
+@Slf4j
+@Component
+@RabbitListener(queues = "merge.queue")
+public class MergeFileListener {
+    
+    @Autowired
+    private FileUploadService fileUploadService;
+    @Autowired
+    private MinioFileUploader minioFileUploader;
+    @RabbitHandler
+    public void handleMergeRequest(MergeDto mergeDto, Channel channel, Message message) {
+        try {
+            // 执行文件合并
+            minioFileUploader.mergeFile(mergeDto);
+            // 确认消息
+            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
+        } catch (Exception e) {
+            log.error("合并文件失败: {}", mergeDto.getFileKey(), e);
+            try {
+                // 拒绝消息并重新入队
+                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
+            } catch (IOException ioEx) {
+                log.error("消息确认失败", ioEx);
+            }
+        }
+    }
+}

+ 34 - 0
media/src/main/java/com/media/config/ErrorMessageasConfiguration.java

@@ -0,0 +1,34 @@
+//package com.media.config;
+//import lombok.RequiredArgsConstructor;
+//import org.springframework.amqp.core.Binding;
+//import org.springframework.amqp.core.BindingBuilder;
+//import org.springframework.amqp.core.DirectExchange;
+//import org.springframework.amqp.core.Queue;
+//import org.springframework.amqp.rabbit.core.RabbitTemplate;
+//import org.springframework.amqp.rabbit.retry.MessageRecoverer;
+//import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer;
+//import org.springframework.context.annotation.Bean;
+//import org.springframework.context.annotation.Configuration;
+//
+//@Configuration
+//@RequiredArgsConstructor
+//public class ErrorMessageasConfiguration {
+//    private  final RabbitTemplate rabbitTemplate;
+//    @Bean
+//    public DirectExchange errExchange(){
+//        return new DirectExchange("error.direct");
+//    }
+//    @Bean
+//    public Queue errorQueue(){
+//        return new Queue("error.queue");
+//    }
+//    @Bean
+//    public Binding bindingerrorExchange(Queue errorQueue,DirectExchange errExchange){
+//        return BindingBuilder.bind(errorQueue).to(errExchange).with("error");
+//    }
+//    // 异常策略
+//    @Bean
+//    public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate){
+//        return new RepublishMessageRecoverer(rabbitTemplate,"error.direct","error");
+//    }
+//}

+ 18 - 0
media/src/main/java/com/media/config/MqConfig.java

@@ -0,0 +1,18 @@
+package com.media.config;
+
+import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
+import org.springframework.amqp.support.converter.MessageConverter;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MqConfig {
+    @Bean
+    public MessageConverter messageConverter(){
+        // 1.定义消息转换器
+        Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
+        // 2.配置自动创建消息id,用于识别不同消息,也可以在业务中基于ID判断是否是重复消息
+        jackson2JsonMessageConverter.setCreateMessageIds(true);
+        return jackson2JsonMessageConverter;
+    }
+}

+ 25 - 0
media/src/main/java/com/media/config/RabbitConfig.java

@@ -0,0 +1,25 @@
+package com.media.config;
+
+
+import org.springframework.amqp.core.*;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class RabbitConfig {
+
+    @Bean
+    public Queue mergeQueue() {
+        return QueueBuilder.durable("merge.queue").build();
+    }
+
+    @Bean
+    public DirectExchange mergeExchange() {
+        return ExchangeBuilder.directExchange("merge.exchange").build();
+    }
+
+    @Bean
+    public Binding mergeBinding(DirectExchange mergeExchange, Queue mergeQueue) {
+        return BindingBuilder.bind(mergeQueue).to(mergeExchange).with("merge.routing");
+    }
+}

+ 67 - 161
media/src/main/java/com/media/controller/FileUploadController.java

@@ -1,214 +1,120 @@
 package com.media.controller;
-
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.base.utils.Result;
-import com.media.domain.dto.MergeChunkDTO;
+import com.media.domain.dto.MergeDto;
 import com.media.domain.po.MediaFile;
-import com.media.domain.vo.UploadStatusVO;
 import com.media.service.FileUploadService;
 import com.media.service.IMediaFileService;
-import com.media.service.impl.ChunkUploadService;
-import com.media.util.enums.StorageBucket;
 import com.media.util.upload.MinioFileUploader;
+import com.media.util.enums.StorageBucket;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
-/**
- * 媒体文件上传控制器
- * 提供文件上传、分块上传、状态查询等接口
- */
+import java.util.List;
+
+import static com.media.util.enums.StorageBucket.VIDEO_FILES;
+import static com.media.util.enums.StorageBucket.Video_Temp;
 @RestController
 @RequestMapping("/media")
-@CrossOrigin(origins = "*")
 @Slf4j
 public class FileUploadController {
-
     @Autowired
     private FileUploadService fileUploadService;
-
-    @Autowired
-    private IMediaFileService mediaFileService;
-
-    @Autowired
-    private ChunkUploadService chunkUploadService;
-
     @Autowired
     private MinioFileUploader minioFileUploader;
-
+    @Autowired
+    private IMediaFileService mediaService;
     /**
-     * 普通文件上传接口(适用于3M以内文件)
+     * 文件上传接口(适用于3M以内文件)
      *
-     * @param file 上传的文件
-     * @param bucket 存储桶名称
+     * @param file       上传的文件
+     * @param bucket     存储桶名称
      * @param objectName 对象名称(可选)
-     * @param md5 文件MD5值(可选,用于校验)
      * @return 上传结果
      */
     @PostMapping("/upload")
-    public Result<String> uploadFile(
+    public Result uploadFile(
             @RequestParam("file") MultipartFile file,
             @RequestParam("bucket") StorageBucket bucket,
             @RequestParam(value = "objectName", required = false) String objectName,
             @RequestParam(value = "md5", required = false) String md5) {
-
         try {
-            String fileUrl = fileUploadService.uploadFile(file, bucket, objectName, md5);
-            return Result.success(fileUrl);
+            String s = fileUploadService.uploadFile(file, bucket, objectName, md5);
+            return Result.success(s);
         } catch (Exception e) {
+            // 记录详细错误日志
             log.error("文件上传失败", e);
             return Result.fail("文件上传失败: " + e.getMessage());
         }
     }
-
-    /**
-     * 分块上传接口 - 上传单个分块
-     *
-     * @param chunk 分块文件
-     * @param fileMd5 文件MD5
-     * @param chunkIndex 分块索引
-     * @param totalChunks 总分块数
-     * @param fileName 原始文件名
-     * @return 上传结果
-     */
-    @PostMapping("/chunk/upload")
-    public Result<String> uploadChunk(
-            @RequestParam("chunk") MultipartFile chunk,
-            @RequestParam("fileMd5") String fileMd5,
-            @RequestParam("chunkIndex") int chunkIndex,
-            @RequestParam("totalChunks") int totalChunks,
-            @RequestParam("fileName") String fileName) {
-
-        try {
-            String chunkUrl = chunkUploadService.uploadChunk(
-                    fileMd5, fileName, chunk, chunkIndex, totalChunks);
-            return Result.success(chunkUrl);
-        } catch (Exception e) {
-            log.error("分块上传失败", e);
-            return Result.fail("分块上传失败: " + e.getMessage());
+    // 分片上传接口
+    @PostMapping("/upload/part")
+    public Result uploadPart(
+            @RequestParam("file") MultipartFile file,
+            @RequestParam("fileKey") String fileKey,
+            @RequestParam("chunkNumber") Integer chunkNumber,
+            @RequestParam("totalChunks") Integer totalChunks) throws Exception {
+        boolean exists1 = minioFileUploader.checkObjectExists(VIDEO_FILES, fileKey);
+        if (exists1){
+            log.info("文件" + fileKey + "已存在,无需重复上传");
+            return Result.success("文件" + fileKey + "已存在,无需重复上传");
         }
-    }
+        // 检查分片是否已存在
+        String tempObjectName = "temp/" + fileKey + "/" + chunkNumber;
+        boolean exists = minioFileUploader.checkObjectExists(Video_Temp, tempObjectName);
 
-    /**
-     * 分块上传合并接口
-     *
-     * @param mergeDTO 合并参数
-     * @return 合并结果
-     */
-    @PostMapping("/chunk/merge")
-    public Result<String> mergeChunks(@RequestBody MergeChunkDTO mergeDTO) {
-        try {
-            String finalUrl = chunkUploadService.mergeChunks(
-                    mergeDTO.getFileMd5(),
-                    mergeDTO.getFileName(),
-                    mergeDTO.getBucket()
-            );
-            return Result.success(finalUrl);
-        } catch (Exception e) {
-            log.error("分块合并失败", e);
-            return Result.fail("分块合并失败: " + e.getMessage());
+        if (exists) {
+            log.info("分片" + chunkNumber + "已存在,无需重复上传");
+            return Result.success("分片" + chunkNumber + "已存在,无需重复上传");
         }
+        // 上传分片到MinIO
+        String fileUrl = minioFileUploader.uploadFile(
+                Video_Temp,
+                tempObjectName,
+                file.getInputStream(),
+                file.getContentType()
+        );
+
+        log.info("分片" + chunkNumber + "上传成功,文件路径:" + fileUrl);
+        return Result.success("分片" + chunkNumber + "上传成功");
     }
-
-    /**
-     * 查询分块上传状态
-     *
-     * @param fileMd5 文件MD5
-     * @return 上传状态信息
-     */
-    @GetMapping("/chunk/status")
-    public Result<UploadStatusVO> getUploadStatus(@RequestParam String fileMd5) {
-        try {
-            UploadStatusVO status = chunkUploadService.getUploadStatus(fileMd5);
-            return Result.success(status);
-        } catch (Exception e) {
-            log.error("查询上传状态失败", e);
-            return Result.fail("查询状态失败: " + e.getMessage());
-        }
-    }
-
     /**
      * 获取支持的存储桶列表
-     *
-     * @return 存储桶列表
      */
     @GetMapping("/buckets")
     public ResponseEntity<Result<StorageBucket[]>> getSupportedBuckets() {
         return ResponseEntity.ok(Result.success(StorageBucket.values()));
     }
-
-    /**
-     * 查询媒体文件列表
-     *
-     * @param current 页码
-     * @param size 页大小
-     * @param fileType 文件类型
-     * @return 文件列表
-     */
-    @GetMapping("/files")
-    public Result<IPage<MediaFile>> getFileList(
-            @RequestParam(defaultValue = "1") Long current,
-            @RequestParam(defaultValue = "10") Long size,
-            @RequestParam(required = false) String fileType) {
-
-        IPage<MediaFile> page = new Page<>(current, size);
-        LambdaQueryWrapper<MediaFile> queryWrapper = new LambdaQueryWrapper<>();
-
-        if (StringUtils.isNotBlank(fileType)) {
-            queryWrapper.eq(MediaFile::getFileType, fileType);
-        }
-
-        queryWrapper.eq(MediaFile::getStatus, 0) // 只查询正常状态的文件
-                .orderByDesc(MediaFile::getCreateTime);
-
-        IPage<MediaFile> result = mediaFileService.page(page, queryWrapper);
-        return Result.success(result);
-    }
-
-    /**
-     * 根据ID查询文件信息
-     *
-     * @param id 文件ID
-     * @return 文件信息
-     */
-    @GetMapping("/files/{id}")
-    public Result<MediaFile> getFileById(@PathVariable Long id) {
-        MediaFile file = mediaFileService.getById(id);
-        if (file == null) {
-            return Result.fail("文件不存在");
+    @PostMapping("/upload/merge")
+    public Result mergeParts(@RequestBody MergeDto mergeDto) {
+        try {
+            // 参数验证
+            if (mergeDto.getFileKey() == null || mergeDto.getTotalChunk() == null) {
+                return Result.fail("参数不完整");
+            }
+            // 发送合并消息到MQ
+            boolean success = fileUploadService.sendMergeMessage(mergeDto);
+            if (success) {
+                return Result.success("合并任务已提交");
+            } else {
+                return Result.fail("合并任务提交失败");
+            }
+        } catch (Exception e) {
+            log.error("合并请求处理失败", e);
+            return Result.fail("合并请求处理失败: " + e.getMessage());
         }
-        return Result.success(file);
     }
-
-    /**
-     * 删除文件(逻辑删除)
-     *
-     * @param id 文件ID
-     * @return 删除结果
-     */
-    @DeleteMapping("/files/{id}")
-    public Result<String> deleteFile(@PathVariable Long id) {
+    @GetMapping("/getFileUrlByMd5")
+    public Result getFileUrlByMd5(@RequestParam  String md5) {
         try {
-            MediaFile file = mediaFileService.getById(id);
-            if (file == null) {
-                return Result.fail("文件不存在");
-            }
-
-            // 逻辑删除
-            MediaFile updateFile = new MediaFile();
-            updateFile.setId(id);
-            updateFile.setStatus(1); // 已删除状态
-            mediaFileService.updateById(updateFile);
-
-            return Result.success("删除成功");
+            List<MediaFile> list = mediaService.lambdaQuery().eq(MediaFile::getMd5, md5).select(MediaFile::getFileUrl).list();
+            String fileUrl = list.get(0).getFileUrl();
+            return Result.success(fileUrl);
         } catch (Exception e) {
-            log.error("删除文件失败", e);
-            return Result.fail("删除失败: " + e.getMessage());
+            log.error("获取文件URL失败", e);
+            return Result.fail("获取文件URL失败: " + e.getMessage());
         }
     }
-}
+}

+ 0 - 14
media/src/main/java/com/media/domain/dto/MergeChunkDTO.java

@@ -1,14 +0,0 @@
-package com.media.domain.dto;
-
-import com.media.util.enums.StorageBucket;
-import lombok.Data;
-
-/**
- * 分块合并参数DTO
- */
-@Data
-public class MergeChunkDTO {
-    private String fileMd5;     // 文件MD5
-    private String fileName;    // 原始文件名
-    private StorageBucket bucket; // 存储桶
-}

+ 15 - 0
media/src/main/java/com/media/domain/dto/MergeDto.java

@@ -0,0 +1,15 @@
+package com.media.domain.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class MergeDto {
+    private String fileKey;        // 文件标识符
+    private Integer totalChunk;    // 总分片数
+    private String fileName;       // 原文件名
+    private String contentType;    // 文件类型
+}

+ 0 - 30
media/src/main/java/com/media/domain/po/ChunkInfo.java

@@ -1,30 +0,0 @@
-package com.media.domain.po;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-
-import java.io.Serializable;
-import java.time.LocalDateTime;
-
-/**
- * 分块信息实体类
- */
-@Data
-@TableName("chunk_info")
-public class ChunkInfo implements Serializable {
-    private static final long serialVersionUID = 1L;
-    
-    @TableId(type = IdType.AUTO)
-    private Long id;
-    
-    private String fileMd5;             // 文件MD5
-    private String fileName;            // 原始文件名
-    private Integer chunkIndex;         // 分块索引
-    private Integer totalChunks;        // 总分块数
-    private String chunkUrl;            // 分块文件URL
-    private Integer status;             // 状态:0-待上传,1-已上传
-    private LocalDateTime uploadedTime; // 上传时间
-    private LocalDateTime createTime;   // 创建时间
-}

+ 0 - 17
media/src/main/java/com/media/domain/vo/UploadStatusVO.java

@@ -1,17 +0,0 @@
-package com.media.domain.vo;
-
-import lombok.Data;
-
-/**
- * 上传状态返回VO
- */
-@Data
-public class UploadStatusVO {
-    private String fileMd5;         // 文件MD5
-    private String fileName;        // 文件名
-    private Integer totalChunks;    // 总分块数
-    private Integer uploadedChunks; // 已上传分块数
-    private String status;          // 状态:uploading, merged, failed
-    private String progress;        // 进度百分比
-    private String message;         // 状态消息
-}

+ 0 - 12
media/src/main/java/com/media/service/FileUploadService.java

@@ -1,12 +0,0 @@
-package com.media.service;
-
-import com.media.util.enums.StorageBucket;
-import org.springframework.web.multipart.MultipartFile;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-public interface FileUploadService {
-
-    String uploadFile(MultipartFile file, StorageBucket bucket,String objectName, String md5) throws Exception;
-}

+ 0 - 16
media/src/main/java/com/media/service/IMediaFileService.java

@@ -1,16 +0,0 @@
-package com.media.service;
-
-import com.baomidou.mybatisplus.extension.service.IService;
-import com.media.domain.po.MediaFile;
-
-/**
- * <p>
- * 媒体文件信息表 服务类
- * </p>
- *
- * @author xiang
- * @since 2025-11-18
- */
-public interface IMediaFileService extends IService<MediaFile> {
-
-}

+ 0 - 282
media/src/main/java/com/media/service/impl/ChunkUploadService.java

@@ -1,282 +0,0 @@
-package com.media.service.impl;
-import com.media.domain.po.ChunkInfo;
-import com.media.domain.po.MediaFile;
-import com.media.domain.vo.UploadStatusVO;
-import com.media.service.IChunkInfoService;
-import com.media.service.IMediaFileService;
-import com.media.util.enums.StorageBucket;
-import com.media.util.upload.MinioFileUploader;
-import io.minio.ComposeObjectArgs;
-import io.minio.ComposeSource;
-import io.minio.RemoveObjectArgs;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.web.multipart.MultipartFile;
-import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * 分块上传服务实现
- */
-@Service
-@Slf4j
-public class ChunkUploadService {
-    
-    @Autowired
-    private MinioFileUploader minioFileUploader;
-    
-    @Autowired
-    private IChunkInfoService chunkInfoService;
-    
-    @Autowired
-    private IMediaFileService mediaFileService;
-    
-    private static final String TEMP_CHUNK_DIR = "temp_chunks/";
-    
-    /**
-     * 上传单个分块
-     */
-    public String uploadChunk(String fileMd5, String fileName,
-                              MultipartFile chunk, int chunkIndex, int totalChunks) {
-        try {
-            // 验证分块索引
-            if (chunkIndex >= totalChunks) {
-                throw new RuntimeException("分块索引超出范围");
-            }
-            
-            // 上传分块到MinIO临时目录
-            String chunkObjectName = TEMP_CHUNK_DIR + fileMd5 + "/" + chunkIndex;
-            String chunkUrl = minioFileUploader.uploadFile(
-                StorageBucket.VIDEO_FILES, // 或其他合适的存储桶
-                chunkObjectName,
-                chunk.getInputStream(),
-                chunk.getContentType()
-            );
-            
-            // 记录分块信息到数据库
-            ChunkInfo chunkInfo = new ChunkInfo();
-            chunkInfo.setFileMd5(fileMd5);
-            chunkInfo.setFileName(fileName);
-            chunkInfo.setChunkIndex(chunkIndex);
-            chunkInfo.setTotalChunks(totalChunks);
-            chunkInfo.setChunkUrl(chunkUrl);
-            chunkInfo.setStatus(1); // 已上传
-            chunkInfo.setUploadedTime(LocalDateTime.now());
-            chunkInfoService.save(chunkInfo);
-            
-            log.info("分块上传成功: fileMd5={}, chunkIndex={}", fileMd5, chunkIndex);
-            return chunkUrl;
-            
-        } catch (Exception e) {
-            log.error("分块上传失败: fileMd5={}, chunkIndex={}", fileMd5, chunkIndex, e);
-            throw new RuntimeException("分块上传失败: " + e.getMessage());
-        }
-    }
-    
-    /**
-     * 合并分块
-     */
-    @Transactional
-    public String mergeChunks(String fileMd5, String fileName, StorageBucket bucket) {
-        try {
-            log.info("开始合并分块: fileMd5={}", fileMd5);
-            
-            // 查询所有分块信息
-            List<ChunkInfo> chunks = chunkInfoService.lambdaQuery()
-                .eq(ChunkInfo::getFileMd5, fileMd5)
-                .orderByAsc(ChunkInfo::getChunkIndex)
-                .list();
-            
-            if (chunks.isEmpty()) {
-                throw new RuntimeException("未找到分块信息");
-            }
-            
-            // 验证分块完整性
-            int expectedChunks = chunks.get(0).getTotalChunks();
-            Set<Integer> uploadedIndices = chunks.stream()
-                .map(ChunkInfo::getChunkIndex)
-                .collect(Collectors.toSet());
-            
-            for (int i = 0; i < expectedChunks; i++) {
-                if (!uploadedIndices.contains(i)) {
-                    throw new RuntimeException("分块不完整,缺少第" + i + "块");
-                }
-            }
-            
-            // 构建合并源
-            List<ComposeSource> sources = new ArrayList<>();
-            for (ChunkInfo chunk : chunks) {
-                String objectName = extractObjectNameFromUrl(chunk.getChunkUrl());
-                sources.add(ComposeSource.builder()
-                    .bucket(extractBucketName(chunk.getChunkUrl()))
-                    .object(objectName)
-                    .build());
-            }
-            
-            // 合并文件
-            String finalObjectName = "files/" + fileMd5 + "_" + System.currentTimeMillis() + "_" + fileName;
-            minioClient.composeObject(ComposeObjectArgs.builder()
-                .bucket(bucket.getBucketName())
-                .object(finalObjectName)
-                .sources(sources)
-                .build());
-            
-            // 生成最终文件URL
-            String finalUrl = minioFileUploader.generateFileUrl(bucket, finalObjectName);
-            
-            // 保存到媒体文件表
-            MediaFile mediaFile = new MediaFile();
-            mediaFile.setOriginalName(fileName);
-            mediaFile.setFileName(finalObjectName);
-            mediaFile.setFileType(getFileTypeFromName(fileName));
-            mediaFile.setFileSize(calculateTotalSize(chunks));
-            mediaFile.setBucketName(bucket.getBucketName());
-            mediaFile.setFileUrl(finalUrl);
-            mediaFile.setStatus(0); // 正常状态
-            mediaFile.setStorageType("minio");
-            mediaFile.setMd5(fileMd5);
-            mediaFile.setCreateTime(LocalDateTime.now());
-            mediaFile.setUpdateTime(LocalDateTime.now());
-            mediaFileService.save(mediaFile);
-            
-            // 删除临时分块文件
-            deleteTempChunks(fileMd5);
-            
-            // 删除分块记录
-            chunkInfoService.lambdaQuery()
-                .eq(ChunkInfo::getFileMd5, fileMd5)
-                .remove();
-            
-            log.info("分块合并完成: fileMd5={}, finalUrl={}", fileMd5, finalUrl);
-            return finalUrl;
-            
-        } catch (Exception e) {
-            log.error("分块合并失败: fileMd5={}", fileMd5, e);
-            // 清理临时文件
-            deleteTempChunks(fileMd5);
-            throw new RuntimeException("分块合并失败: " + e.getMessage());
-        }
-    }
-    
-    /**
-     * 查询上传状态
-     */
-    public UploadStatusVO getUploadStatus(String fileMd5) {
-        List<ChunkInfo> chunks = chunkInfoService.lambdaQuery()
-            .eq(ChunkInfo::getFileMd5, fileMd5)
-            .list();
-        
-        UploadStatusVO status = new UploadStatusVO();
-        status.setFileMd5(fileMd5);
-        
-        if (chunks.isEmpty()) {
-            status.setStatus("not_found");
-            status.setMessage("未找到上传记录");
-            return status;
-        }
-        
-        ChunkInfo firstChunk = chunks.get(0);
-        int totalChunks = firstChunk.getTotalChunks();
-        int uploadedChunks = chunks.size();
-        
-        status.setFileName(firstChunk.getFileName());
-        status.setTotalChunks(totalChunks);
-        status.setUploadedChunks(uploadedChunks);
-        
-        if (uploadedChunks == totalChunks) {
-            status.setStatus("merged");
-            status.setProgress("100%");
-            status.setMessage("上传完成");
-        } else {
-            status.setStatus("uploading");
-            String progress = String.format("%.2f", (uploadedChunks * 100.0 / totalChunks));
-            status.setProgress(progress + "%");
-            status.setMessage("上传中: " + uploadedChunks + "/" + totalChunks);
-        }
-        
-        return status;
-    }
-    
-    /**
-     * 提取对象名称
-     */
-    private String extractObjectNameFromUrl(String url) {
-        // 从URL中提取对象名称部分
-        int lastSlash = url.lastIndexOf('/');
-        return lastSlash > 0 ? url.substring(lastSlash + 1) : url;
-    }
-    
-    /**
-     * 提取存储桶名称
-     */
-    private String extractBucketName(String url) {
-        // 从URL中提取存储桶名称
-        String[] parts = url.split("/");
-        return parts.length > 3 ? parts[3] : "default";
-    }
-    
-    /**
-     * 根据文件名获取文件类型
-     */
-    private String getFileTypeFromName(String fileName) {
-        String ext = getFileExtension(fileName).toLowerCase();
-        if (Arrays.asList("jpg", "jpeg", "png", "gif", "bmp").contains(ext)) {
-            return "image";
-        } else if (Arrays.asList("mp3", "wav", "flac", "aac").contains(ext)) {
-            return "audio";
-        } else if (Arrays.asList("mp4", "avi", "mov", "mkv", "flv").contains(ext)) {
-            return "video";
-        } else {
-            return "other";
-        }
-    }
-    
-    /**
-     * 获取文件扩展名
-     */
-    private String getFileExtension(String fileName) {
-        int dotIndex = fileName.lastIndexOf('.');
-        return dotIndex > 0 ? fileName.substring(dotIndex + 1) : "";
-    }
-    
-    /**
-     * 计算总文件大小
-     */
-    private Long calculateTotalSize(List<ChunkInfo> chunks) {
-        return chunks.stream()
-            .mapToLong(chunk -> {
-                // 这里需要根据实际存储的大小信息计算
-                return 0L; // 需要从MinIO获取实际大小
-            })
-            .sum();
-    }
-    
-    /**
-     * 删除临时分块文件
-     */
-    private void deleteTempChunks(String fileMd5) {
-        try {
-            List<ChunkInfo> chunks = chunkInfoService.lambdaQuery()
-                .eq(ChunkInfo::getFileMd5, fileMd5)
-                .list();
-            
-            for (ChunkInfo chunk : chunks) {
-                String objectName = extractObjectNameFromUrl(chunk.getChunkUrl());
-                String bucketName = extractBucketName(chunk.getChunkUrl());
-                
-                minioClient.removeObject(RemoveObjectArgs.builder()
-                    .bucket(bucketName)
-                    .object(objectName)
-                    .build());
-            }
-        } catch (Exception e) {
-            log.error("删除临时分块文件失败", e);
-        }
-    }
-}

+ 0 - 74
media/src/main/java/com/media/service/impl/FileUploadServiceImpl.java

@@ -1,74 +0,0 @@
-package com.media.service.impl;
-import com.media.domain.po.MediaFile;
-import com.media.service.FileUploadService;
-import com.media.service.IMediaFileService;
-import com.media.util.enums.StorageBucket;
-import com.media.util.upload.MinioFileUploader;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import org.springframework.web.multipart.MultipartFile;
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.security.MessageDigest;
-@Service
-public class FileUploadServiceImpl implements FileUploadService {
-    @Autowired
-    private MinioFileUploader minioFileUploader;
-
-    @Autowired
-    private IMediaFileService mediaService;
-
-    // 完全去掉 @Transactional 注解
-    @Override
-    public String uploadFile(MultipartFile file, StorageBucket bucket, String objectName, String md5) throws Exception {
-        // 1. 处理文件名
-        if (objectName == null || objectName.isEmpty()) {
-            objectName = System.currentTimeMillis() + "_" + file.getOriginalFilename();
-        }
-        // 2. MD5校验(耗时操作,无连接占用)
-        InputStream fileInputStream = file.getInputStream();
-        if (md5 != null && !md5.isEmpty()) {
-            byte[] fileBytes = file.getBytes();
-            String calculatedMd5 = calculateMD5FromBytes(fileBytes);
-            if (!calculatedMd5.equalsIgnoreCase(md5)) {
-                throw new RuntimeException("文件MD5校验失败,上传过程发生错误");
-            }
-            fileInputStream = new ByteArrayInputStream(fileBytes);
-        }
-
-        // 3. MinIO上传(耗时操作,无连接占用)
-        String fileUrl = minioFileUploader.uploadFile(
-                bucket,
-                objectName,
-                fileInputStream,
-                file.getContentType()
-        );
-
-        // 4. 单表新增(无事务,快速执行,连接用完立即释放)
-        MediaFile mediaFile = new MediaFile();
-        mediaFile.setOriginalName(file.getOriginalFilename());
-        mediaFile.setFileName(objectName);
-        mediaFile.setFileType(file.getContentType());
-        mediaFile.setFileSize(file.getSize());
-        mediaFile.setBucketName(bucket.getBucketName());
-        mediaFile.setFileUrl(fileUrl);
-        mediaFile.setStatus(0);
-        mediaFile.setStorageType("minio");
-        if (md5 != null) {
-            mediaFile.setMd5(md5);
-        }
-        mediaService.save(mediaFile);
-        return fileUrl;
-    }
-
-    private String calculateMD5FromBytes(byte[] data) throws Exception {
-        MessageDigest md = MessageDigest.getInstance("MD5");
-        md.update(data);
-        byte[] digest = md.digest();
-        StringBuilder sb = new StringBuilder();
-        for (byte b : digest) {
-            sb.append(String.format("%02x", b));
-        }
-        return sb.toString();
-    }
-}

+ 2 - 1
media/src/main/java/com/media/util/enums/StorageBucket.java

@@ -10,7 +10,8 @@ public enum StorageBucket {
     ALBUM_SHOP("album-shop"),//    ALBUM_SHOP: 专辑商城存储桶
     COMMENT_IMAGES("comment-images"),//    COMMENT_IMAGES: 评论图片存储桶
     SONG_AUDIO("song-audio"),//    SONG_AUDIO: 歌曲音频存储桶
-    PLAYLIST_COVERS("playlist-covers");//    PLAYLIST_COVERS: 歌单封面存储桶
+    PLAYLIST_COVERS("playlist-covers"),//    PLAYLIST_COVERS: 歌单封面存储桶
+    Video_Temp("video-temp");
     private final String bucketName;
     
     StorageBucket(String bucketName) {

+ 237 - 55
media/src/main/java/com/media/util/upload/MinioFileUploader.java

@@ -1,15 +1,26 @@
 package com.media.util.upload;
 
-import com.media.service.FileUploadService;
+import com.base.utils.Result;
+import com.media.domain.dto.MergeDto;
+import com.media.domain.po.MediaFile;
+import com.media.service.IMediaFileService;
 import com.media.util.enums.StorageBucket;
-import io.minio.MinioClient;
-import io.minio.PutObjectArgs;
+import io.minio.*;
+import io.minio.errors.*;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
+import java.io.IOException;
 import java.io.InputStream;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.media.util.enums.StorageBucket.VIDEO_FILES;
+
 @Slf4j
 @Component
 public class MinioFileUploader {
@@ -17,65 +28,236 @@ public class MinioFileUploader {
     private MinioClient minioClient;
     @Value("${minio.endpoint}")
     private String endpoint;
+    @Autowired
+    private IMediaFileService mediaFileService;
+
+    public String uploadFile(StorageBucket bucket, String objectName, InputStream inputStream, String contentType) {
+        try {
+            // 检查并创建存储桶
+            checkAndCreateBucket(bucket.getBucketName());
+
+            // 简化版上传,适用于3M大小的文件
+            minioClient.putObject(
+                    PutObjectArgs.builder()
+                            .bucket(bucket.getBucketName())
+                            .object(objectName)
+                            .stream(inputStream, inputStream.available(), -1)
+                            .contentType(contentType)
+                            .build()
+            );
 
-public String uploadFile(StorageBucket bucket, String objectName, InputStream inputStream, String contentType) {
-    try {
-        // 检查并创建存储桶
-        checkAndCreateBucket(bucket.getBucketName());
-
-        // 简化版上传,适用于3M大小的文件
-        minioClient.putObject(
-                PutObjectArgs.builder()
-                        .bucket(bucket.getBucketName())
-                        .object(objectName)
-                        .stream(inputStream, inputStream.available(), -1)
-                        .contentType(contentType)
-                        .build()
-        );
-
-        return generateFileUrl(bucket, objectName);
-    } catch (Exception e) {
-        throw new RuntimeException("文件上传失败: " + e.getMessage(), e);
+            return generateFileUrl(bucket, objectName);
+        } catch (Exception e) {
+            throw new RuntimeException("文件上传失败: " + e.getMessage(), e);
+        }
+    }
+
+    private void checkAndCreateBucket(String bucketName) {
+        try {
+            // 统一转换为小写,避免MinIO桶名兼容问题
+            String lowerCaseBucketName = bucketName.toLowerCase();
+            boolean exists = minioClient.bucketExists(
+                    BucketExistsArgs.builder().bucket(lowerCaseBucketName).build()
+            );
+            if (!exists) {
+                // 创建存储桶
+                minioClient.makeBucket(
+                        MakeBucketArgs.builder()
+                                .bucket(lowerCaseBucketName)
+                                .build()
+                );
+                log.info("存储桶创建成功: {}", lowerCaseBucketName);
+
+                // 设置存储桶策略为公开读取
+                setBucketPolicy(lowerCaseBucketName);
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("存储桶操作失败: " + e.getMessage(), e);
+        }
     }
-}
-
-private void checkAndCreateBucket(String bucketName) {
-    try {
-        boolean exists = minioClient.bucketExists(
-            io.minio.BucketExistsArgs.builder().bucket(bucketName).build()
-        );
-        if (!exists) {
-            // 创建存储桶时设置为公开读取
-            minioClient.makeBucket(
-                io.minio.MakeBucketArgs.builder()
-                    .bucket(bucketName)
-                    .build()
+
+    private void setBucketPolicy(String bucketName) {
+        try {
+            // 存储桶策略配置(适配小写桶名)
+            String policyConfig = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"*\"},\"Action\":[\"s3:GetObject\"],\"Resource\":\"arn:aws:s3:::" + bucketName + "/*\"}]}";
+            minioClient.setBucketPolicy(
+                    SetBucketPolicyArgs.builder()
+                            .bucket(bucketName)
+                            .config(policyConfig)
+                            .build()
             );
-            // 设置存储桶策略为公开读取
-            setBucketPolicy(bucketName);
+            log.info("存储桶公开读取策略设置成功: {}", bucketName);
+        } catch (Exception e) {
+            log.error("设置存储桶策略失败: {}", bucketName, e);
         }
-    } catch (Exception e) {
-        throw new RuntimeException("存储桶操作失败: " + e.getMessage(), e);
     }
-}
-
-private void setBucketPolicy(String bucketName) {
-    try {
-        // 设置存储桶策略为公开读取
-        minioClient.setBucketPolicy(
-            io.minio.SetBucketPolicyArgs.builder()
-                .bucket(bucketName)
-                .config("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"*\"},\"Action\":[\"s3:GetObject\"],\"Resource\":\"arn:aws:s3:::" + bucketName + "/*\"}]}\n")
-                .build()
-        );
-    } catch (Exception e) {
-        log.error("设置存储桶策略失败: {}", bucketName, e);
+
+    private String generateFileUrl(StorageBucket bucket, String objectName) {
+        // 拼接URL时使用小写桶名
+        String lowerCaseBucketName = bucket.getBucketName().toLowerCase();
+        return endpoint + "/" + lowerCaseBucketName + "/" + objectName;
+    }
+
+    // 文件是否存在
+    public boolean checkObjectExists(StorageBucket bucket, String objectName) {
+        try {
+            String lowerCaseBucketName = bucket.getBucketName().toLowerCase();
+            minioClient.statObject(
+                    StatObjectArgs.builder()
+                            .bucket(lowerCaseBucketName)
+                            .object(objectName)
+                            .build()
+            );
+            return true;
+        } catch (Exception e) {
+            // 捕获不存在的异常,返回false
+            if (e instanceof ErrorResponseException && ((ErrorResponseException) e).errorResponse().code().equals("NoSuchKey")) {
+                return false;
+            }
+            log.warn("检查文件存在性异常: {}", objectName, e);
+            return false;
+        }
     }
-}
 
+    public void mergeFile(MergeDto mergeDto) {
+        log.info("开始合并文件: {}", mergeDto.getFileKey());
+        boolean exists1 = checkObjectExists(VIDEO_FILES, mergeDto.getFileKey());
+        if (exists1) {
+            log.info("文件" + mergeDto.getFileKey() + "已存在,无需重复上传");
+            saveFile(mergeDto);
+            return;
+        }
+        try {
+            // 关键修复1:先检查并创建合并所需的2个存储桶(转为小写)
+            String tempBucketName = StorageBucket.Video_Temp.getBucketName().toLowerCase();
+            String targetBucketName = StorageBucket.VIDEO_FILES.getBucketName().toLowerCase();
+            checkAndCreateBucket(tempBucketName); // 分块存储桶
+            checkAndCreateBucket(targetBucketName); // 合并后存储桶
 
+            // 1. 获取分块文件列表
+            List<ComposeSource> sourceList = getChunkSources(mergeDto, tempBucketName);
 
-    private String generateFileUrl(StorageBucket bucket, String objectName) {
-        return endpoint + "/" + bucket.getBucketName() + "/" + objectName;
+            // 校验分块列表是否为空
+            if (sourceList.isEmpty()) {
+                throw new RuntimeException("分块文件列表为空,无法合并: " + mergeDto.getFileKey());
+            }
+
+            // 2. 执行合并操作
+            String mergedObjectName = executeMerge(mergeDto, sourceList, targetBucketName);
+
+            // 3. 删除分块文件
+            deleteChunkFiles(mergeDto, tempBucketName);
+
+            log.info("文件合并完成: {} -> {}", mergeDto.getFileKey(), mergedObjectName);
+            saveFile(mergeDto);
+        } catch (Exception e) {
+            log.error("合并文件失败: {}", mergeDto.getFileKey(), e);
+            throw new RuntimeException("文件合并失败: " + e.getMessage(), e);
+        }
+    }
+
+    public void saveFile(MergeDto mergeDto) {
+        log.info("开始保存文件: {}", mergeDto.getFileKey());
+        MediaFile mediaFile = new MediaFile();
+        mediaFile.setOriginalName(mergeDto.getFileName());
+        mediaFile.setFileName(mergeDto.getFileKey());
+        mediaFile.setFileType(mergeDto.getContentType());
+        mediaFile.setFileSize((long) (mergeDto.getTotalChunk()*1024*1024*5));
+        mediaFile.setBucketName(StorageBucket.VIDEO_FILES.getBucketName());
+        mediaFile.setFileUrl(generateFileUrl(StorageBucket.VIDEO_FILES, mergeDto.getFileKey() + "." + getFileExtension(mergeDto.getContentType())));
+        mediaFile.setStorageType("minio");
+        mediaFile.setStatus(0);
+        mediaFile.setMd5(mergeDto.getFileKey());
+        mediaFileService.save(mediaFile);
+    }
+
+    /**
+     * 获取分块文件源列表(修复分块索引从0开始)
+     */
+    private List<ComposeSource> getChunkSources(MergeDto mergeDto, String tempBucketName) {
+        try {
+            List<ComposeSource> sourceList = new ArrayList<>();
+            int totalChunk = mergeDto.getTotalChunk();
+
+            // 关键修复2:分块索引从0开始,遍历所有分块
+            for (int i = 1; i <= totalChunk; i++) {
+                String chunkObjectName = "temp/" + mergeDto.getFileKey() + "/" + i;
+                log.debug("添加分块文件到合并列表: {}", chunkObjectName);
+
+                // 可选:检查分块是否存在,避免合并缺失
+                if (!checkObjectExists(StorageBucket.Video_Temp, chunkObjectName)) {
+                    throw new RuntimeException("分块文件不存在: " + chunkObjectName);
+                }
+
+                ComposeSource source = ComposeSource.builder()
+                        .bucket(tempBucketName)
+                        .object(chunkObjectName)
+                        .build();
+                sourceList.add(source);
+            }
+
+            return sourceList;
+        } catch (Exception e) {
+            throw new RuntimeException("获取分块文件列表失败: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 执行文件合并操作
+     */
+
+    private String executeMerge(MergeDto mergeDto, List<ComposeSource> sourceList, String targetBucketName)
+            throws IOException, NoSuchAlgorithmException, InvalidKeyException, XmlParserException,
+            ErrorResponseException, InsufficientDataException, InternalException, InvalidResponseException, ServerException {
+        // 目标文件名使用 MD5,格式为 md5/md5.格式
+        String fileKey = mergeDto.getFileKey();
+        String fileExtension = getFileExtension(mergeDto.getContentType());
+
+        String targetObjectName = fileKey +"." +fileExtension;
+
+        log.debug("开始合并文件到存储桶: {}, 目标对象名: {}", targetBucketName, targetObjectName);
+
+        ComposeObjectArgs composeArgs = ComposeObjectArgs.builder()
+                .bucket(targetBucketName)
+                .object(targetObjectName)
+                .sources(sourceList)
+                .build();
+        String etag = minioClient.composeObject(composeArgs).toString();
+        log.debug("合并操作完成,ETag: {}", etag);
+        return targetObjectName;
+    }
+
+    private String getFileExtension(String contentType) {
+        if (contentType == null || !contentType.contains("/")) {
+            return "unknown"; // 默认扩展名
+        }
+
+        // 提取 "video/mp4" 中的 "mp4" 部分
+        String[] parts = contentType.split("/");
+        if (parts.length >= 2) {
+            return parts[1]; // 返回类型部分,如 mp4, avi, mov 等
+        }
+
+        return "unknown";
+    }
+    /**
+     * 删除分块文件
+     */
+    private void deleteChunkFiles(MergeDto mergeDto, String tempBucketName) {
+        int totalChunk = mergeDto.getTotalChunk();
+        for (int i = 1; i <= totalChunk; i++) { // 同步从0开始遍历
+            String chunkObjectName = "temp/" + mergeDto.getFileKey() + "/" + i;
+            try {
+                minioClient.removeObject(
+                        RemoveObjectArgs.builder()
+                                .bucket(tempBucketName)
+                                .object(chunkObjectName)
+                                .build()
+                );
+                log.debug("分块文件已删除: {}", chunkObjectName);
+            } catch (Exception e) {
+                log.warn("删除分块文件失败: {}", chunkObjectName, e);
+            }
+        }
     }
-}
+}

+ 56 - 0
media/src/test/java/com/media/RabbitMQBeanTest.java

@@ -0,0 +1,56 @@
+package com.media;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@SpringBootTest
+public class RabbitMQBeanTest {
+    
+    @Autowired
+    private ApplicationContext context;
+    
+    @Test
+    public void testRabbitMQBeans() {
+        System.out.println("=== 开始测试RabbitMQ Bean ===");
+        
+        // 1. 检查是否有ConnectionFactory
+        try {
+            Object cf = context.getBean("rabbitConnectionFactory");
+            System.out.println("✅ rabbitConnectionFactory Bean: " + cf.getClass().getName());
+        } catch (Exception e) {
+            System.err.println("❌ rabbitConnectionFactory Bean 不存在");
+        }
+        
+        // 2. 检查是否有RabbitTemplate
+        try {
+            Object rt = context.getBean("rabbitTemplate");
+            System.out.println("✅ rabbitTemplate Bean: " + rt.getClass().getName());
+        } catch (Exception e) {
+            System.err.println("❌ rabbitTemplate Bean 不存在");
+        }
+        
+        // 3. 列出所有相关Bean
+        System.out.println("\n所有相关Bean:");
+        String[] beanNames = context.getBeanDefinitionNames();
+        boolean foundRabbitBeans = false;
+        for (String beanName : beanNames) {
+            String lowerName = beanName.toLowerCase();
+            if (lowerName.contains("rabbit") || lowerName.contains("amqp") || lowerName.contains("mq")) {
+                System.out.println("  - " + beanName + " (" + context.getBean(beanName).getClass().getSimpleName() + ")");
+                foundRabbitBeans = true;
+            }
+        }
+        
+        if (!foundRabbitBeans) {
+            System.err.println("❌ 没有找到任何RabbitMQ相关的Bean");
+            System.err.println("可能的原因:");
+            System.err.println("1. spring-boot-starter-amqp 依赖缺失");
+            System.err.println("2. 配置文件中RabbitMQ配置有语法错误");
+            System.err.println("3. Spring Boot自动配置被排除");
+        }
+    }
+}

+ 29 - 0
media/src/test/java/com/media/RabbitMQConnectionTest.java

@@ -0,0 +1,29 @@
+package com.media;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.amqp.rabbit.core.RabbitTemplate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.stereotype.Component;
+
+
+@SpringBootTest
+public class RabbitMQConnectionTest {
+    
+    @Autowired
+    private RabbitTemplate rabbitTemplate;
+    
+    @Test
+    public void testConnection() {
+        try {
+            // 发送测试消息
+            rabbitTemplate.convertAndSend("mp.direct", "Hello RabbitMQ");
+            System.out.println("✅ RabbitMQ连接成功!");
+            System.out.println("Virtual Host: " + 
+                rabbitTemplate.getConnectionFactory().getVirtualHost());
+        } catch (Exception e) {
+            System.err.println("❌ RabbitMQ连接失败: " + e.getMessage());
+            e.printStackTrace();
+        }
+    }
+}

+ 60 - 0
media/src/test/java/com/media/VideoToGifTest.java

@@ -0,0 +1,60 @@
+package com.media;
+import java.io.*;
+public class VideoToGifTest {
+    private static final String OUTPUT_MP4_PATH = "C:\\Users\\asus\\Desktop\\shop\\converted_video.mp4";
+
+    public static void main(String[] args) {
+        // 指定被操控的AVI视频路径
+        String testAviPath = "C:\\Users\\asus\\Downloads\\dzq.avi"; // 你的AVI文件路径
+
+        // 将AVI转换为MP4
+        System.out.println("开始将AVI转换为MP4...");
+        boolean convertSuccess = convertAviToMp4(testAviPath, OUTPUT_MP4_PATH);
+        if (!convertSuccess) {
+            System.out.println("AVI转MP4失败!");
+            return;
+        }
+        System.out.println("AVI转MP4成功!输出路径:" + new File(OUTPUT_MP4_PATH).getAbsolutePath());
+    }
+
+    /**
+     * 将AVI格式转换为MP4格式
+     */
+    private static boolean convertAviToMp4(String inputAviPath, String outputMp4Path) {
+        ProcessBuilder processBuilder = new ProcessBuilder(
+                "D:\\heima1\\xuecheng\\ffmpeg\\ffmpeg.exe",
+                "-i", inputAviPath,                    // 输入AVI文件
+                "-c:v", "libx264",                    // 视频编码器使用H.264
+                "-c:a", "aac",                        // 音频编码器使用AAC
+                "-movflags", "+faststart",            // 优化MP4文件结构,支持流式播放
+                "-preset", "medium",                  // 编码速度与质量平衡
+                "-crf", "23",                         // 恒定质量因子(18-28之间,值越小质量越高)
+                "-y",                                 // 覆盖输出文件
+                outputMp4Path                         // 输出MP4文件
+        );
+
+        processBuilder.redirectErrorStream(true);
+
+        try {
+            Process process = processBuilder.start();
+
+            // 实时读取FFmpeg输出
+            try (BufferedReader reader = new BufferedReader(
+                    new InputStreamReader(process.getInputStream()))) {
+                String line;
+                while ((line = reader.readLine()) != null) {
+                    if (line.contains("frame=") || line.contains("Duration:")) {
+                        System.out.println("FFmpeg进度: " + line);
+                    }
+                }
+            }
+
+            int exitCode = process.waitFor();
+            return exitCode == 0;
+        } catch (IOException | InterruptedException e) {
+            System.err.println("FFmpeg AVI转MP4失败: " + e.getMessage());
+            e.printStackTrace();
+            return false;
+        }
+    }
+}

+ 7 - 0
parent/pom.xml

@@ -126,6 +126,13 @@
                 <version>${minio.version}</version>
             </dependency>
 
+            <!-- RabbitMQ -->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-amqp</artifactId>
+                <version>${spring-boot.version}</version>
+            </dependency>
+
             <!-- XXL-JOB 分布式任务调度 -->
             <dependency>
                 <groupId>com.xuxueli</groupId>