亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

利用SpringBoot實現(xiàn)高效的文件分塊上傳方案

 更新時間:2025年06月30日 08:53:40   作者:皮皮林551  
在互聯(lián)網(wǎng)應用中,大文件上傳是一個常見而棘手的挑戰(zhàn),傳統(tǒng)的單文件上傳方式在面對大文件時經(jīng)常面臨超時、內(nèi)存溢出等問題,本文將深入探討如何利用Spring Boot實現(xiàn)高效的分塊上傳方案,解決大文件傳輸痛點,需要的朋友可以參考下

一、為什么需要文件分塊上傳?

當文件上傳超過100MB時,傳統(tǒng)上傳方式存在三大痛點:

  • 網(wǎng)絡傳輸不穩(wěn)定:  單次請求時間長,容易中斷
  • 服務器資源耗盡:  大文件一次性加載導致內(nèi)存溢出
  • 上傳失敗代價高:  需要重新上傳整個文件

分塊上傳的優(yōu)勢

  • 減小單次請求負載
  • 支持斷點續(xù)傳
  • 并發(fā)上傳提高效率
  • 降低服務器內(nèi)存壓力

二、分塊上傳核心原理

三、Spring Boot實現(xiàn)方案

1. 核心依賴

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.11.0</version>
    </dependency>
</dependencies>

2. 關鍵控制器實現(xiàn)

