Переглянути джерело

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

liudongzhi 1 місяць тому
батько
коміт
4e2f441e41

+ 54 - 47
alien-store/src/main/java/shop/alien/store/util/FileUploadUtil.java

@@ -17,6 +17,7 @@ import shop.alien.util.file.FileUtil;
 
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.nio.channels.Channels;
 import java.nio.channels.FileChannel;
@@ -214,16 +215,28 @@ public class FileUploadUtil {
                 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"));
-                });
-                
-                // 复制文件用于截图(使用优化的流式复制)
+                // 重要:必须先保存文件到本地,因为 MultipartFile 的临时文件可能被清理
+                // 在并行处理之前,先确保文件已经保存到本地
                 String cacheVideoPath = copyFileOptimized(uploadDir, multipartFile);
                 File videoFile = new File(cacheVideoPath);
                 
-                // 等待上传完成(如果复制已完成,这里不会阻塞太久)
+                if (!videoFile.exists() || videoFile.length() == 0) {
+                    throw new RuntimeException("视频文件复制失败,文件不存在或为空: " + cacheVideoPath);
+                }
+                
+                // 使用本地文件进行上传(避免 MultipartFile 临时文件被清理的问题)
+                File localVideoFile = videoFile;
+                CompletableFuture<String> uploadFuture = CompletableFuture.supplyAsync(() -> {
+                    try {
+                        // 使用本地文件上传,而不是 MultipartFile
+                        return aliOSSUtil.uploadFile(localVideoFile, prefix + videoFileName + "." + fileNameAndType.get("type"));
+                    } catch (Exception e) {
+                        log.error("视频上传失败", e);
+                        throw new RuntimeException("视频上传失败: " + e.getMessage(), e);
+                    }
+                });
+                
+                // 等待上传完成
                 String videoUrl = uploadFuture.get();
                 filePathList.add(videoUrl);
                 
@@ -362,6 +375,8 @@ public class FileUploadUtil {
     /**
      * 优化的文件复制方法 - 使用流式复制,避免一次性读取全部字节
      * 对于大文件(如视频),性能提升显著
+     * 
+     * 注意:MultipartFile 的临时文件可能被清理,需要立即读取
      *
      * @param localFilePath 本地路径
      * @param file          文件
@@ -370,8 +385,6 @@ public class FileUploadUtil {
     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);
@@ -383,53 +396,47 @@ public class FileUploadUtil {
             Path path = Paths.get(localFilePath, fileName);
             Files.createDirectories(path.getParent());
             
-            // 使用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.copyFileOptimized ERROR Msg={}", e.getMessage(), e);
-            // 降级到传统方式
+            // 重要:立即获取 InputStream,因为临时文件可能很快被清理
+            // 使用 transferTo 方法,这是 MultipartFile 推荐的方式
             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);
+                // 优先使用 transferTo 方法(Spring 5.1+ 支持,性能最好)
+                file.transferTo(targetFile);
+                return localFilePath + fileName;
+            } catch (IllegalStateException | IOException e) {
+                // 如果 transferTo 失败(可能是临时文件已被清理),使用流式复制
+                log.warn("transferTo 失败,使用流式复制: {}", e.getMessage());
+                
+                // 重新获取 InputStream(如果可能)
+                inputStream = file.getInputStream();
+                outputStream = new FileOutputStream(targetFile);
+                
+                // 使用缓冲流进行复制(8KB 缓冲区)
+                byte[] buffer = new byte[8192];
+                int bytesRead;
+                long totalBytes = 0;
+                long fileSize = file.getSize();
+                
+                while ((bytesRead = inputStream.read(buffer)) != -1) {
+                    outputStream.write(buffer, 0, bytesRead);
+                    totalBytes += bytesRead;
+                    
+                    // 如果文件大小已知,可以显示进度
+                    if (fileSize > 0 && totalBytes % (10 * 1024 * 1024) == 0) {
+                        log.debug("文件复制进度: {}/{} bytes", totalBytes, fileSize);
                     }
                 }
-                return localFilePath + file.getOriginalFilename();
-            } catch (Exception ex) {
-                log.error("FileUpload.copyFileOptimized 降级方案也失败 Msg={}", ex.getMessage(), ex);
-                throw new RuntimeException("文件复制失败: " + ex.getMessage(), ex);
+                
+                outputStream.flush();
+                return localFilePath + fileName;
             }
+        } catch (Exception e) {
+            log.error("FileUpload.copyFileOptimized ERROR Msg={}", e.getMessage(), e);
+            throw new RuntimeException("文件复制失败: " + e.getMessage(), e);
         } 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) {