Javaweb分片上傳大檔案
阿新 • • 發佈:2022-05-27
Javaweb分片上傳大檔案
大檔案上傳採取分片上傳,實現為:1、分片上傳檔案,2、合併檔案
1、後端java介面程式碼
/** * 上傳分片檔案 * @param file 分片檔案 * @param fileid 前端生成的uuid,用於指定此次上傳的唯一標識 * @param request * @return * @throws IOException */ @RequestMapping(value = "/file/uploadFilePart", method = {RequestMethod.POST}) public Map<String,Object> uploadFilePart(@RequestParam("file") MultipartFile file,String fileid,HttpServletRequest request) throws IOException { Map<String,Object> resultMap = new HashMap<>(); String basePath = appConfig.getZipUnPath() + File.separatorChar + fileid; File baseDir= new File(basePath); if(!baseDir.exists()) { baseDir.mkdirs(); } System.out.println("檔案目錄為:"+ basePath); Integer current = Integer.parseInt(request.getParameter("index")); // 總分片數 Integer total = Integer.parseInt(request.getParameter("total")); // 當前檔案的路徑 String fileNamePath = basePath + File.separatorChar + current; OutputStream out = new FileOutputStream(fileNamePath); // 獲取檔案的相關資料,然後寫入到檔案中 byte[] bytes = file.getBytes(); out.write(bytes); out.flush(); out.close(); return resultMap; } /** * 合併分片檔案 * @param filename 合併後的檔名(也可後端自定義) * @param fileid 前端生成的uuid,用於指定此次上傳的唯一標識 * @return * @throws IOException */ @RequestMapping(value = "/file/mergeFilePart", method = {RequestMethod.POST}) public Map<String,Object> mergeFilePart(String filename,String fileid) throws IOException { Map<String,Object> resultMap = new HashMap<>(); String basePath = appConfig.getZipUnPath() + File.separatorChar + fileid; int combineFlag = 0; // 合併操作 try { String fileUrl = combineFiles(filename, basePath); if(StringUtils.isNotBlank(fileUrl)) { combineFlag = 1; resultMap.put("filePath", fileUrl); } } catch (Exception e) { e.printStackTrace(); } finally { if(combineFlag == 0) { // 合併失敗 resultMap.put("errMessage", "合併檔案失敗"); resultMap.put("code", 500); } else { resultMap.put("code", 0); } } resultMap.put("code", 0); resultMap.put("massage", "合併成功"); return resultMap; } //合併檔案 private String combineFiles(String fileName, String basePath) throws Exception { int returnFlag = CombineFile(fileName, basePath); String fileUrl = null; if(returnFlag == 1) { // 此處表示檔案合併成功,合併後的檔案路徑為:basePath+"/"+fileName fileUrl = basePath+"/"+fileName; } return fileUrl; } /** * 合併檔案, * @param tar 合成目標的檔名稱 * @param baseFilePath 你要合併哪個資料夾的檔案,裡面必須是要合併的檔案 * @return * @throws Exception */ public int CombineFile(String tar, String baseFilePath) throws Exception { File dirFile = new File(baseFilePath); FileInputStream fis; // 一次讀取2M資料,將讀取到的資料儲存到byte位元組陣列中 byte buffer[] = new byte[1024 * 1024 * 2]; int len; // 判斷file是否為目錄 if(dirFile.isDirectory()) { String[] fileNames = dirFile.list(); FileOutputStream fos = new FileOutputStream(baseFilePath + File.separatorChar + tar); // 實現目錄自定義排序 Arrays.sort(fileNames, new StringComparator()); for (int i = 0;i<fileNames.length ;i++){ fis = new FileInputStream(baseFilePath + File.separatorChar + fileNames[i]); len = 0; while ((len = fis.read(buffer)) != -1) { // buffer從指定位元組陣列寫入。buffer:資料中的起始偏移量,len:寫入的字數。 fos.write(buffer, 0, len); } fos.flush(); fis.close(); } fos.close(); } return 1; }
檔名比較類
import java.util.Comparator; /** * 檔名排序比較類 */ public class StringComparator implements Comparator<String> { @Override public int compare(String s1, String s2) { if (returnDouble(s1) < returnDouble(s2)){ return -1; } else if (returnDouble(s1) > returnDouble(s2)) { return 1; } else { return 0; } } public static double returnDouble(String str){ StringBuffer sb = new StringBuffer(); for(int i=0;i<str.length();i++){ if(Character.isDigit(str.charAt(i))) { sb.append(str.charAt(i)); } else if(str.charAt(i)=='.'&&i<str.length()-1&&Character.isDigit(str.charAt(i+1))) { sb.append(str.charAt(i)); } else { break; } } if(sb.toString().isEmpty()){ return 0; } else { return Double.parseDouble(sb.toString()); } } }
2、前端程式碼比較簡單,網上尋找根據自己需要更改的,簡單的js+html實現的分片上傳檔案
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>檔案上傳</title> <script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script> <script src="https://code.jquery.com/jquery-3.4.1.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.0/spark-md5.js"></script> <style> /* 自定義進度條樣式 */ .precent input[type=range] { -webkit-appearance: none; /*清除系統預設樣式*/ width: 7.8rem; /* background: -webkit-linear-gradient(#ddd, #ddd) no-repeat, #ddd; */ /*設定左邊顏色為#61bd12,右邊顏色為#ddd*/ background-size: 75% 100%; /*設定左右寬度比例*/ height: 0.6rem; /*橫條的高度*/ border-radius: 0.4rem; border: 1px solid #ddd; box-shadow: 0 0 10px rgba(0,0,0,.125) inset ; } /*拖動塊的樣式*/ .precent input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; /*清除系統預設樣式*/ height: .9rem; /*拖動塊高度*/ width: .9rem; /*拖動塊寬度*/ background: #fff; /*拖動塊背景*/ border-radius: 50%; /*外觀設定為圓形*/ border: solid 1px #ddd; /*設定邊框*/ } </style> </head> <body> <h1>大檔案分片上傳測試</h1> <div> <input id="file" type="file" name="avatar" /> <div style="padding: 10px 0;"> <input id="submitBtn" type="button" value="提交" /> <input id="pauseBtn" type="button" value="暫停" /> </div> <div class="precent"> <input type="range" value="0" /><span id="precentVal">0%</span> </div> </div> <script type="text/javascript" src="./index.js"></script> </body> </html>
$(document).ready(() => { const submitBtn = $('#submitBtn'); //提交按鈕 const precentDom = $(".precent input")[0]; // 進度條 const precentVal = $("#precentVal"); // 進度條值對應dom const pauseBtn = $('#pauseBtn'); // 暫停按鈕 // 每個chunk的大小,設定為1兆 const chunkSize = 1 * 1024 * 1024; // 獲取slice方法,做相容處理 const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; // 對檔案進行MD5加密(檔案內容+檔案標題形式) const hashFile = (file) => { return new Promise((resolve, reject) => { const chunks = Math.ceil(file.size / chunkSize); let currentChunk = 0; const spark = new SparkMD5.ArrayBuffer(); const fileReader = new FileReader(); function loadNext() { const start = currentChunk * chunkSize; const end = start + chunkSize >= file.size ? file.size : start + chunkSize; fileReader.readAsArrayBuffer(blobSlice.call(file, start, end)); } fileReader.onload = e => { spark.append(e.target.result); // Append array buffer currentChunk += 1; if (currentChunk < chunks) { loadNext(); } else { console.log('finished loading'); const result = spark.end(); // 通過內容和檔名稱進行md5加密 const sparkMd5 = new SparkMD5(); sparkMd5.append(result); sparkMd5.append(file.name); const hexHash = sparkMd5.end(); resolve(hexHash); } }; fileReader.onerror = () => { console.warn('檔案讀取失敗!'); }; loadNext(); }).catch(err => { console.log(err); }); } // 提交 submitBtn.on('click', async () => { var pauseStatus = false; var nowUploadNums = 0 // 1.讀取檔案 const fileDom = $('#file')[0]; const files = fileDom.files; const file = files[0]; if (!file) { alert('沒有獲取檔案'); return; } // 2.設定分片引數屬性、獲取檔案MD5值 const hash = await hashFile(file); //檔案 hash const blockCount = Math.ceil(file.size / chunkSize); // 分片總數 const axiosPromiseArray = []; // axiosPromise陣列 const fileid = getUuid(); const naame = file.name; debugger; // 檔案上傳 const uploadFile = () => { const start = nowUploadNums * chunkSize; const end = Math.min(file.size, start + chunkSize); // 構建表單 const form = new FormData(); // blobSlice.call(file, start, end)方法是用於進行檔案分片 form.append('file', blobSlice.call(file, start, end)); form.append('index', nowUploadNums); form.append('total', blockCount); //form.append('hash', hash); form.append('filename', name); form.append('fileid', fileid); // ajax提交 分片,此時 content-type 為 multipart/form-data const axiosOptions = { onUploadProgress: e => { nowUploadNums++; // 判斷分片是否上傳完成 if (nowUploadNums < blockCount) { setPrecent(nowUploadNums, blockCount); uploadFile(nowUploadNums) } else { // 4.所有分片上傳後,請求合併分片檔案 setPrecent(blockCount, blockCount); // 全部上傳完成 var merge = new FormData(); debugger; merge.append('filename', file.name); merge.append('total', blockCount); merge.append('fileid', fileid); axios.post('/file/mergeFilePart',merge).then(res => { console.log(res.data, file); pauseStatus = false; alert('上傳成功'); }).catch(err => { console.log(err); }); } }, }; // 加入到 Promise 陣列中 if (!pauseStatus) { axiosPromiseArray.push(axios.post('/file/uploadFilePart', form, axiosOptions)); } } function getUuid() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8; return v.toString(16); }); } // 設定進度條 function setPrecent(now, total) { var prencentValue = ((now / total) * 100).toFixed(2) precentDom.value = prencentValue precentVal.text(prencentValue + '%') precentDom.style.cssText = `background:-webkit-linear-gradient(top, #059CFA, #059CFA) 0% 0% / ${prencentValue}% 100% no-repeat` } // 暫停 pauseBtn.on('click', (e) => { pauseStatus = !pauseStatus; e.currentTarget.value = pauseStatus ? '開始' : '暫停' if (!pauseStatus) { uploadFile(nowUploadNums) } }) uploadFile(); }); })
3、效果展示,壓縮檔案為合併後的檔案