@RestController
@RequestMapping("/upload")
publicclassChunkUploadController{
    
    privatefinal String CHUNK_DIR = "uploads/chunks/";
    privatefinal String FINAL_DIR = "uploads/final/";
    
    /**
     * 初始化上傳
     * @param fileName 文件名
     * @param fileMd5 文件唯一標識
     */
    @PostMapping("/init")
    public ResponseEntity<String> initUpload(
            @RequestParam String fileName,
            @RequestParam String fileMd5){
        
        // 創(chuàng)建分塊臨時目錄
        String uploadId = UUID.randomUUID().toString();
        Path chunkDir = Paths.get(CHUNK_DIR, fileMd5 + "_" + uploadId);
        try {
            Files.createDirectories(chunkDir);
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("創(chuàng)建目錄失敗");
        }
        return ResponseEntity.ok(uploadId);
    }
    
    /**
     * 上傳分塊
     * @param chunk 分塊文件
     * @param index 分塊索引
     */
    @PostMapping("/chunk")
    public ResponseEntity<String> uploadChunk(
            @RequestParam MultipartFile chunk,
            @RequestParam String uploadId,
            @RequestParam String fileMd5,
            @RequestParam Integer index){
        
        // 生成分塊文件名
        String chunkName = "chunk_" + index + ".tmp";
        Path filePath = Paths.get(CHUNK_DIR, fileMd5 + "_" + uploadId, chunkName);
        
        try {
            chunk.transferTo(filePath);
            return ResponseEntity.ok("分塊上傳成功");
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("分塊保存失敗");
        }
    }
    
    /**
     * 合并文件分塊
     */
    @PostMapping("/merge")
    public ResponseEntity<String> mergeChunks(
            @RequestParam String fileName,
            @RequestParam String uploadId,
            @RequestParam String fileMd5){
        
        // 1. 獲取分塊目錄
        File chunkDir = new File(CHUNK_DIR + fileMd5 + "_" + uploadId);
        
        // 2. 獲取排序后的分塊文件
        File[] chunks = chunkDir.listFiles();
        if (chunks == null || chunks.length == 0) {
            return ResponseEntity.badRequest().body("無分塊文件");
        }
        
        Arrays.sort(chunks, Comparator.comparingInt(f -> 
            Integer.parseInt(f.getName().split("_")[1].split("\.")[0])));
        
        // 3. 合并文件
        Path finalPath = Paths.get(FINAL_DIR, fileName);
        try (BufferedOutputStream outputStream = 
             new BufferedOutputStream(Files.newOutputStream(finalPath))) {
            
            for (File chunkFile : chunks) {
                Files.copy(chunkFile.toPath(), outputStream);
            }
            
            // 4. 清理臨時分塊
            FileUtils.deleteDirectory(chunkDir);
            
            return ResponseEntity.ok("文件合并成功:" + finalPath);
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("合并失?。? + e.getMessage());
        }
    }
}

3. 高性能文件合并優(yōu)化

當處理超大文件(10GB以上)時,需要避免將所有內(nèi)容加載到內(nèi)存:

// 使用RandomAccessFile提高性能
publicvoidmergeFiles(File targetFile, List<File> chunkFiles)throws IOException {
    
    try (RandomAccessFile target = 
         new RandomAccessFile(targetFile, "rw")) {
        
        byte[] buffer = newbyte[1024 * 8]; // 8KB緩沖區(qū)
        long position = 0;
        
        for (File chunk : chunkFiles) {
            try (RandomAccessFile src = 
                 new RandomAccessFile(chunk, "r")) {
                
                int bytesRead;
                while ((bytesRead = src.read(buffer)) != -1) {
                    target.write(buffer, 0, bytesRead);
                }
                position += chunk.length();
            }
        }
    }
}

四、前端實現(xiàn)關鍵代碼(Vue示例)

1. 分塊處理函數(shù)

// 5MB分塊大小
const CHUNK_SIZE = 5 * 1024 * 1024; 

/**
 * 處理文件分塊
 */
functionprocessFile(file) {
    const chunkCount = Math.ceil(file.size / CHUNK_SIZE);
    const chunks = [];
    
    for (let i = 0; i < chunkCount; i++) {
        const start = i * CHUNK_SIZE;
        const end = Math.min(file.size, start + CHUNK_SIZE);
        chunks.push(file.slice(start, end));
    }
    return chunks;
}

2. 帶進度顯示的上傳邏輯

asyncfunctionuploadFile(file) {
    // 1. 初始化上傳
    const { data: uploadId } = await axios.post('/upload/init', {
        fileName: file.name,
        fileMd5: await calculateFileMD5(file) // 文件MD5計算
    });
    
    // 2. 分塊上傳
    const chunks = processFile(file);
    const total = chunks.length;
    let uploaded = 0;
    
    awaitPromise.all(chunks.map((chunk, index) => {
        const formData = new FormData();
        formData.append('chunk', chunk, `chunk_${index}`);
        formData.append('index', index);
        formData.append('uploadId', uploadId);
        formData.append('fileMd5', fileMd5);
        
        return axios.post('/upload/chunk', formData, {
            headers: {'Content-Type': 'multipart/form-data'},
            onUploadProgress: progress => {
                // 更新進度條
                const percent = ((uploaded * 100) / total).toFixed(1);
                updateProgress(percent);
            }
        }).then(() => uploaded++);
    }));
    
    // 3. 觸發(fā)合并
    const result = await axios.post('/upload/merge', {
        fileName: file.name,
        uploadId,
        fileMd5
    });
    
    alert(`上傳成功: ${result.data}`);
}

五、企業(yè)級優(yōu)化方案

1. 斷點續(xù)傳實現(xiàn)

服務端增加檢查接口:

@GetMapping("/check/{fileMd5}/{uploadId}")
public ResponseEntity<List<Integer>> getUploadedChunks(
        @PathVariable String fileMd5,
        @PathVariable String uploadId) {
    
    Path chunkDir = Paths.get(CHUNK_DIR, fileMd5 + "_" + uploadId);
    if (!Files.exists(chunkDir)) {
        return ResponseEntity.ok(Collections.emptyList());
    }
    
    try {
        List<Integer> uploaded = Files.list(chunkDir)
                .map(p -> p.getFileName().toString())
                .filter(name -> name.startsWith("chunk_"))
                .map(name -> name.replace("chunk_", "").replace(".tmp", ""))
                .map(Integer::parseInt)
                .collect(Collectors.toList());
                
        return ResponseEntity.ok(uploaded);
    } catch (IOException e) {
        return ResponseEntity.status(500).body(Collections.emptyList());
    }
}

前端上傳前檢查:

const uploadedChunks = await axios.get(
    `/upload/check/${fileMd5}/${uploadId}`
);

chunks.map((chunk, index) => {
    if (uploadedChunks.includes(index)) {
        uploaded++; // 已上傳則跳過
        returnPromise.resolve(); 
    }
    // 執(zhí)行上傳...
});

2. 分塊安全驗證

使用HmacSHA256確保分塊完整性:

@PostMapping("/chunk")
public ResponseEntity<?> uploadChunk(
        @RequestParam MultipartFile chunk,
        @RequestParam String sign // 前端生成的簽名
        ) {
    
    // 使用密鑰驗證簽名
    String secretKey = "your-secret-key";
    String serverSign = HmacUtils.hmacSha256Hex(secretKey, 
            chunk.getBytes());
    
    if (!serverSign.equals(sign)) {
        return ResponseEntity.status(403).body("簽名驗證失敗");
    }
    
    // 處理分塊...
}

3. 云存儲集成(MinIO示例)

@Configuration
publicclassMinioConfig{
    
    @Bean
    public MinioClient minioClient(){
        return MinioClient.builder()
                .endpoint("http://minio:9000")
                .credentials("minio-access", "minio-secret")
                .build();
    }
}

@Service
publicclassMinioUploadService{
    
    @Autowired
    private MinioClient minioClient;
    
    publicvoiduploadChunk(String bucket, 
                            String object, 
                            InputStream chunkStream, 
                            long length)throws Exception {
        
        minioClient.putObject(
            PutObjectArgs.builder()
                .bucket(bucket)
                .object(object)
                .stream(chunkStream, length, -1)
                .build()
        );
    }
}

六、性能測試對比

我們使用10GB文件進行測試,結果如下:

方案平均上傳時間內(nèi)存占用失敗重傳開銷
傳統(tǒng)上傳3小時+10GB+100%
分塊上傳(單線程)1.5小時100MB≈10%
分塊上傳(多線程)20分鐘100MB<1%

七、最佳實踐建議

分塊大小選擇

  • 內(nèi)網(wǎng)環(huán)境:10MB-20MB
  • 移動網(wǎng)絡:1MB-5MB
  • 廣域網(wǎng):500KB-1MB

定時清理策略

@Scheduled(fixedRate = 24 * 60 * 60 * 1000) // 每日清理
publicvoidcleanTempFiles(){
    File tempDir = new File(CHUNK_DIR);
    // 刪除超過24小時的臨時目錄
    FileUtils.deleteDirectory(tempDir);
}

限流保護

spring:
  servlet:
    multipart:
      max-file-size:100MB# 單塊最大限制
      max-request-size:100MB

結語

Spring Boot實現(xiàn)文件分塊上傳解決了大文件傳輸?shù)暮诵耐袋c,結合斷點續(xù)傳、分塊驗證和安全控制,可構建出健壯的企業(yè)級文件傳輸方案。本文提供的代碼可直接集成到生產(chǎn)環(huán)境,根據(jù)實際需求調(diào)整分塊大小和并發(fā)策略。

以上就是利用SpringBoot實現(xiàn)高效的文件分塊上傳方案的詳細內(nèi)容,更多關于SpringBoot文件分塊上傳的資料請關注腳本之家其它相關文章!

相關文章

最新評論