Ver código fonte

Merge branch 'dev' of http://192.168.1.245:11111/jinjilong/onlineEducation-fwd into dev

honorfire 7 meses atrás
pai
commit
8784bb806f
16 arquivos alterados com 550 adições e 64 exclusões
  1. 6 2
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/pom.xml
  2. 84 10
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/config/minio/MinioConfig.java
  3. 3 1
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/consumer/TranscodingConsumer.java
  4. 1 1
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/consumer/config/ConsumerConfig.java
  5. 4 4
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/controller/MinioController.java
  6. 5 1
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/controller/TranscodingController.java
  7. 19 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/domain/UploadFile.java
  8. 1 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/domain/UploadTaskDetail.java
  9. 0 1
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/ResourceFileService.java
  10. 4 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/TranscodingService.java
  11. 11 17
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/impl/ResourceFileServiceImpl.java
  12. 88 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/impl/TranscodingServiceImpl.java
  13. 27 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/util/LocalCache.java
  14. 154 17
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/util/MinioUtil.java
  15. 13 10
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/util/PdfUtils.java
  16. 130 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/util/S3MultiThreadMultipartUpload.java

+ 6 - 2
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/pom.xml

@@ -58,7 +58,6 @@
         <javacpp.version>1.5.7</javacpp.version>
         <javacv.version>1.5.7</javacv.version>
         <ffmpeg.version>5.0-1.5.7</ffmpeg.version>
-        <minio.version>8.5.7</minio.version>
         <qiniu.version>7.9.2</qiniu.version>
         <hutool.version>5.8.16</hutool.version>
         <springdoc-openapi.version>1.6.1</springdoc-openapi.version>
@@ -76,7 +75,6 @@
         <dependency>
             <groupId>io.minio</groupId>
             <artifactId>minio</artifactId>
-            <version>8.4.0</version>
         </dependency>
 
         <!-- 每个插件都要引入自己的对外接口 -->
@@ -182,6 +180,12 @@
             <version>2.0.1</version>
         </dependency>
 
+        <dependency>
+            <groupId>software.amazon.awssdk</groupId>
+            <artifactId>s3</artifactId>
+            <version>2.20.130</version>
+        </dependency>
+
         <dependency>
             <groupId>com.baomidou</groupId>
             <artifactId>mybatis-plus-boot-starter</artifactId>

+ 84 - 10
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/config/minio/MinioConfig.java

@@ -1,7 +1,7 @@
 package vip.xiaonuo.disk.config.minio;
 
 import io.minio.MinioClient;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
@@ -10,20 +10,94 @@ import org.springframework.context.annotation.Configuration;
  * @since 2024/05/29
  */
 @Configuration
