偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

告別卡頓!SpringBoot 大文件上傳最佳實(shí)踐:分片+并發(fā)+秒傳全搞定

開發(fā) 后端
通過 Spring Boot 構(gòu)建的分片上傳系統(tǒng),不僅解決了大文件傳輸過程中的性能瓶頸,還提供了斷點(diǎn)續(xù)傳、上傳加速、失敗重試、安全校驗(yàn)等完整機(jī)制。結(jié)合前端優(yōu)化與后端云服務(wù)整合,可無縫部署到生產(chǎn)環(huán)境,廣泛適用于音視頻平臺(tái)、文檔系統(tǒng)、網(wǎng)盤等多種場景。

在現(xiàn)代 Web 系統(tǒng)中,尤其是涉及視頻、圖像、大型文檔管理等場景時(shí),大文件上傳常常成為性能瓶頸。傳統(tǒng)上傳方式面對(duì) 100MB 以上的大文件時(shí)常出現(xiàn)超時(shí)、資源溢出、上傳失敗等問題。為了提升上傳效率與穩(wěn)定性,我們構(gòu)建了一套基于 Spring Boot 的高性能文件分片上傳系統(tǒng),結(jié)合斷點(diǎn)續(xù)傳、并發(fā)上傳、安全驗(yàn)證與 MinIO 云存儲(chǔ),打造出企業(yè)級(jí)健壯傳輸方案。

為什么要進(jìn)行文件分片上傳?

在上傳大于 100MB 的文件時(shí),傳統(tǒng)上傳方式暴露出以下問題:

  • 傳輸不穩(wěn)定:長時(shí)間傳輸極易超時(shí)或被網(wǎng)絡(luò)波動(dòng)中斷
  • 資源壓力大:服務(wù)器需要一次性讀取整個(gè)文件,造成內(nèi)存暴漲
  • 失敗代價(jià)高:上傳中斷后無法恢復(fù),只能重新上傳整個(gè)文件

通過將大文件拆分為多個(gè)較小的塊,可以顯著優(yōu)化上述問題:

優(yōu)勢

說明

斷點(diǎn)續(xù)傳

支持中斷后從上次上傳點(diǎn)恢復(fù)

并發(fā)加速

多個(gè)分塊同時(shí)上傳,提升速度

降低服務(wù)器壓力

每個(gè)分塊獨(dú)立上傳,降低內(nèi)存占用

分片上傳的原理

分片上傳核心思想如下:

  1. 將大文件在前端分割為若干固定大小的塊
  2. 后端接受每個(gè)分塊,并臨時(shí)存儲(chǔ)
  3. 上傳完成后,后端將所有分塊按順序合并為最終文件
  4. 合并成功后清理臨時(shí)分塊

項(xiàng)目結(jié)構(gòu)設(shè)計(jì)

/upload-platform
├── src/
│   ├── main/
│   │   ├── java/com/icoderoad/upload/
│   │   │   ├── controller/ChunkUploadController.java       # 上傳控制器
│   │   │   ├── service/FileMergeService.java               # 文件合并服務(wù)
│   │   │   ├── config/MinioConfig.java                     # MinIO配置
│   │   │   └── UploadPlatformApplication.java              # 主程序
│   │   ├── resources/
│   │   │   ├── templates/
│   │   │   │   └── upload.html                             # 上傳頁面(Thymeleaf)
│   │   │   ├── static/
│   │   │   │   ├── css/bootstrap.min.css
│   │   │   │   └── js/upload.js                            # 分片上傳邏輯
│   │   │   └── application.yml                             # 配置文件
├── pom.xml

后端實(shí)現(xiàn)

pom.xml

<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>
    <dependency>
        <groupId>io.minio</groupId>
        <artifactId>minio</artifactId>
        <version>8.5.3</version>
    </dependency>
</dependencies>

application.yml

server:
  port: 8080


spring:
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB

MinioConfig.java

package com.icoderoad.upload.config;


import io.minio.MinioClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


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

ChunkUploadController.java

package com.icoderoad.upload.controller;


import org.apache.commons.io.FileUtils;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;


import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.stream.Collectors;


@RestController
@RequestMapping("/upload")
public class ChunkUploadController {


