Java實(shí)現(xiàn)瀏覽器大文件上傳的示例詳解
前言
文件上傳是許多項(xiàng)目都有的功能,用戶上傳小文件速度一般都很快,但如果是大文件幾個(gè)g,幾十個(gè)g的時(shí)候,上傳了半天,馬上就要完成的時(shí)候,網(wǎng)絡(luò)波動(dòng)一下,文件又要重新上傳,抓狂。那有什么辦法解決解決這個(gè)問題,答案就是把文件分片,一段一段把文件拆開上傳。
核心講解
原理
分片上傳:把一個(gè)完整的文件,前端把文件分成多個(gè)小塊的chunk,一塊一塊的傳遞給后端,后端接收到后再把全部的塊拼接起來,這樣就算在某個(gè)時(shí)間點(diǎn)發(fā)生網(wǎng)絡(luò)波動(dòng),那么丟失的也只有一塊。

秒傳:前端在把文件分片前,先計(jì)算出文件的md5值,后端拿到這個(gè)md5先去檢查下是否已經(jīng)有這個(gè)文件了,如果有直接給前端上傳成功。這就是我們在網(wǎng)盤上有時(shí)候出現(xiàn)的文件秒傳,說明已經(jīng)有人跟你上傳過同一份文件了。
斷點(diǎn)續(xù)傳:當(dāng)網(wǎng)絡(luò)出現(xiàn)異常上傳中斷后我們繼續(xù)上傳時(shí),先去后端請求接口,拿到已經(jīng)上傳過的分片下標(biāo),再繼續(xù)上傳沒有上傳的分片。
整體流程
- 用戶選擇文件進(jìn)行上傳
- 前端獲取文件唯一標(biāo)識md5
- 判斷文件md5是否已經(jīng)保存,是則秒傳
- 判斷文件分片是否已經(jīng)上傳部分,是則斷點(diǎn)續(xù)傳
- 上傳分片文件
- 后端合并分片
- 分片上傳完成

功能分析
前端
前端實(shí)現(xiàn)的功能難點(diǎn)在于文件分片,和獲取文件的md5。
文件分片
因?yàn)閖s的File對象繼承自Blob,所以他也有slice方法,slice方法需要的參數(shù)有兩個(gè),一個(gè)是startByte文件起始讀取的字節(jié)位置,另一個(gè)是endByte結(jié)束讀取的字節(jié)位置。
let fileChunkList = []; //存放文件切片
let cur = 0;
// 分片
while(cur < file.size){
fileChunkList.push(file.slice(cur,cur + chunkSize));
cur += chunkSize;
}
獲取文件md5
獲取文件的md5,推薦使用SparkMD5的文件增量方式獲取,如果直接計(jì)算文件的hash,文件過大時(shí)對瀏覽器負(fù)擔(dān)會(huì)較大。
上傳文件
通過check接口上傳前先判斷是否秒傳和獲取已經(jīng)上傳的分片下標(biāo)。
function handleBeforeUpload(file) {
const chunkSize = 1024 * 1024 * 10; // 10MB
// 計(jì)算md5
md5(file, chunkSize).then(md5 => {
//檢查是否秒傳
request({
url: "/upload/check/" + md5,
method: "get",
}).then(result => {
const isOk = result.isOk;
const haveList2 = result.haveList; //已經(jīng)上傳的分片下標(biāo)
if(isOk) {
console.log("秒傳成功");
return;
}
haveList.value = haveList2;
let chunkIndex = 0;
//上傳第一個(gè)分片
upload(fileChunkList.value, chunkIndex, md5, file);
})
});
return false;
}
已經(jīng)上傳的這些分片下標(biāo)要跳過上傳

