Java實現(xiàn)瀏覽器大文件上傳的示例詳解
前言
文件上傳是許多項目都有的功能,用戶上傳小文件速度一般都很快,但如果是大文件幾個g,幾十個g的時候,上傳了半天,馬上就要完成的時候,網絡波動一下,文件又要重新上傳,抓狂。那有什么辦法解決解決這個問題,答案就是把文件分片,一段一段把文件拆開上傳。
核心講解
原理
分片上傳:把一個完整的文件,前端把文件分成多個小塊的chunk,一塊一塊的傳遞給后端,后端接收到后再把全部的塊拼接起來,這樣就算在某個時間點發(fā)生網絡波動,那么丟失的也只有一塊。
秒傳:前端在把文件分片前,先計算出文件的md5值,后端拿到這個md5先去檢查下是否已經有這個文件了,如果有直接給前端上傳成功。這就是我們在網盤上有時候出現(xiàn)的文件秒傳,說明已經有人跟你上傳過同一份文件了。
斷點續(xù)傳:當網絡出現(xiàn)異常上傳中斷后我們繼續(xù)上傳時,先去后端請求接口,拿到已經上傳過的分片下標,再繼續(xù)上傳沒有上傳的分片。
整體流程
- 用戶選擇文件進行上傳
- 前端獲取文件唯一標識md5
- 判斷文件md5是否已經保存,是則秒傳
- 判斷文件分片是否已經上傳部分,是則斷點續(xù)傳
- 上傳分片文件
- 后端合并分片
- 分片上傳完成
功能分析
前端
前端實現(xiàn)的功能難點在于文件分片,和獲取文件的md5。
文件分片
因為js的File對象繼承自Blob,所以他也有slice方法,slice方法需要的參數有兩個,一個是startByte文件起始讀取的字節(jié)位置,另一個是endByte結束讀取的字節(jié)位置。
let fileChunkList = []; //存放文件切片 let cur = 0; // 分片 while(cur < file.size){ fileChunkList.push(file.slice(cur,cur + chunkSize)); cur += chunkSize; }
獲取文件md5
獲取文件的md5,推薦使用SparkMD5的文件增量方式獲取,如果直接計算文件的hash,文件過大時對瀏覽器負擔會較大。
上傳文件
通過check接口上傳前先判斷是否秒傳和獲取已經上傳的分片下標。
function handleBeforeUpload(file) { const chunkSize = 1024 * 1024 * 10; // 10MB // 計算md5 md5(file, chunkSize).then(md5 => { //檢查是否秒傳 request({ url: "/upload/check/" + md5, method: "get", }).then(result => { const isOk = result.isOk; const haveList2 = result.haveList; //已經上傳的分片下標 if(isOk) { console.log("秒傳成功"); return; } haveList.value = haveList2; let chunkIndex = 0; //上傳第一個分片 upload(fileChunkList.value, chunkIndex, md5, file); }) }); return false; }
已經上傳的這些分片下標要跳過上傳
后端
分片來后端后,使用RandomAccessFile就可以在一個文件上進行操作,而不用使用創(chuàng)建多個臨時文件最后合并的方式,通過分片下標和分片大小計算出偏移量,使用RandomAccessFile將跳到偏移開始位置存放數據。RandomAccessFile的第二個參數的model有如下;
? "r":以只讀方式打開指定文件。 ? "rw":以讀、寫方式打開指定文件。 ? "rws":以讀、寫方式打開指定文件。相對于"rw"模式,還要求對文件的內容或元數據的每個更新都同步寫入到底層存儲設備。 ? "rwd":以讀、寫方式打開指定文件。相對于"rw"模式,還要求對文件內容的每個更新都同步寫入到底層存儲設備。
/** * 分片文件上傳 * @param file 文件 * @param chunkIndex 分片下標 * @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); // 寫入分片數據 rw.write(file.getBytes()); // 關閉流 rw.close(); // 讀取已經分片集合 List<Object> hasChunkList; String hasChunkKey = CHUNK_PREFIX + md5; if(redisCache.hasKey(hasChunkKey)){ hasChunkList = redisCache.getCacheList(hasChunkKey); } else { hasChunkList = new ArrayList<>(); } hasChunkList.add(chunkIndex); // 將最新的分片下標更新到Redis中 redisCache.addCacheListOne(hasChunkKey,chunkIndex); // 判斷是否上傳完成 int totalNeedChunks = (int) Math.ceil((double) totalFileSize / CHUNK_SIZE); // 總共需要的分片數 和 已經分片上傳的數量相等 則上傳完成 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; }
最終演示
上傳完成演示
秒傳演示
斷點演示
待優(yōu)化
- 提供查詢進度接口,前端進度條展示,增加用戶體驗。
- 多線程上傳,不同分片用多線程,提高下載速度。
到此這篇關于Java實現(xiàn)瀏覽器大文件上傳的示例詳解的文章就介紹到這了,更多相關Java瀏覽器大文件上傳內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
idea運行程序報錯java程序包org.junit不存在解決辦法
這篇文章主要給大家介紹了關于idea運行程序報錯java程序包org.junit不存在的解決辦法, 當出現(xiàn)程序包org.junit不存在的問題時,可以通過使用適當的JUnit版本、添加依賴或重新下載程序包等方式進行解決,需要的朋友可以參考下2024-02-02JAVA Comparator 和 Comparable接口使用方法
本文介紹了Java中Comparable和Comparator接口的使用,包括它們的定義、方法和應用場景,Comparable用于定義類的自然排序規(guī)則,而Comparator提供了一種靈活的方式來定義對象之間的排序規(guī)則,無需修改類本身,感興趣的朋友一起看看吧2025-03-03SpringBoot使用EmbeddedDatabaseBuilder進行數據庫集成測試
在開發(fā)SpringBoot應用程序時,我們通常需要與數據庫進行交互,為了確保我們的應用程序在生產環(huán)境中可以正常工作,我們需要進行數據庫集成測試,在本文中,我們將介紹如何使用 SpringBoot 中的 EmbeddedDatabaseBuilder 來進行數據庫集成測試2023-07-07Java高并發(fā)之CyclicBarrier的用法詳解
CyclicBarrier 是 Java 中的一種同步工具,它可以讓多個線程在一個屏障點處等待,直到所有線程都到達該點后,才能繼續(xù)執(zhí)行。本文就來和大家聊聊它的用法,需要的可以參考一下2023-03-03