SpringBoot大文件上傳卡死?分塊切割術(shù)搞定GB級傳輸,速度飆升!?。?/h1>
兄弟們,當(dāng)你正在開發(fā)一個視頻網(wǎng)站,用戶要上傳一個 5GB 的 4K 視頻。傳統(tǒng)的 SpringBoot 單文件上傳就像開著一輛裝滿貨物的三輪車爬坡 ——內(nèi)存爆炸、超時崩潰、網(wǎng)絡(luò)波動分分鐘讓你前功盡棄。這時候,分塊切割術(shù)就像給三輪車裝上渦輪增壓,把大文件切成小塊分批運(yùn)輸,讓上傳過程變得像高鐵一樣平穩(wěn)高效。
一、傳統(tǒng)上傳的「死亡陷阱」
1. 內(nèi)存黑洞
SpringBoot 默認(rèn)用MultipartFile接收文件,大文件會直接加載到內(nèi)存。5GB 的文件相當(dāng)于把一頭大象塞進(jìn)小轎車,內(nèi)存直接溢出,服務(wù)器瞬間卡死。
2. 超時魔咒
HTTP 請求有默認(rèn)超時時間(Tomcat 默認(rèn) 60 秒),上傳一個 5GB 文件需要至少 10 分鐘,超時是必然的。用戶只能眼睜睜看著進(jìn)度條卡在 99%,然后重新再來。
3. 網(wǎng)絡(luò)過山車
上傳到一半突然斷網(wǎng),傳統(tǒng)方案只能從頭再來。用戶可能已經(jīng)等了半小時,結(jié)果竹籃打水一場空,這種體驗(yàn)簡直讓人想砸電腦。
二、分塊切割術(shù)的「九陽神功」
1. 分而治之的智慧
把大文件切成 20MB 的小塊,就像把大象拆成零件運(yùn)輸。每個小塊獨(dú)立上傳,失敗了只需要重傳那一塊,大大降低風(fēng)險。
2. 斷點(diǎn)續(xù)傳的魔法
記錄已經(jīng)上傳的分片,網(wǎng)絡(luò)恢復(fù)后從斷點(diǎn)繼續(xù)。用戶可以暫停、重啟上傳,甚至關(guān)閉電腦第二天接著傳,就像下載電影一樣方便。
3. 并行加速的奧義
同時上傳多個分片,充分利用帶寬。就像多條車道同時通車,上傳速度直接翻倍。
三、后端實(shí)現(xiàn):打造「文件運(yùn)輸線」
1. 數(shù)據(jù)庫設(shè)計(jì):記錄運(yùn)輸狀態(tài)
CREATE TABLE file_upload (
id VARCHAR(36) PRIMARY KEY, -- 文件唯一標(biāo)識
total_size BIGINT NOT NULL, -- 文件總大小
total_chunks INT NOT NULL, -- 總分片數(shù)
uploaded_chunks INT DEFAULT 0, -- 已上傳分片數(shù)
status INT DEFAULT 0 -- 0-進(jìn)行中,1-完成,2-失敗
);
2. 分片上傳接口:接收零件
@PostMapping("/upload/chunk")
public ResponseEntity<?> uploadChunk(
@RequestParam("file") MultipartFile chunk,
@RequestParam("fileId") String fileId,
@RequestParam("chunkIndex") int chunkIndex) {
// 檢查分片是否已存在
if (chunkRepository.existsByFileIdAndChunkIndex(fileId, chunkIndex)) {
return ResponseEntity.ok("分片已存在");
}
// 保存分片到臨時目錄
String chunkPath = Paths.get(uploadDir, fileId, chunkIndex + ".part").toString();
chunk.transferTo(Paths.get(chunkPath));
// 更新數(shù)據(jù)庫狀態(tài)
chunkRepository.save(new Chunk(fileId, chunkIndex, chunk.getSize()));
return ResponseEntity.ok("分片上傳成功");
}
3. 合并接口:組裝零件
@PostMapping("/upload/merge")
public ResponseEntity<?> mergeChunks(@RequestParam("fileId") String fileId) {
// 查詢所有分片
List<Chunk> chunks = chunkRepository.findByFileId(fileId);
// 按順序合并
try (RandomAccessFile target = new RandomAccessFile(Paths.get(uploadDir, fileId).toFile(), "rw")) {
for (Chunk chunk : chunks) {
try (FileInputStream in = new FileInputStream(Paths.get(uploadDir, fileId, chunk.getChunkIndex() + ".part").toFile())) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
target.write(buffer, 0, bytesRead);
}
}
}
}
// 刪除臨時分片
chunks.forEach(chunk -> {
try {
Files.delete(Paths.get(uploadDir, fileId, chunk.getChunkIndex() + ".part"));
} catch (IOException e) {
log.error("刪除分片失敗", e);
}
});
// 更新文件狀態(tài)
fileUploadRepository.updateStatus(fileId, 1);
return ResponseEntity.ok("文件合并成功");
}
四、前端實(shí)現(xiàn):「零件加工廠」
1. 分片切割:拆大象
function splitFile(file, chunkSize = 20 * 1024 * 1024) {
const chunks = [];
let current = 0;
while (current < file.size) {
const chunk = file.slice(current, current + chunkSize);
chunks.push(chunk);
current += chunkSize;
}
return chunks;
}
2. 并行上傳:多條車道
async function uploadChunks(chunks, fileId) {
const promises = chunks.map((chunk, index) => {
const formData = new FormData();
formData.append("file", chunk);
formData.append("fileId", fileId);
formData.append("chunkIndex", index);
return fetch("/upload/chunk", {
method: "POST",
body: formData
});
});
await Promise.all(promises);
}
3. 進(jìn)度條:實(shí)時反饋
<div class="progress">
<div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<script>
function updateProgress(uploaded, total) {
const progressBar = document.querySelector('.progress-bar');
progressBar.style.width = `${(uploaded / total) * 100}%`;
progressBar.setAttribute('aria-valuenow', `${(uploaded / total) * 100}`);
}
</script>
五、斷點(diǎn)續(xù)傳:「失敗重來」的勇氣
1. 記錄上傳狀態(tài)
localStorage.setItem('uploadStatus', JSON.stringify({
fileId: '123',
uploadedChunks: [0, 1, 3]
}));
2. 恢復(fù)上傳
async function resumeUpload(file) {
const status = JSON.parse(localStorage.getItem('uploadStatus'));
const chunks = splitFile(file);
// 找出未上傳的分片
const remainingChunks = chunks.filter((_, index) => !status.uploadedChunks.includes(index));
await uploadChunks(remainingChunks, status.fileId);
}
六、性能優(yōu)化:「速度與激情」
1. 異步處理:解放線程
@Async("fileUploadExecutor")
public CompletableFuture<?> asyncMerge(String fileId) {
return CompletableFuture.runAsync(() -> {
// 合并文件邏輯
});
}
2. 動態(tài)分塊:適應(yīng)路況
function calculateChunkSize(bandwidth) {
// 根據(jù)網(wǎng)絡(luò)帶寬動態(tài)調(diào)整分片大小
return Math.max(10 * 1024 * 1024, Math.min(50 * 1024 * 1024, bandwidth * 0.8));
}
3. 多線程合并:同時組裝
ExecutorService executor = Executors.newFixedThreadPool(4);
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < chunks.size(); i += 4) {
final int start = i;
futures.add(executor.submit(() -> {
for (int j = start; j < Math.min(start + 4, chunks.size()); j++) {
// 合并分片
}
}));
}
futures.forEach(future -> {
try {
future.get();
} catch (Exception e) {
log.error("合并失敗", e);
}
});
七、安全防護(hù):「文件運(yùn)輸?shù)谋kU」
1. MD5 校驗(yàn):防篡改
@PostMapping("/upload/check")
public ResponseEntity<?> checkFile(@RequestParam("file") MultipartFile file) {
String md5 = calculateMD5(file.getInputStream());
FileUpload fileUpload = fileUploadRepository.findByMd5(md5);
if (fileUpload != null && fileUpload.getStatus() == 1) {
return ResponseEntity.ok("文件已存在");
}
return ResponseEntity.ok("文件不存在");
}
2. 文件類型驗(yàn)證:防病毒
@PostMapping("/upload/chunk")
public ResponseEntity<?> uploadChunk(@RequestParam("file") MultipartFile chunk) {
String contentType = chunk.getContentType();
if (!contentType.startsWith("image/") && !contentType.startsWith("video/")) {
return ResponseEntity.badRequest().body("不支持的文件類型");
}
// 其他邏輯
}
3. 權(quán)限控制:防越權(quán)
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/upload/merge")
public ResponseEntity<?> mergeChunks(@RequestParam("fileId") String fileId) {
// 合并邏輯
}
八、實(shí)戰(zhàn)案例:「5GB 視頻上傳的逆襲」
1. 傳統(tǒng)方案
- 上傳時間:15 分鐘
- 內(nèi)存占用:800MB
- 失敗率:30%(網(wǎng)絡(luò)波動)
2. 分塊方案
- 上傳時間:4 分鐘(并行上傳)
- 內(nèi)存占用:50MB(流式處理)
- 失敗率:2%(斷點(diǎn)續(xù)傳)
九、總結(jié):「分塊切割術(shù)」的終極奧義
分塊上傳就像把大文件運(yùn)輸變成一場接力賽,每個分片都是一個選手,各司其職,共同完成任務(wù)。通過分塊切割、斷點(diǎn)續(xù)傳、并行加速和安全防護(hù),我們不僅解決了大文件上傳的卡死問題,還提升了用戶體驗(yàn)和系統(tǒng)性能?,F(xiàn)在,你可以自信地對用戶說:不管多大的文件,我們都能輕松搞定!