    private static final String CHUNK_DIR = "/tmp/upload/chunks/";
    private static final String FINAL_DIR = "/tmp/upload/final/";


    @PostMapping("/init")
    public ResponseEntity<String> initUpload(@RequestParam String fileName, @RequestParam String fileMd5) {
        String uploadId = UUID.randomUUID().toString();
        Path path = Paths.get(CHUNK_DIR, fileMd5 + "_" + uploadId);
        try {
            Files.createDirectories(path);
        } catch (IOException e) {
            return ResponseEntity.status(500).body("初始化失敗");
        }
        return ResponseEntity.ok(uploadId);
    }


    @PostMapping("/chunk")
    public ResponseEntity<String> uploadChunk(@RequestParam MultipartFile chunk,
                                              @RequestParam String uploadId,
                                              @RequestParam String fileMd5,
                                              @RequestParam Integer index) {
        String chunkName = "chunk_" + index + ".tmp";
        Path target = Paths.get(CHUNK_DIR, fileMd5 + "_" + uploadId, chunkName);
        try {
            chunk.transferTo(target);
            return ResponseEntity.ok("分塊成功");
        } catch (IOException e) {
            return ResponseEntity.status(500).body("保存失敗");
        }
    }


    @PostMapping("/merge")
    public ResponseEntity<String> mergeChunks(@RequestParam String fileName,
                                              @RequestParam String uploadId,
                                              @RequestParam String fileMd5) {
        File chunkFolder = new File(CHUNK_DIR + fileMd5 + "_" + uploadId);
        File[] chunkFiles = chunkFolder.listFiles();


        if (chunkFiles == null || chunkFiles.length == 0) {
            return ResponseEntity.badRequest().body("未找到分塊");
        }


        Arrays.sort(chunkFiles, Comparator.comparingInt(f ->
                Integer.parseInt(f.getName().replace("chunk_", "").replace(".tmp", ""))));


        Path finalPath = Paths.get(FINAL_DIR, fileName);
        try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(finalPath))) {
            for (File chunk : chunkFiles) {
                Files.copy(chunk.toPath(), output);
            }
            FileUtils.deleteDirectory(chunkFolder);
            return ResponseEntity.ok("上傳完成:" + finalPath);
        } catch (IOException e) {
            return ResponseEntity.status(500).body("合并失?。? + e.getMessage());
        }
    }


    @GetMapping("/check/{fileMd5}/{uploadId}")
    public ResponseEntity<List<Integer>> checkChunks(@PathVariable String fileMd5, @PathVariable String uploadId) {
        Path chunkPath = Paths.get(CHUNK_DIR, fileMd5 + "_" + uploadId);
        if (!Files.exists(chunkPath)) return ResponseEntity.ok(Collections.emptyList());


        try {
            List<Integer> indices = Files.list(chunkPath)
                    .map(p -> p.getFileName().toString())
                    .map(name -> name.replace("chunk_", "").replace(".tmp", ""))
                    .map(Integer::parseInt)
                    .collect(Collectors.toList());
            return ResponseEntity.ok(indices);
        } catch (IOException e) {
            return ResponseEntity.status(500).body(Collections.emptyList());
        }
    }
}

前端頁面(upload.html)

路徑:resources/templates/upload.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>分片上傳演示</title>
    <link rel="stylesheet" href="/css/bootstrap.min.css"/>
</head>
<body class="p-4">
    <h3>分片上傳</h3>
    <input type="file" id="fileInput" class="form-control mb-2"/>
    <div class="progress mb-2">
        <div class="progress-bar" id="progressBar" role="progressbar" style="width: 0%;">0%</div>
    </div>
    <button class="btn btn-primary" onclick="startUpload()">開始上傳</button>


    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script src="/js/upload.js"></script>
</body>
</html>

JS 邏輯(upload.js)

路徑:resources/static/js/upload.js

const CHUNK_SIZE = 5 * 1024 * 1024;


async function calculateFileMd5(file) {
    // 簡化處理:用文件名和大小模擬唯一標(biāo)識(shí)(實(shí)際應(yīng)用使用 SparkMD5)
    return `${file.name}-${file.size}`;
}


function splitFile(file) {
    const chunks = [];
    const count = Math.ceil(file.size / CHUNK_SIZE);
    for (let i = 0; i < count; i++) {
        chunks.push(file.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE));
    }
    return chunks;
}