-@EnableConfigurationProperties(MinioProperties.class)
+@ConfigurationProperties(prefix = "minio")
 public class MinioConfig {
+    /**
+     * 服务地址
+     */
+    private String url;
+    private String domain;
 
-    private final MinioProperties minioProperties;
+    public String getDomain() {
+        return domain;
+    }
+
+    public void setDomain(String domain) {
+        this.domain = domain;
+    }
+
+    /**
+     * 用户名
+     */
+    private String accessKey;
+
+    /**
+     * 密码
+     */
+    private String secretKey;
+
+    /**
+     * 存储桶名称
+     */
+    private String bucketName;
+    /**
+     * 文件存储指定位置路径
+     */
+    private String filePath;
+
+    public String getUrl()
+    {
+        return url;
+    }
+
+    public void setUrl(String url)
+    {
+        this.url = url;
+    }
+
+    public String getAccessKey()
+    {
+        return accessKey;
+    }
+
+    public void setAccessKey(String accessKey)
+    {
+        this.accessKey = accessKey;
+    }
+
+    public String getSecretKey()
+    {
+        return secretKey;
+    }
+
+    public void setSecretKey(String secretKey)
+    {
+        this.secretKey = secretKey;
+    }
+
+    public String getBucketName()
+    {
+        return bucketName;
+    }
 
-    public MinioConfig(MinioProperties minioProperties) {
-        this.minioProperties = minioProperties;
+    public void setBucketName(String bucketName)
+    {
+        this.bucketName = bucketName;
     }
 
+    public String getFilePath() {
+        return filePath;
+    }
+
+    public void setFilePath(String filePath) {
+        this.filePath = filePath;
+    }
+
+
     @Bean
-    public MinioClient minioClient() {
-        return MinioClient.builder()
-                .credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey())
-                .endpoint(minioProperties.getEndpoint())
-                .build();
+    public MinioClient getMinioClient()
+    {
+        return MinioClient.builder().endpoint(url).credentials(accessKey, secretKey).build();
     }
+
 }

+ 3 - 1
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/consumer/TranscodingConsumer.java

@@ -18,7 +18,9 @@ public class TranscodingConsumer {
     @KafkaListener(containerFactory = "listenerFactory", topics = "TRANSCODING_RESOURCE", groupId = "transcoding_resource_group")
     public void transcodingResourceListen(List<ConsumerRecord<String, String>> consumerRecordList, Acknowledgment acknowledgment) {
         try {
-            log.info("transcodingResourceListen");
+            log.info("transcodingResourceListen===================");
+            log.info("transcodingResourceListen===================");
+            log.info("transcodingResourceListen===================");
             transcodingService.transcodingResourceParallel(consumerRecordList);
             acknowledgment.acknowledge();
         } catch (Exception e) {

+ 1 - 1
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/consumer/config/ConsumerConfig.java

@@ -58,7 +58,7 @@ public class ConsumerConfig {
         //单个分区获取消息的最大值为900M
         props.put(org.apache.kafka.clients.consumer.ConsumerConfig.MAX_PARTITION_FETCH_BYTES_CONFIG, 943718400);
         //一次最大拉取的条数
-        props.put(org.apache.kafka.clients.consumer.ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 100);
+        props.put(org.apache.kafka.clients.consumer.ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 500);
         props.put(org.apache.kafka.clients.consumer.ConsumerConfig.FETCH_MAX_BYTES_CONFIG, 1073741824);
         //earliest当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费
         //latest当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据

+ 4 - 4
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/controller/MinioController.java

@@ -19,7 +19,7 @@ import java.util.List;
  * @since 2024/05/29
  */
 @RestController
-@RequestMapping("/disk/minio")
+@RequestMapping("/minio")
 public class MinioController {
 
     @Autowired
@@ -37,14 +37,14 @@ public class MinioController {
     }
 
     @PostMapping("/upload")
-    public ResponseEntity<Boolean> upload(String md5,@RequestParam Integer chunkIndex, @RequestParam MultipartFile chunk)
+    public ResponseEntity<Boolean> upload(String md5,@RequestParam Integer chunkIndex,@RequestParam MultipartFile chunk)
             throws IOException {
         return ResponseEntity.ok(minioUtil.upload(md5, chunkIndex, chunk.getInputStream(), chunk.getSize()));
     }
 
     @PostMapping("/merge")
-    public ResponseEntity<String> merge(@RequestParam String md5, @RequestParam Integer chunkTotal, @RequestParam String fileSuffix) {
-        return ResponseEntity.ok(minioUtil.merge(md5, chunkTotal, fileSuffix) ? "成功!" : "失败!");
+    public ResponseEntity<Boolean> merge(@RequestParam String md5, @RequestParam Integer chunkTotal, @RequestParam String fileSuffix,@RequestParam String fileName, @RequestParam long fileSize) {
+        return ResponseEntity.ok(minioUtil.merge(md5, chunkTotal, fileSuffix,fileSize,fileName));
     }
 
 

+ 5 - 1
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/controller/TranscodingController.java

@@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RestController;
 import vip.xiaonuo.common.pojo.CommonResult;
 import vip.xiaonuo.disk.dto.file.TranscodingReqDTO;
+import vip.xiaonuo.disk.dto.file.TranscodingResourceReqDTO;
 import vip.xiaonuo.disk.service.TranscodingService;
 
 import javax.annotation.Resource;
@@ -37,6 +38,9 @@ public class TranscodingController {
     public CommonResult<String> transcodingVideo(@RequestBody TranscodingReqDTO transcodingReqDTO) {
         return transcodingService.transcodingVideo(transcodingReqDTO);
     }
-
+    @PostMapping("/transcodingResource/file")
+    public CommonResult<String> transcodingResource(@RequestBody TranscodingResourceReqDTO transcodingResourceReqDTO) {
+        return transcodingService.transcodingResource(transcodingResourceReqDTO);
+    }
 
 }

+ 19 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/domain/UploadFile.java

@@ -1,5 +1,6 @@
 package vip.xiaonuo.disk.domain;
 
+import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
 import java.util.ArrayList;
@@ -9,4 +10,22 @@ public class UploadFile {
     private String md5;
     private String fileSuffix;
     private ArrayList chunks;
+
+    @Schema(description = "文件名")
+    private String fileName;
+
+    @Schema(description = "切片数量")
+    private int chunkNumber;
+
+    @Schema(description = "相对路径")
+    private String relativePath;
+
+    @Schema(description = "所有切片")
+    private int chunkSize;
+
+    @Schema(description = "md5码")
+    private String identifier;
+
+
+
 }

+ 1 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/domain/UploadTaskDetail.java

@@ -39,4 +39,5 @@ public class UploadTaskDetail {
 
     @Column(columnDefinition="varchar(200) comment '文件md5唯一标识'")
     private String identifier;
+
 }

+ 0 - 1
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/ResourceFileService.java

@@ -1,7 +1,6 @@
 package vip.xiaonuo.disk.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
-import vip.xiaonuo.disk.domain.FileBean;
 import vip.xiaonuo.disk.domain.ResourceFile;
 import vip.xiaonuo.disk.dto.file.DownloadFileDTO;
 import vip.xiaonuo.disk.dto.file.EachOtherExchangeDTO;

+ 4 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/TranscodingService.java

@@ -3,6 +3,7 @@ package vip.xiaonuo.disk.service;
 import org.apache.kafka.clients.consumer.ConsumerRecord;
 import vip.xiaonuo.common.pojo.CommonResult;
 import vip.xiaonuo.disk.dto.file.TranscodingReqDTO;
+import vip.xiaonuo.disk.dto.file.TranscodingResourceReqDTO;
 
 import java.util.List;
 
@@ -22,4 +23,7 @@ public interface TranscodingService {
     CommonResult<String> transcodingVideo(TranscodingReqDTO transcodingReqDTO);
 
     void transcodingResourceParallel(List<ConsumerRecord<String, String>> consumerRecordList);
+
+    CommonResult<String> transcodingResource(TranscodingResourceReqDTO transcodingResourceReqDTO);
+
 }

+ 11 - 17
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/impl/ResourceFileServiceImpl.java

@@ -130,13 +130,13 @@ public class ResourceFileServiceImpl extends ServiceImpl<ResourceFileMapper, Res
                 if(StringUtils.isEmpty(file.getPreviewFileUrl()))
                 {
                     try {
-                    //    ExecutorService threadPool = CheckThreadPool.getExecutor();
-                    //    threadPool.execute(() -> {
+                        ExecutorService threadPool = CheckThreadPool.getExecutor();
+                        threadPool.execute(() -> {
                             TranscodingResourceReqDTO transcodingResourceReqDTO = new TranscodingResourceReqDTO();
                             transcodingResourceReqDTO.setUserFileId(resourceUserFile.getUserFileId());
                             transcodingResourceReqDTO.setUserId(userId);
                             kafKaProducerUtil.sendTranscodingResource(producer.getProducer(), JSON.toJSONString(transcodingResourceReqDTO));
-                     //   });
+                        });
                     } catch (Exception e) {
                         throw new RuntimeException(e);
                     }
@@ -161,14 +161,13 @@ public class ResourceFileServiceImpl extends ServiceImpl<ResourceFileMapper, Res
 
         } else {
             //根据MD5值清空表
-            uploadTaskDetailMapper.deleteUploadTaskDetail(uploadFileDTO.getIdentifier());
+
             uploadFileVo.setSkipUpload(false);
             uploadFileVo.setUploadStatus("0");
-          //  List<Integer> uploaded = uploadTaskDetailMapper.selectUploadedChunkNumList(uploadFileDTO.getIdentifier());
-//            if (uploaded != null && !uploaded.isEmpty()) {
-//                uploadFileVo.setUploaded(uploaded);
-//            } else {
-
+            List<Integer> uploaded = uploadTaskDetailMapper.selectUploadedChunkNumList(uploadFileDTO.getIdentifier());
+            if (uploaded != null && !uploaded.isEmpty()) {
+                uploadFileVo.setUploaded(uploaded);
+            } else {
                 LambdaQueryWrapper<UploadTask> lambdaQueryWrapper = new LambdaQueryWrapper<>();
                 lambdaQueryWrapper.eq(UploadTask::getIdentifier, uploadFileDTO.getIdentifier());
                 List<UploadTask> rslist = uploadTaskMapper.selectList(lambdaQueryWrapper);
@@ -183,8 +182,7 @@ public class ResourceFileServiceImpl extends ServiceImpl<ResourceFileMapper, Res
                     uploadTask.setUserId(userId);
                     uploadTaskMapper.insert(uploadTask);
                 }
-         //   }
-
+            }
         }
 
 
@@ -264,20 +262,16 @@ public class ResourceFileServiceImpl extends ServiceImpl<ResourceFileMapper, Res
                 uploadFileVo.setUploadStatus("1");
                 ExecutorService threadPool = CheckThreadPool.getExecutor();
                 try {
-            //        threadPool.execute(() -> {
+                    threadPool.execute(() -> {
                         TranscodingResourceReqDTO transcodingResourceReqDTO = new TranscodingResourceReqDTO();
                         transcodingResourceReqDTO.setUserFileId(resourceUserFile.getUserFileId());
                         transcodingResourceReqDTO.setUserId(userId);
                         kafKaProducerUtil.sendTranscodingResource(producer.getProducer(), JSON.toJSONString(transcodingResourceReqDTO));
-            //        });
+                    });
                 } catch (Exception e) {
                     throw new RuntimeException(e);
                 }
                 //上传成功同时用新线程异步转换格式用来创建预览用的文件
-
-
-
-
                 if (relativePath.contains("/")) {
                     QiwenFile finalQiwenFile = qiwenFile;
                     exec.execute(()->{

+ 88 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/impl/TranscodingServiceImpl.java

@@ -113,7 +113,95 @@ public class TranscodingServiceImpl implements TranscodingService {
         return CommonResult.ok("格式转换成功");
     }
 
+    @Override
+    public CommonResult<String> transcodingResource(TranscodingResourceReqDTO transcodingResourceReqDTO) {
+        // 校验目标文件是否存在,是否是本人的
+        String userId = StpLoginUserUtil.getLoginUser().getId();
+        QueryWrapper<ResourceUserFile> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(ResourceUserFile::getUserFileId, transcodingResourceReqDTO.getUserFileId());
+        ResourceUserFile userFile = resourceUserFileMapper.selectOne(queryWrapper);
+        if (userFile == null || !userFile.getUserId().equals(userId)) {
+            return CommonResult.error("文件不存在或不是本人的");
+        }
+
+        List<String> videoList= Arrays.asList("wmv","avi","flv","mpeg","mpg","rmvb","mov","mkv");
+
+        List<String> wordList= Arrays.asList("doc","docx","ppt","pptx","xls","xlsx","pdf");
+
+
+        ResourceFile fileBean = resourceFileMapper.selectById(userFile.getFileId());
+        if (fileBean == null) {
+            return CommonResult.error("文件不存在");
+        }
+        if(videoList.contains(userFile.getExtendName())){
+            transcodingResourceReqDTO.setTranscodingType(0);
+            transcodingResourceReqDTO.setFormat("mp4");
+        }
+
+        if(wordList.contains(userFile.getExtendName())){
+            transcodingResourceReqDTO.setTranscodingType(1);
+            transcodingResourceReqDTO.setFormat("pdf");
+        }
+
 
+        // 构建转码格式列表
+        ArrayList<String> formatList = new ArrayList<>();
+        formatList.add(transcodingResourceReqDTO.getFormat());
+        // 判断视频还是图片
+        if(transcodingResourceReqDTO.getTranscodingType()!= null)
+        {
+            if (transcodingResourceReqDTO.getTranscodingType() == 0) {
+                try {
+                    // 转码视频
+                    // mp4 wmv avi flv mpeg mpg rmvb mov 互相转
+                    videoConverter.convertAndUpload(fileBean.getFileUrl(), fileBean.getFileId(), new String[]{transcodingResourceReqDTO.getFormat()});
+                    //根据文件id修改预览地址
+                    if(videoList.contains(userFile.getExtendName())||wordList.contains(userFile.getExtendName())){
+                        ResourceFile newFile = new ResourceFile();
+                        newFile.setFileId(fileBean.getFileId());
+                        newFile.setPreviewFileUrl("converted/" + fileBean.getFileId() + "." + transcodingResourceReqDTO.getFormat());
+                        resourceFileMapper.updateById(newFile);
+                    }
+                } catch (Exception ex) {
+                    log.error(ex.getMessage());
+                    return CommonResult.error("格式转换失败");
+                }
+            } else if (transcodingResourceReqDTO.getTranscodingType() == 1) {
+                // 转码文档
+                // pdf doc docx ppt pptx xls xlsx
+                // doc转docx,ppt转pptx,xls转xlsx 所有均转pdf
+                // 判断目标格式
+
+                try {
+                    if ("pdf".equals(transcodingResourceReqDTO.getFormat())) {
+                        pdfUtils.convertToPdf(fileBean.getFileUrl(), fileBean.getFileId() + "." + transcodingResourceReqDTO.getFormat());
+                    } else {
+                        officeConverter.convertAndUpload(fileBean.getFileUrl(), fileBean.getFileId() + "." + transcodingResourceReqDTO.getFormat());
+                    }
+                    //根据文件id修改预览地址
+                    ResourceFile newFile = new ResourceFile();
+                    newFile.setFileId(fileBean.getFileId());
+                    if(videoList.contains(userFile.getExtendName())||wordList.contains(userFile.getExtendName())){
+                        newFile.setPreviewFileUrl("converted/" + fileBean.getFileId() + "." + transcodingResourceReqDTO.getFormat());
+                    }else{
+                        newFile.setPreviewFileUrl(fileBean.getFileUrl());
+                    }
+                    resourceFileMapper.updateById(newFile);
+                } catch (Exception ex) {
+                    log.error("格式转换:"+ex.getMessage());
+                    return CommonResult.error("格式转换失败");
+                }
+            }
+        }
+        else
+        {
+            ResourceFile newFile = new ResourceFile();
+            newFile.setFileId(fileBean.getFileId());
+            newFile.setPreviewFileUrl(fileBean.getFileUrl());
+            resourceFileMapper.updateById(newFile);
+        }
+        return CommonResult.ok("格式转换成功");
+    }
 
     @Override
     public void transcodingResourceParallel(List<ConsumerRecord<String, String>> consumerRecordList) {

+ 27 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/util/LocalCache.java

@@ -0,0 +1,27 @@
+package vip.xiaonuo.disk.util;
+
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.ConcurrentHashMap;
+@Component
+public class LocalCache {
+
+    // 创建并初始化一个 ConcurrentHashMap 作为缓存
+    private ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>();
+
+    // 存储 List 的方法
+    public void put(String key, String value) {
+        cache.put(key, value);
+    }
+
+    // 获取 List 的方法
+    public String get(String key) {
+        return cache.get(key);
+    }
+
+    // 删除 List 的方法
+    public void remove(String key) {
+        cache.remove(key);
+    }
+
+}

+ 154 - 17
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/util/MinioUtil.java

@@ -1,23 +1,44 @@
 package vip.xiaonuo.disk.util;
 
 
+import com.alibaba.fastjson2.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.qiwenshare.common.util.DateUtil;
+import com.qiwenshare.ufop.constant.UploadFileStatusEnum;
+import com.qiwenshare.ufop.util.UFOPUtils;
 import io.minio.*;
+import io.minio.errors.*;
 import io.minio.messages.DeleteError;
 import io.minio.messages.DeleteObject;
 import io.minio.messages.Item;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
-import vip.xiaonuo.disk.domain.FileLink;
-import vip.xiaonuo.disk.domain.UploadFile;
+import vip.xiaonuo.auth.core.util.StpLoginUserUtil;
+import vip.xiaonuo.disk.commpen.ProducerCommpen;
+import vip.xiaonuo.disk.component.ResourceFileDealComp;
+import vip.xiaonuo.disk.domain.*;
+import vip.xiaonuo.disk.dto.file.TranscodingResourceReqDTO;
+import vip.xiaonuo.disk.io.QiwenFile;
+import vip.xiaonuo.disk.mapper.ImageMapper;
+import vip.xiaonuo.disk.mapper.ResourceFileMapper;
+import vip.xiaonuo.disk.mapper.ResourceUserFileMapper;
+import vip.xiaonuo.disk.mapper.UploadTaskMapper;
 
 import javax.annotation.Resource;
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
 import java.io.IOException;
 import java.io.InputStream;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.concurrent.ExecutorService;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
@@ -40,6 +61,24 @@ public class MinioUtil {
     @Resource
     private MinioClient minioClient;
 
+    @Resource
+    ResourceFileMapper resourceFileMapper;
+
+    @Resource
+    ResourceUserFileMapper resourceUserFileMapper;
+
+    @Resource
+    ResourceFileDealComp resourceFileDealComp;
+    @Resource
+    private ProducerCommpen producer;
+    @Resource
+    private KafKaProducerUtil kafKaProducerUtil;
+
+    @Resource
+    UploadTaskMapper uploadTaskMapper;
+
+    @Resource
+    ImageMapper imageMapper;
 
     Logger logger = LoggerFactory.getLogger(MinioUtil.class);
 
@@ -65,11 +104,19 @@ public class MinioUtil {
      * @return
      */
     public List<String> checkFilesExits(List<UploadFile> md5List) {
+
         ArrayList<String> noExitList = new ArrayList<>();
+
+
         md5List.forEach(item -> {
             try {
-                logger.info("校验是否存在的文件名是: files/" + item.getMd5() + item.getFileSuffix());
-                minioClient.statObject(StatObjectArgs.builder().bucket(bucket).object("files/" + item.getMd5() + item.getFileSuffix()).build());
+
+                String fileSuffix=item.getFileSuffix().substring(1, item.getFileSuffix().length());
+
+                String fileUrl = UFOPUtils.getUploadFileUrl(item.getMd5(), fileSuffix);
+
+                logger.info("校验是否存在的文件名是: "+fileUrl);
+                minioClient.statObject(StatObjectArgs.builder().bucket(bucket).object(fileUrl).build());
             } catch (Exception e) {
                 logger.info("该文件需要上传,md5值是:" + item.getMd5());
                 // 未存在的文件添加到队列中,前端仍需上传
@@ -88,7 +135,7 @@ public class MinioUtil {
      * @param fileSize    分块文件大小
      * @return 上传结果
      */
-    public Boolean upload(String md5, Integer chunkIndex, InputStream inputStream, long fileSize) {
+    public Boolean upload(String md5, Integer chunkIndex, InputStream inputStream , long fileSize) {
         logger.info("开始上传, 文件" + md5);
         logger.info("开始上传, 文件" + chunkIndex);
 
@@ -202,7 +249,10 @@ public class MinioUtil {
      * @param fileSuffix
      * @return
      */
-    public Boolean merge(String md5, Integer chunkTotal, String fileSuffix) {
+    public Boolean merge(String md5, Integer chunkTotal, String fileSuffix,long fileSize,String fileName) {
+        fileSuffix=fileSuffix.substring(1, fileSuffix.length());
+
+        String fileUrl = UFOPUtils.getUploadFileUrl(md5, fileSuffix);
         logger.info("开始合并");
         // 获取所有分块
         List<Item> itemList = getChunkList(md5);
@@ -227,7 +277,7 @@ public class MinioUtil {
             minioClient.composeObject(
                     ComposeObjectArgs.builder()
                             .bucket(bucket)
-                            .object("files/" + md5 + fileSuffix)
+                            .object(fileUrl)
                             .sources(sourceObjectList)
                             .build());
 
@@ -237,10 +287,10 @@ public class MinioUtil {
             return false;
         }
         logger.info("合并成功了...");
-//        http://192.168.123.101:9000/sph/files/71149ab18f8279400e4ba5269ae295f5.jpg
-        String url = endpoint + "/" + bucket + "/" + "files/" + md5 + fileSuffix;
+
+        String url = fileUrl;
         logger.info("插入的的url是:" + url);
-        Boolean insertResult = this.insertUrl(url);
+        Boolean insertResult = this.insertUrl(url, fileSize,md5,fileName);
         Boolean deleteResult = this.deleteChunks(md5);
         logger.info(insertResult.toString());
         logger.info(deleteResult.toString());
@@ -249,10 +299,18 @@ public class MinioUtil {
             return true;
         } else {
             logger.error("插入失败...");
+            UploadTask uploadTask = new UploadTask();
+            uploadTask.setIdentifier(md5);
+            uploadTask.setUploadTime(DateUtil.getCurrentTime());
+            uploadTask.setUploadStatus(UploadFileStatusEnum.FAIL.getCode());
+            uploadTask.setFileName(fileName);
+            uploadTask.setFilePath("/");
+            uploadTask.setExtendName(fileSuffix);
+            uploadTask.setUserId(StpLoginUserUtil.getLoginUser().getId());
+            uploadTaskMapper.insert(uploadTask);
             return false;
         }
 
-
     }
 
 
@@ -262,12 +320,91 @@ public class MinioUtil {
      * @param url
      * @return
      */
-    public Boolean insertUrl(String url) {
-        FileLink fileLink = new FileLink();
-        fileLink.setUrl(url);
-  //      mongoTemplate.insert(fileLink);
-    //    List<FileLink> fileLinks = mongoTemplate.find(Query.query(Criteria.where("url").is(url)), FileLink.class);
-        return false;
+    public Boolean insertUrl(String url,long fileSize,String md5,String fileName) {
+        ResourceFile file = new ResourceFile(url,fileSize,3,md5, StpLoginUserUtil.getLoginUser().getId());
+        resourceFileMapper.insert(file);
+
+        QiwenFile qiwenFile = new QiwenFile("/", fileName, false);
+
+        ResourceUserFile resourceUserFile = new ResourceUserFile(qiwenFile, StpLoginUserUtil.getLoginUser().getId(), file.getFileId());
+        resourceUserFile.setIsCollet("0");
+        try {
+            resourceUserFile.setIsCollet("0");
+            resourceUserFileMapper.insert(resourceUserFile);
+
+        } catch (Exception e) {
+            ResourceUserFile userFile1 = resourceUserFileMapper.selectOne(new QueryWrapper<ResourceUserFile>().lambda()
+                    .eq(ResourceUserFile::getUserId, resourceUserFile.getUserId())
+                    .eq(ResourceUserFile::getFilePath, resourceUserFile.getFilePath())
+                    .eq(ResourceUserFile::getFileName, resourceUserFile.getFileName())
+                    .eq(ResourceUserFile::getExtendName, resourceUserFile.getExtendName())
+                    .eq(ResourceUserFile::getDeleteFlag, resourceUserFile.getDeleteFlag())
+                    .eq(ResourceUserFile::getIsDir, resourceUserFile.getIsDir()));
+            ResourceFile file1 = resourceFileMapper.selectById(userFile1.getFileId());
+            if (!StringUtils.equals(md5, file1.getIdentifier())) {
+                logger.warn("文件冲突重命名处理: {}", JSON.toJSONString(userFile1));
+                String fileName1 = resourceFileDealComp.getRepeatFileName(resourceUserFile, resourceUserFile.getFilePath());
+                resourceUserFile.setFileName(fileName1);
+                resourceUserFileMapper.insert(resourceUserFile);
+            }
+        }
+        ExecutorService threadPool = CheckThreadPool.getExecutor();
+        try {
+            threadPool.execute(() -> {
+                TranscodingResourceReqDTO transcodingResourceReqDTO = new TranscodingResourceReqDTO();
+                transcodingResourceReqDTO.setUserFileId(resourceUserFile.getUserFileId());
+                transcodingResourceReqDTO.setUserId(StpLoginUserUtil.getLoginUser().getId());
+                kafKaProducerUtil.sendTranscodingResource(producer.getProducer(), JSON.toJSONString(transcodingResourceReqDTO));
+            });
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        //上传成功同时用新线程异步转换格式用来创建预览用的文件
+        if (fileName.contains("/")) {
+            QiwenFile finalQiwenFile = qiwenFile;
+            threadPool.execute(()->{
+                resourceFileDealComp.restoreParentFilePath(finalQiwenFile, StpLoginUserUtil.getLoginUser().getId());
+            });
+        }
+
+        UploadTask uploadTask = new UploadTask();
+        uploadTask.setIdentifier(md5);
+        uploadTask.setUploadTime(DateUtil.getCurrentTime());
+        uploadTask.setUploadStatus(UploadFileStatusEnum.SUCCESS.getCode());
+        uploadTask.setFileName(qiwenFile.getNameNotExtend());
+        uploadTask.setFilePath(qiwenFile.getParent());
+        uploadTask.setExtendName(qiwenFile.getExtendName());
+        uploadTask.setUserId(StpLoginUserUtil.getLoginUser().getId());
+        uploadTaskMapper.insert(uploadTask);
+
+
+        if (UFOPUtils.isImageFile(qiwenFile.getExtendName())) {
+            InputStream inputStream = null;
+            try {
+                MinioClient minioClient =
+                        MinioClient.builder().endpoint(endpoint)
+                                .credentials(accessKey, secretKey).build();
+
+                inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(bucket).object(url).build());
+
+                BufferedImage src  = ImageIO.read(inputStream);
+                Image image = new Image();
+                image.setImageWidth(src.getWidth());
+                image.setImageHeight(src.getHeight());
+                image.setFileId(file.getFileId());
+                imageMapper.insert(image);
+            } catch (IOException | InternalException | XmlParserException | InvalidResponseException |
+                     InvalidKeyException | NoSuchAlgorithmException | ErrorResponseException |
+                     InsufficientDataException | ServerException e) {
+                logger.error("生成图片缩略图失败!", e);
+                e.printStackTrace();
+            } finally {
+                IOUtils.closeQuietly(inputStream);
+            }
+        }
+        resourceFileDealComp.parseMusicFile(qiwenFile.getExtendName(), 3, url, file.getFileId());
+        return true;
     }
 
 

+ 13 - 10
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/util/PdfUtils.java

@@ -2,7 +2,6 @@ package vip.xiaonuo.disk.util;
 
 import io.minio.DownloadObjectArgs;
 import io.minio.MinioClient;
-import io.minio.UploadObjectArgs;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
@@ -20,6 +19,8 @@ public class PdfUtils {
     private final MinioClient minioClient;
     private final String tempDir = System.getProperty("java.io.tmpdir") + "/minio-converter/";
 
+    @Autowired
+    private  S3MultiThreadMultipartUpload s3MultiThreadMultipartUpload;
     // 使用构造函数注入@Value属性
     @Autowired
     public PdfUtils(
@@ -39,6 +40,8 @@ public class PdfUtils {
         createTempDir();
     }
 
+
+
     private void createTempDir() {
         File dir = new File(tempDir);
         if (!dir.exists() && !dir.mkdirs()) {
@@ -64,7 +67,7 @@ public class PdfUtils {
              outputPath = convertToPdfLocal(tempFile.getName(),tempFile.getAbsolutePath(), "convert");
 
             // 3. 上传转换结果
-            uploadToMinio(outputPath, fileId);
+            uploadToMinio(objectName,outputPath, fileId);
 
         } finally {
             if (tempFile != null && tempFile.exists()) {
@@ -152,15 +155,15 @@ public class PdfUtils {
         return outputPath;
     }
 
-    private void uploadToMinio(String filePath, String fileId) throws Exception {
+    private void uploadToMinio(String filePath, String fileId,String objectName) throws Exception {
         String fileName = new File(filePath).getName();
-
-        minioClient.uploadObject(
-                UploadObjectArgs.builder()
-                        .bucket(bucketName)
-                        .object("converted/"+fileId)
-                        .filename(filePath)
-                        .build());
+//        minioClient.uploadObject(
+//                UploadObjectArgs.builder()
+//                        .bucket(bucketName)
+//                        .object("converted/"+fileId)
+//                        .filename(filePath)
+//                        .build());
+        s3MultiThreadMultipartUpload.uploadFile(filePath,fileName);
     }
 
     private void cleanTempDir(String filePath,String outputPath) {

+ 130 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/util/S3MultiThreadMultipartUpload.java

@@ -0,0 +1,130 @@
+package vip.xiaonuo.disk.util;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.core.sync.RequestBody;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.model.*;
+
+import java.io.File;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+@Component
+public class S3MultiThreadMultipartUpload {
+
+    private  final  long partSize = 10 * 1024 * 1024; // 10MB
+    private  final int threadCount = 4;
+
+    @Value("${ufop.minio.endpoint}")
+    private String endpoint;
+
+    @Value("${ufop.minio.access-key}")
+    private String accessKey;
+
+    @Value("${ufop.minio.secret-key}")
+    private String secretKey;
+
+    @Value("${ufop.minio.bucket-name}")
+    private String bucket;
+
+
+
+    public  void uploadFile(String filePath, String objectName) throws Exception {
+        S3Client s3 = S3Client.builder()
+                .endpointOverride(java.net.URI.create(endpoint))
+                .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, secretKey)))
+                .region(Region.US_EAST_1)
+                .forcePathStyle(true)
+                .build();
+
+        long start = System.currentTimeMillis();
+        File file = new File(filePath);
+        long fileSize = file.length();
+        int partCount = (int) ((fileSize + partSize - 1) / partSize);
+
+        // 1. 初始化分片上传
+        CreateMultipartUploadResponse createResp = s3.createMultipartUpload(
+                CreateMultipartUploadRequest.builder()
+                        .bucket(bucket)
+                        .key(objectName)
+                        .build()
+        );
+        String uploadId = createResp.uploadId();
+
+        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
+        List<Future<CompletedPart>> futures = new ArrayList<>();
+
+        try {
+            for (int partNumber = 1; partNumber <= partCount; partNumber++) {
+                final int pn = partNumber;
+                futures.add(executor.submit(() -> {
+                    long offset = (pn - 1) * partSize;
+                    long size = Math.min(partSize, fileSize - offset);
+                    byte[] buffer = new byte[(int) size];
+                    try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
+                        raf.seek(offset);
+                        int read = raf.read(buffer);
+                        if (read != size) throw new RuntimeException("Read error");
+                    }
+                    UploadPartResponse uploadPartResp = s3.uploadPart(
+                            UploadPartRequest.builder()
+                                    .bucket(bucket)
+                                    .key(objectName)
+                                    .uploadId(uploadId)
+                                    .partNumber(pn)
+                                    .contentLength(size)
+                                    .build(),
+                            RequestBody.fromBytes(buffer)
+                    );
+                    return CompletedPart.builder()
+                            .partNumber(pn)
+                            .eTag(uploadPartResp.eTag())
+                            .build();
+                }));
+            }
+
+            List<CompletedPart> completedParts = new ArrayList<>();
+            for (Future<CompletedPart> f : futures) {
+                completedParts.add(f.get());
+            }
+            completedParts.sort(Comparator.comparingInt(CompletedPart::partNumber));
+
+            // 4. 合并分片
+            s3.completeMultipartUpload(
+                    CompleteMultipartUploadRequest.builder()
+                            .bucket(bucket)
+                            .key(objectName)
+                            .uploadId(uploadId)
+                            .multipartUpload(
+                                    CompletedMultipartUpload.builder()
+                                            .parts(completedParts)
+                                            .build()
+                            )
+                            .build()
+            );
+            System.out.println("Upload complete: " + objectName + ", time: " + (System.currentTimeMillis() - start) / 1000.0 + "s");
+        } catch (Exception e) {
+            s3.abortMultipartUpload(
+                    AbortMultipartUploadRequest.builder()
+                            .bucket(bucket)
+                            .key(objectName)
+                            .uploadId(uploadId)
+                            .build()
+            );
+            throw e;
+        } finally {
+            executor.shutdown();
+        }
+    }
+
+
+}