Java實(shí)現(xiàn)大文件上傳與斷點(diǎn)續(xù)傳的全過(guò)程
在現(xiàn)代 Web 應(yīng)用中,用戶經(jīng)常需要上傳大型文件——如高清視頻、設(shè)計(jì)圖紙或數(shù)據(jù)庫(kù)備份。然而,受限于網(wǎng)絡(luò)穩(wěn)定性、服務(wù)器內(nèi)存和瀏覽器限制,直接上傳大文件極易失敗。為此,“分片上傳 + 斷點(diǎn)續(xù)傳”成為業(yè)界標(biāo)準(zhǔn)解決方案。本文將深入講解如何使用 Java(基于 Spring Boot) 實(shí)現(xiàn)高效、可靠的大文件上傳系統(tǒng)。
一、為什么需要斷點(diǎn)續(xù)傳?
- 網(wǎng)絡(luò)不穩(wěn)定:上傳過(guò)程中斷后,無(wú)需從頭開始。
- 節(jié)省帶寬與時(shí)間:僅重傳失敗或未傳的分片。
- 提升用戶體驗(yàn):支持上傳進(jìn)度顯示、暫停/恢復(fù)。
- 避免服務(wù)器壓力:避免一次性加載整個(gè)大文件到內(nèi)存。
二、核心設(shè)計(jì)思想
1. 文件分片(Chunking)
將一個(gè)大文件按固定大?。ㄈ?2MB)切分為多個(gè)小塊(chunks),每個(gè)塊獨(dú)立上傳。
2. 唯一標(biāo)識(shí)
使用文件內(nèi)容的 MD5 值 作為唯一 ID,既可識(shí)別文件,又能避免重復(fù)上傳相同內(nèi)容。
3. 分片管理
- 后端為每個(gè)文件創(chuàng)建臨時(shí)目錄,按序號(hào)存儲(chǔ)分片。
- 提供接口查詢“已上傳分片列表”,實(shí)現(xiàn)斷點(diǎn)檢測(cè)。
4. 合并與清理
所有分片收齊后,按順序合并成完整文件,并清理臨時(shí)數(shù)據(jù)。
三、后端實(shí)現(xiàn)(Spring Boot)
1. 項(xiàng)目依賴(Maven)
<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. 控制器代碼
@RestController
@RequestMapping("/api/upload")
public class ResumableUploadController {
private static final String TEMP_DIR = "uploads/temp/";
private static final String FINAL_DIR = "uploads/final/";
/**
* 檢查哪些分片已上傳(用于斷點(diǎn)續(xù)傳)
*/
@GetMapping("/check")
public Set<Integer> checkUploadedChunks(@RequestParam String fileMd5) {
Set<Integer> uploaded = new HashSet<>();
Path tempPath = Paths.get(TEMP_DIR, fileMd5);
if (Files.exists(tempPath)) {
try (Stream<Path> stream = Files.list(tempPath)) {
stream.forEach(p -> {
String name = p.getFileName().toString();
if (name.matches("\\d+")) {
uploaded.add(Integer.parseInt(name));
}
});
} catch (IOException e) {
// 日志記錄
}
}
return uploaded;
}
/**
* 上傳單個(gè)分片
*/
@PostMapping("/chunk")
public ResponseEntity<?> uploadChunk(
@RequestParam String fileMd5,
@RequestParam int chunkIndex,
@RequestParam MultipartFile chunk) {
try {
Path tempDir = Paths.get(TEMP_DIR, fileMd5);
Files.createDirectories(tempDir);
Path chunkFile = tempDir.resolve(String.valueOf(chunkIndex));
chunk.transferTo(chunkFile.toFile());
return ResponseEntity.ok().build();
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
/**
* 合并所有分片為完整文件
*/
@PostMapping("/merge")
public ResponseEntity<?> mergeChunks(
@RequestParam String fileMd5,
@RequestParam String fileName,
@RequestParam int totalChunks) {
try {
Path finalPath = Paths.get(FINAL_DIR, fileName);
Files.createDirectories(finalPath.getParent());
try (OutputStream out = Files.newOutputStream(finalPath)) {
for (int i = 0; i < totalChunks; i++) {
Path chunk = Paths.get(TEMP_DIR, fileMd5, String.valueOf(i));
if (!Files.exists(chunk)) {
return ResponseEntity.badRequest()
.body("Missing chunk: " + i);
}
byte[] data = Files.readAllBytes(chunk);
out.write(data);
}
}
// 清理臨時(shí)分片
FileUtils.deleteDirectory(new File(TEMP_DIR + fileMd5));
return ResponseEntity.ok(Map.of("success", true, "path", finalPath.toString()));
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}
注意:實(shí)際項(xiàng)目中應(yīng)加入文件名校驗(yàn)、權(quán)限控制、異常日志等。
四、前端配合邏輯(簡(jiǎn)要)
前端需完成以下步驟:
- 計(jì)算文件 MD5(推薦使用
spark-md5庫(kù))。 - 調(diào)用
/check接口 獲取已上傳分片索引。 - 循環(huán)上傳未完成的分片,每個(gè)請(qǐng)求攜帶:
fileMd5chunkIndex- 分片 Blob 數(shù)據(jù)
- 全部上傳完成后,調(diào)用
/merge觸發(fā)合并。
// 示例:上傳一個(gè)分片
const uploadChunk = async (fileMd5, index, blob) => {
const formData = new FormData();
formData.append('fileMd5', fileMd5);
formData.append('chunkIndex', index);
formData.append('chunk', blob);
await fetch('/api/upload/chunk', { method: 'POST', body: formData });
};
五、關(guān)鍵優(yōu)化建議
| 優(yōu)化點(diǎn) | 說(shuō)明 |
|---|---|
| MD5 計(jì)算 | 前端計(jì)算可避免重復(fù)上傳;若安全要求高,后端也可二次校驗(yàn)。 |
| 并發(fā)上傳 | 允許同時(shí)上傳多個(gè)分片(如 3~5 個(gè)),提升速度。 |
| 超時(shí)清理 | 定時(shí)任務(wù)刪除 24 小時(shí)未合并的臨時(shí)分片,防止磁盤占滿。 |
| 限流與鑒權(quán) | 防止惡意上傳,限制單用戶上傳頻率和總大小。 |
| 進(jìn)度反饋 | 前端根據(jù)已傳分片數(shù)實(shí)時(shí)更新進(jìn)度條。 |
六、進(jìn)階方案
1. 使用 TUS 協(xié)議
TUS 是一個(gè)開源的 Resumable Upload 標(biāo)準(zhǔn)。Java 社區(qū)有 tus-java-server 實(shí)現(xiàn),開箱即用,支持跨平臺(tái)、跨語(yǔ)言。
2. 對(duì)接云存儲(chǔ)
阿里云 OSS、騰訊云 COS、AWS S3 等均提供 分片上傳 API??蓪⒎制苯由蟼髦猎拼鎯?chǔ),后端僅負(fù)責(zé)協(xié)調(diào),大幅降低服務(wù)器負(fù)載。
七、總結(jié)
通過(guò)“分片上傳 + 斷點(diǎn)續(xù)傳”,我們不僅能可靠地處理 GB 級(jí)文件,還能顯著提升用戶體驗(yàn)和系統(tǒng)健壯性。雖然實(shí)現(xiàn)細(xì)節(jié)較多,但核心邏輯清晰:切分 → 上傳 → 校驗(yàn) → 合并。
在實(shí)際項(xiàng)目中,建議結(jié)合業(yè)務(wù)需求選擇自研或集成成熟方案。對(duì)于高并發(fā)、高可靠場(chǎng)景,優(yōu)先考慮云廠商的分片上傳能力;對(duì)于內(nèi)部系統(tǒng)或定制化需求,本文提供的 Java 實(shí)現(xiàn)可作為堅(jiān)實(shí)基礎(chǔ)。
以上就是Java實(shí)現(xiàn)大文件上傳與斷點(diǎn)續(xù)傳的全過(guò)程的詳細(xì)內(nèi)容,更多關(guān)于Java大文件上傳與斷點(diǎn)續(xù)傳的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java求素?cái)?shù)和最大公約數(shù)的簡(jiǎn)單代碼示例
這篇文章主要介紹了Java求素?cái)?shù)和最大公約數(shù)的簡(jiǎn)單代碼示例,其中作者創(chuàng)建的Fraction類可以用來(lái)進(jìn)行各種分?jǐn)?shù)運(yùn)算,需要的朋友可以參考下2015-09-09
springboot使用spring-data-jpa操作MySQL數(shù)據(jù)庫(kù)
這篇文章主要介紹了springboot使用spring-data-jpa操作MySQL數(shù)據(jù)庫(kù),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07
Java Gradle項(xiàng)目中的資源正確獲取方式
這篇文章主要介紹了Java Gradle項(xiàng)目中的資源正確獲取方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11
詳解poi+springmvc+springjdbc導(dǎo)入導(dǎo)出excel實(shí)例
本篇文章主要介紹了poi+springmvc+springjdbc導(dǎo)入導(dǎo)出excel實(shí)例,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2017-01-01
Java實(shí)現(xiàn)多用戶注冊(cè)登錄的幸運(yùn)抽獎(jiǎng)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)多用戶注冊(cè)登錄的幸運(yùn)抽獎(jiǎng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11
Spring Boot利用Docker快速部署項(xiàng)目的完整步驟
這篇文章主要給大家介紹了關(guān)于Spring Boot利用Docker快速部署項(xiàng)目的完整步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Spring Boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07

