Просмотр исходного кода

优化上传视频接口,尝试,有问题直接回退

liudongzhi 1 месяц назад
Родитель
Сommit
0e8b2750c9
1 измененных файлов с 242 добавлено и 81 удалено
  1. 242 81
      alien-store/src/main/java/shop/alien/store/util/FileUploadUtil.java

+ 242 - 81
alien-store/src/main/java/shop/alien/store/util/FileUploadUtil.java

@@ -16,10 +16,18 @@ import shop.alien.util.common.VideoUtils;
 import shop.alien.util.file.FileUtil;
 
 import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
 import java.util.*;
+import java.util.concurrent.*;
+import java.util.stream.Collectors;
 
 /**
  * 二期-文件上传
@@ -118,7 +126,7 @@ public class FileUploadUtil {
     }
 
     /**
-     * 上传多个文件
+     * 上传多个文件(性能优化版 - 并行处理 + 流式复制)
      *
      * @param multipartRequest 多文件
      * @return List<String>
@@ -127,82 +135,173 @@ public class FileUploadUtil {
         try {
             log.info("FileUpload.uploadMoreFile multipartRequest={}", multipartRequest.getFileNames());
             Set<String> fileNameSet = multipartRequest.getMultiFileMap().keySet();
-            List<String> filePathList = new ArrayList<>();
-            for (String s : fileNameSet) {
-                MultipartFile multipartFile = multipartRequest.getFileMap().get(s);
-                log.info("FileUpload.uploadMoreFile fileName={}", multipartFile.getOriginalFilename());
-                String uploadDir = this.uploadDir.replace("file:///", "").replace("\\", "/");
-                String prefix;
-                Map<String, String> fileNameAndType = FileUtil.getFileNameAndType(multipartFile);
-                //区分文件类型
-                if (imageFileType.contains(fileNameAndType.get("type").toLowerCase())) {
-                    uploadDir += "/image";
-                    prefix = "image/";
-                    log.info("FileUpload.uploadMoreFile 获取到图片文件准备复制 {} {} {}", uploadDir, prefix, multipartFile.getOriginalFilename());
-                    // 去除文件名中的逗号,避免URL拼接时被错误分割
-                    String imageFileName = fileNameAndType.get("name").replaceAll(",", "");
-                    filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + imageFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
-                    ;
-                } else if (videoFileType.contains(fileNameAndType.get("type").toLowerCase())) {
-                    uploadDir += "/video/";
-                    prefix = "video/";
-                    //上传视频文件
-                    log.info("FileUpload.uploadMoreFile 获取到视频文件准备复制 {} {} {}", uploadDir, prefix, multipartFile.getOriginalFilename());
-                    // 去除文件名中的逗号,避免URL拼接时被错误分割
-                    String videoFileName = fileNameAndType.get("name").replaceAll(",", "") + RandomCreateUtil.getRandomNum(6);
-                    String cacheVideoPath = copyFile(uploadDir, multipartFile);
-                    filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + videoFileName + "." + fileNameAndType.get("type")));
-                    //缓存视频截图使用
-                    File videoFile = new File(cacheVideoPath);
-                    //获取视频某帧截图
-                    log.info("FileUpload.uploadMoreFile 视频文件复制完毕, 获取第一秒图片 {}", videoFile.getName());
-                    String videoPath = videoUtils.getImg(uploadDir + videoFile.getName());
-                    log.info("FileUpload.uploadMoreFile 视频文件复制完毕, 图片位置 {}", videoPath);
-                    if (!videoPath.isEmpty()) {
-                        File videoImgFile = new File(videoPath);
-                        Map<String, String> videoImg = FileUtil.getFileNameAndType(videoImgFile);
-                        filePathList.add(aliOSSUtil.uploadFile(videoImgFile, prefix + videoFileName + "." + videoImg.get("type")));
-                        videoImgFile.delete();
-                        videoFile.delete();
-                    } else {
-                        throw new RuntimeException("视频截图失败");
+            String uploadDir = this.uploadDir.replace("file:///", "").replace("\\", "/");
+            
+            // 如果只有一个文件,直接处理(避免线程池开销)
+            if (fileNameSet.size() == 1) {
+                return uploadMoreFileSequential(multipartRequest, fileNameSet, uploadDir);
+            }
+            
+            // 多个文件使用并行处理
+            List<CompletableFuture<List<String>>> futures = new ArrayList<>();
+            ExecutorService executor = createUploadExecutor();
+            
+            try {
+                for (String s : fileNameSet) {
+                    MultipartFile multipartFile = multipartRequest.getFileMap().get(s);
+                    CompletableFuture<List<String>> future = CompletableFuture.supplyAsync(() -> {
+                        return processSingleFile(multipartFile, uploadDir);
+                    }, executor);
+                    futures.add(future);
+                }
+                
+                // 等待所有任务完成并合并结果
+                List<String> filePathList = new ArrayList<>();
+                for (CompletableFuture<List<String>> future : futures) {
+                    try {
+                        filePathList.addAll(future.get());
+                    } catch (Exception e) {
+                        log.error("处理文件失败", e);
+                        throw new RuntimeException("文件处理失败: " + e.getMessage(), e);
                     }
-                } else if (voiceFileType.contains(fileNameAndType.get("type").toLowerCase())) {
-                    uploadDir += "/voice";
-                    prefix = "voice/";
-                    log.info("FileUpload.uploadMoreFile 获取到语音文件准备复制 {} {} {}", uploadDir, prefix, multipartFile.getOriginalFilename());
-                    // 去除文件名中的逗号,避免URL拼接时被错误分割
-                    String voiceFileName = fileNameAndType.get("name").replaceAll(",", "");
-                    filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + voiceFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
-                } else if (privacyFileType.contains(fileNameAndType.get("type").toLowerCase())) {
-                    uploadDir += "/privacy/";
-                    prefix = "privacy/";
-                    log.info("FileUpload.uploadMoreFile 获取到隐私文件准备复制 {} {} {}", uploadDir, prefix, multipartFile.getOriginalFilename());
-                    // 去除文件名中的逗号,避免URL拼接时被错误分割
-                    String privacyFileName = fileNameAndType.get("name").replaceAll(",", "");
-                    filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + privacyFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
-                } else if (pdfFileType.contains(fileNameAndType.get("type").toLowerCase())) {
-                    uploadDir += "/pdf";
-                    prefix = "pdf/";
-                    log.info("FileUpload.uploadMoreFile 获取到PDF文件准备复制 {} {} {}", uploadDir, prefix, multipartFile.getOriginalFilename());
-                    // 去除文件名中的逗号,避免URL拼接时被错误分割
-                    String pdfFileName = fileNameAndType.get("name").replaceAll(",", "");
-                    filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + pdfFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
-                } else if (ohterFileType.contains(fileNameAndType.get("type").toLowerCase())) {
-                    uploadDir += "/other/";
-                    prefix = "other/";
-                    log.info("FileUpload.uploadMoreFile 获取到其他文件准备复制 {} {} {}", uploadDir, prefix, multipartFile.getOriginalFilename());
-                    // 去除文件名中的逗号,避免URL拼接时被错误分割
-                    String otherFileName = fileNameAndType.get("name").replaceAll(",", "");
-                    filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + otherFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
                 }
+                
+                return filePathList;
+            } finally {
+                executor.shutdown();
             }
-            return filePathList;
         } catch (Exception e) {
-            log.error("FileUpload.uploadMoreFile ERROR Msg={}", e.getMessage());
+            log.error("FileUpload.uploadMoreFile ERROR Msg={}", e.getMessage(), e);
             throw new RuntimeException(e);
         }
     }
+    
+    /**
+     * 顺序处理(单个文件时使用,避免线程池开销)
+     */
+    private List<String> uploadMoreFileSequential(MultipartRequest multipartRequest, Set<String> fileNameSet, String uploadDir) {
+        List<String> filePathList = new ArrayList<>();
+        for (String s : fileNameSet) {
+            MultipartFile multipartFile = multipartRequest.getFileMap().get(s);
+            filePathList.addAll(processSingleFile(multipartFile, uploadDir));
+        }
+        return filePathList;
+    }
+    
+    /**
+     * 处理单个文件
+     */
+    private List<String> processSingleFile(MultipartFile multipartFile, String baseUploadDir) {
+        List<String> filePathList = new ArrayList<>();
+        try {
+            log.info("FileUpload.processSingleFile fileName={}", multipartFile.getOriginalFilename());
+            String uploadDir = baseUploadDir;
+            String prefix;
+            Map<String, String> fileNameAndType = FileUtil.getFileNameAndType(multipartFile);
+            String fileType = fileNameAndType.get("type").toLowerCase();
+            
+            //区分文件类型
+            if (imageFileType.contains(fileType)) {
+                uploadDir += "/image";
+                prefix = "image/";
+                log.info("FileUpload.processSingleFile 获取到图片文件准备上传 {} {}", prefix, multipartFile.getOriginalFilename());
+                String imageFileName = fileNameAndType.get("name").replaceAll(",", "");
+                filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + imageFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
+                
+            } else if (videoFileType.contains(fileType)) {
+                uploadDir += "/video/";
+                prefix = "video/";
+                log.info("FileUpload.processSingleFile 获取到视频文件准备上传 {} {}", prefix, multipartFile.getOriginalFilename());
+                String videoFileName = fileNameAndType.get("name").replaceAll(",", "") + RandomCreateUtil.getRandomNum(6);
+                
+                // 优化:并行执行视频上传和文件复制(这两个操作互不依赖,可以并行)
+                CompletableFuture<String> uploadFuture = CompletableFuture.supplyAsync(() -> {
+                    return aliOSSUtil.uploadFile(multipartFile, prefix + videoFileName + "." + fileNameAndType.get("type"));
+                });
+                
+                // 复制文件用于截图(使用优化的流式复制)
+                String cacheVideoPath = copyFileOptimized(uploadDir, multipartFile);
+                File videoFile = new File(cacheVideoPath);
+                
+                // 等待上传完成(如果复制已完成,这里不会阻塞太久)
+                String videoUrl = uploadFuture.get();
+                filePathList.add(videoUrl);
+                
+                // 获取视频截图
+                log.info("FileUpload.processSingleFile 视频文件复制完毕, 获取第一秒图片 {}", videoFile.getName());
+                String videoPath = videoUtils.getImg(uploadDir + videoFile.getName());
+                log.info("FileUpload.processSingleFile 视频文件复制完毕, 图片位置 {}", videoPath);
+                
+                if (!videoPath.isEmpty()) {
+                    File videoImgFile = new File(videoPath);
+                    Map<String, String> videoImg = FileUtil.getFileNameAndType(videoImgFile);
+                    filePathList.add(aliOSSUtil.uploadFile(videoImgFile, prefix + videoFileName + "." + videoImg.get("type")));
+                    videoImgFile.delete();
+                    videoFile.delete();
+                } else {
+                    throw new RuntimeException("视频截图失败");
+                }
+                
+            } else if (voiceFileType.contains(fileType)) {
+                uploadDir += "/voice";
+                prefix = "voice/";
+                log.info("FileUpload.processSingleFile 获取到语音文件准备上传 {} {}", prefix, multipartFile.getOriginalFilename());
+                String voiceFileName = fileNameAndType.get("name").replaceAll(",", "");
+                filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + voiceFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
+                
+            } else if (privacyFileType.contains(fileType)) {
+                uploadDir += "/privacy/";
+                prefix = "privacy/";
+                log.info("FileUpload.processSingleFile 获取到隐私文件准备上传 {} {}", prefix, multipartFile.getOriginalFilename());
+                String privacyFileName = fileNameAndType.get("name").replaceAll(",", "");
+                filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + privacyFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
+                
+            } else if (pdfFileType.contains(fileType)) {
+                uploadDir += "/pdf";
+                prefix = "pdf/";
+                log.info("FileUpload.processSingleFile 获取到PDF文件准备上传 {} {}", prefix, multipartFile.getOriginalFilename());
+                String pdfFileName = fileNameAndType.get("name").replaceAll(",", "");
+                filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + pdfFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
+                
+            } else if (ohterFileType.contains(fileType)) {
+                uploadDir += "/other/";
+                prefix = "other/";
+                log.info("FileUpload.processSingleFile 获取到其他文件准备上传 {} {}", prefix, multipartFile.getOriginalFilename());
+                String otherFileName = fileNameAndType.get("name").replaceAll(",", "");
+                filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + otherFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
+            }
+            
+            return filePathList;
+        } catch (Exception e) {
+            log.error("FileUpload.processSingleFile ERROR fileName={}, Msg={}", multipartFile.getOriginalFilename(), e.getMessage(), e);
+            throw new RuntimeException("处理文件失败: " + multipartFile.getOriginalFilename(), e);
+        }
+    }
+    
+    /**
+     * 创建上传任务线程池
+     */
+    private ExecutorService createUploadExecutor() {
+        int corePoolSize = Math.min(Runtime.getRuntime().availableProcessors() * 2, 8);
+        int maxPoolSize = Math.min(Runtime.getRuntime().availableProcessors() * 4, 16);
+        return new ThreadPoolExecutor(
+                corePoolSize,
+                maxPoolSize,
+                60L,
+                TimeUnit.SECONDS,
+                new LinkedBlockingQueue<>(100),
+                new ThreadFactory() {
+                    private int count = 0;
+                    @Override
+                    public Thread newThread(Runnable r) {
+                        Thread t = new Thread(r);
+                        t.setName("file-upload-" + count++);
+                        return t;
+                    }
+                },
+                new ThreadPoolExecutor.CallerRunsPolicy()
+        );
+    }
 
     /**
      * 上传图片
@@ -250,30 +349,92 @@ public class FileUploadUtil {
     }
 
     /**
-     * 复制文件, 返回url链接
+     * 复制文件, 返回url链接(优化版 - 使用流式复制,避免一次性读取大文件)
      *
      * @param localFilePath 本地路径
      * @param file          文件
      * @return 访问url路径
      */
     private String copyFile(String localFilePath, MultipartFile file) {
+        return copyFileOptimized(localFilePath, file);
+    }
+    
+    /**
+     * 优化的文件复制方法 - 使用流式复制,避免一次性读取全部字节
+     * 对于大文件(如视频),性能提升显著
+     *
+     * @param localFilePath 本地路径
+     * @param file          文件
+     * @return 访问url路径
+     */
+    private String copyFileOptimized(String localFilePath, MultipartFile file) {
+        InputStream inputStream = null;
+        FileOutputStream outputStream = null;
+        ReadableByteChannel inputChannel = null;
+        FileChannel outputChannel = null;
+        
         try {
             File cacheFilePath = new File(localFilePath);
             if (!cacheFilePath.exists()) {
                 cacheFilePath.mkdirs();
             }
-            String fileName = file.getOriginalFilename().substring(0, file.getOriginalFilename().lastIndexOf('.'));
-            log.info("FileUpload.copyFile fileName={}", fileName);
-            String fileType = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf('.'));
-            log.info("FileUpload.copyFile fileType={}", fileType);
-            System.out.println(file.getOriginalFilename());
-            Path path = Paths.get(localFilePath, file.getOriginalFilename());
+            
+            String fileName = file.getOriginalFilename();
+            Path path = Paths.get(localFilePath, fileName);
             Files.createDirectories(path.getParent());
-            Files.write(path, file.getBytes());
-            return localFilePath + file.getOriginalFilename();
+            
+            // 使用NIO的Channel进行高效复制(零拷贝技术)
+            File targetFile = path.toFile();
+            inputStream = file.getInputStream();
+            outputStream = new FileOutputStream(targetFile);
+            
+            // 将InputStream转换为ReadableByteChannel
+            inputChannel = Channels.newChannel(inputStream);
+            outputChannel = outputStream.getChannel();
+            
+            // 使用transferFrom进行高效复制,对于大文件性能更好
+            // 对于大文件,分块传输以避免内存问题
+            long transferred = 0;
+            long fileSize = file.getSize();
+            long chunkSize = 8 * 1024 * 1024; // 8MB chunks
+            
+            while (transferred < fileSize) {
+                long remaining = fileSize - transferred;
+                long toTransfer = Math.min(chunkSize, remaining);
+                transferred += outputChannel.transferFrom(inputChannel, transferred, toTransfer);
+            }
+            
+            return localFilePath + fileName;
         } catch (Exception e) {
-            log.error("FileUpload.copyFile ERROR Msg={}", e.getMessage());
-            return e.getMessage();
+            log.error("FileUpload.copyFileOptimized ERROR Msg={}", e.getMessage(), e);
+            // 降级到传统方式
+            try {
+                Path path = Paths.get(localFilePath, file.getOriginalFilename());
+                Files.createDirectories(path.getParent());
+                // 使用缓冲流进行复制
+                try (InputStream is = file.getInputStream();
+                     FileOutputStream fos = new FileOutputStream(path.toFile())) {
+                    byte[] buffer = new byte[8192];
+                    int bytesRead;
+                    while ((bytesRead = is.read(buffer)) != -1) {
+                        fos.write(buffer, 0, bytesRead);
+                    }
+                }
+                return localFilePath + file.getOriginalFilename();
+            } catch (Exception ex) {
+                log.error("FileUpload.copyFileOptimized 降级方案也失败 Msg={}", ex.getMessage(), ex);
+                throw new RuntimeException("文件复制失败: " + ex.getMessage(), ex);
+            }
+        } finally {
+            // 关闭资源
+            try {
+                if (inputChannel != null) inputChannel.close();
+                if (outputChannel != null) outputChannel.close();
+                if (inputStream != null) inputStream.close();
+                if (outputStream != null) outputStream.close();
+            } catch (Exception e) {
+                log.warn("关闭文件流失败", e);
+            }
         }
     }