后端
分片來后端后,使用RandomAccessFile就可以在一個(gè)文件上進(jìn)行操作,而不用使用創(chuàng)建多個(gè)臨時(shí)文件最后合并的方式,通過分片下標(biāo)和分片大小計(jì)算出偏移量,使用RandomAccessFile將跳到偏移開始位置存放數(shù)據(jù)。RandomAccessFile的第二個(gè)參數(shù)的model有如下;
? "r":以只讀方式打開指定文件。 ? "rw":以讀、寫方式打開指定文件。 ? "rws":以讀、寫方式打開指定文件。相對于"rw"模式,還要求對文件的內(nèi)容或元數(shù)據(jù)的每個(gè)更新都同步寫入到底層存儲設(shè)備。 ? "rwd":以讀、寫方式打開指定文件。相對于"rw"模式,還要求對文件內(nèi)容的每個(gè)更新都同步寫入到底層存儲設(shè)備。
/**
* 分片文件上傳
* @param file 文件
* @param chunkIndex 分片下標(biāo)
* @param md5 md5
* @param totalFileSize 文件總大小
* @param fileName 文件名
*/
@PostMapping("/shard")
public AjaxResult shardUpload(@RequestParam MultipartFile file, @RequestParam Integer chunkIndex,
@RequestParam String md5, @RequestParam Long totalFileSize,
@RequestParam String fileName) throws Exception{
// 存放文件目錄
String dirPath = System.getProperty("user.dir") + "/file/"+md5+"/";
File dirFile = new File(dirPath);
if(!dirFile.exists()){
dirFile.mkdir();
}
File tempFile = new File(dirPath + fileName);
RandomAccessFile rw = new RandomAccessFile(tempFile, "rw");
// 定位到分片的偏移量
rw.seek(CHUNK_SIZE * chunkIndex);
// 寫入分片數(shù)據(jù)
rw.write(file.getBytes());
// 關(guān)閉流
rw.close();
// 讀取已經(jīng)分片集合
List<Object> hasChunkList;
String hasChunkKey = CHUNK_PREFIX + md5;
if(redisCache.hasKey(hasChunkKey)){
hasChunkList = redisCache.getCacheList(hasChunkKey);
} else {
hasChunkList = new ArrayList<>();
}
hasChunkList.add(chunkIndex);
// 將最新的分片下標(biāo)更新到Redis中
redisCache.addCacheListOne(hasChunkKey,chunkIndex);
// 判斷是否上傳完成
int totalNeedChunks = (int) Math.ceil((double) totalFileSize / CHUNK_SIZE);
// 總共需要的分片數(shù) 和 已經(jīng)分片上傳的數(shù)量相等 則上傳完成
boolean isOk = totalNeedChunks == hasChunkList.size();
if(isOk){
redisCache.setCacheObject(UPLOAD_ISOK_PREFIX + md5, true);
}
AjaxResult ajax = AjaxResult.success();
ajax.put("hasChunkList",hasChunkList);
ajax.put("isOk",isOk);
return ajax;
}
最終演示
上傳完成演示

秒傳演示

斷點(diǎn)演示

待優(yōu)化
- 提供查詢進(jìn)度接口,前端進(jìn)度條展示,增加用戶體驗(yàn)。
- 多線程上傳,不同分片用多線程,提高下載速度。
到此這篇關(guān)于Java實(shí)現(xiàn)瀏覽器大文件上傳的示例詳解的文章就介紹到這了,更多相關(guān)Java瀏覽器大文件上傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot整合logback實(shí)現(xiàn)日志管理操作
本章節(jié)是記錄logback在springboot項(xiàng)目中的簡單使用,本文將會(huì)演示如何通過logback將日志記錄到日志文件或輸出到控制臺等管理操作,感興趣的朋友跟隨小編一起看看吧2024-02-02
關(guān)于Spring AOP使用時(shí)的一些問題匯總
這篇文章主要給大家匯總介紹了關(guān)于Spring AOP使用時(shí)的一些問題,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
SpringBoot多租戶配置與實(shí)現(xiàn)示例
本文詳細(xì)介紹了在SpringBoot中實(shí)現(xiàn)多租戶架構(gòu)的方法和步驟,包括配置數(shù)據(jù)源、Hibernate攔截器、租戶解析器等,以共享數(shù)據(jù)庫、共享數(shù)據(jù)表的方式,確保數(shù)據(jù)隔離和安全性,感興趣的可以了解一下2024-09-09
Feign調(diào)用服務(wù)時(shí)丟失Cookie和Header信息的解決方案
這篇文章主要介紹了Feign調(diào)用服務(wù)時(shí)丟失Cookie和Header信息的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
java網(wǎng)上商城開發(fā)之郵件發(fā)送功能(全)
這篇文章主要介紹了java網(wǎng)上商城開發(fā)之郵件發(fā)送功能,第一部分介紹了環(huán)境配置,第二部分則介紹了具體實(shí)現(xiàn)代碼,感興趣的小伙伴們可以參考一下2016-03-03
JAVA Stack詳細(xì)介紹和示例學(xué)習(xí)
JAVA Stack是棧。它的特性是:先進(jìn)后出(FILO, First In Last Out)。2013-11-11