function updateProgress(percent) {
    const bar = document.getElementById("progressBar");
    bar.style.width = `${percent}%`;
    bar.textContent = `${percent}%`;
}


async function startUpload() {
    const file = document.getElementById("fileInput").files[0];
    if (!file) return alert("請(qǐng)選擇文件");


    const fileMd5 = await calculateFileMd5(file);
    const { data: uploadId } = await axios.post("/upload/init", {
        fileName: file.name,
        fileMd5
    });


    const chunks = splitFile(file);
    const uploadedChunks = (await axios.get(`/upload/check/${fileMd5}/${uploadId}`)).data;
    let uploaded = uploadedChunks.length;


    await Promise.all(chunks.map(async (chunk, index) => {
        if (uploadedChunks.includes(index)) return;


        const formData = new FormData();
        formData.append("chunk", chunk);
        formData.append("index", index);
        formData.append("uploadId", uploadId);
        formData.append("fileMd5", fileMd5);


        await axios.post("/upload/chunk", formData, {
            onUploadProgress: () => {
                uploaded++;
                const percent = ((uploaded / chunks.length) * 100).toFixed(1);
                updateProgress(percent);
            }
        });
    }));


    await axios.post("/upload/merge", {
        fileName: file.name,
        uploadId,
        fileMd5
    });


    alert("上傳完成!");
}

總結(jié)建議

  • 適配分片大小

局域網(wǎng):10MB ~ 20MB

移動(dòng)網(wǎng)絡(luò):1MB ~ 5MB

廣域網(wǎng):500KB ~ 1MB

  • 定時(shí)清理策略
@Scheduled(cron = "0 0 3 * * ?") // 每天凌晨3點(diǎn)清理臨時(shí)目錄
public void cleanChunks() {
    FileUtils.deleteQuietly(new File("/tmp/upload/chunks/"));
}
  • 安全驗(yàn)證建議

使用 HMAC + 文件摘要校驗(yàn)簽名完整性

上傳前預(yù)計(jì)算 MD5 或 SHA256

  • 云存儲(chǔ)集成推薦
  • MinIO / OSS / COS / S3 等對(duì)象存儲(chǔ)平臺(tái)
  • 可直接分片上傳至對(duì)象存儲(chǔ),節(jié)省本地合并

結(jié)語

通過 Spring Boot 構(gòu)建的分片上傳系統(tǒng),不僅解決了大文件傳輸過程中的性能瓶頸,還提供了斷點(diǎn)續(xù)傳、上傳加速、失敗重試、安全校驗(yàn)等完整機(jī)制。結(jié)合前端優(yōu)化與后端云服務(wù)整合,可無縫部署到生產(chǎn)環(huán)境,廣泛適用于音視頻平臺(tái)、文檔系統(tǒng)、網(wǎng)盤等多種場景。

如果你正在構(gòu)建涉及大文件上傳的系統(tǒng),這套方案將為你帶來更穩(wěn)定、更高效的體驗(yàn)!

責(zé)任編輯:武曉燕 來源: 路條編程
相關(guān)推薦

2021-01-15 11:40:44

文件Java秒傳

2022-06-15 09:01:45

大文件秒傳分片上傳

2024-11-12 09:54:23

2022-08-05 08:40:37

架構(gòu)

2024-07-02 10:18:18

2025-04-10 08:03:31

Spring系統(tǒng)

2025-06-27 02:32:00

2025-07-02 00:00:00

2021-06-01 05:15:36

JavaScript 前端大文件并發(fā)上傳

2015-05-05 10:08:15

Android谷歌Sky

2022-06-13 14:06:33

大文件上傳前端

2009-11-16 11:41:19

PHP上傳大文件

2020-05-03 13:53:08

WiFi信號(hào)無線網(wǎng)絡(luò)

2020-04-02 20:28:07

微軟Windows 10卡頓

2009-07-21 15:38:31

2022-08-12 22:53:32

HadoopHDFS分布式

2021-01-18 05:19:11

數(shù)字指紋

2019-04-28 11:08:13

Windows 10Windows操作系統(tǒng)

2022-01-28 09:15:54

電腦間歇卡頓

2025-01-02 10:19:18

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)