Java大文件上傳、分片上傳、多文件上傳、斷點續傳、上傳文件minio、分片上傳minio等解決方案
-
上傳說明
文件上傳花樣百出,根據不同場景使用不同方案進行實現尤為必要。通常開發過程中,文件較小,直接將文件轉化為字節流上傳到服務器,但是文件較大時,用普通的方法上傳,顯然效果不是很好,當文件上傳一半中斷再次上傳時,發現需要重新開始,這種體驗不是很爽,下面介紹幾種好一點兒的上傳方式。
這里講講如何在Spring boot 編寫上傳代碼,如有問題可以在下留言,我并在文章末尾附上Java上傳源碼供大家下載。
-
- 分片上傳
分片上傳,就是將所要上傳的文件,按照一定的大小,將整個文件分
隔成多個數據塊(我們稱之為Part)來進行分別上傳,上傳完之后再
由服務端對所有上傳的文件進行匯總整合成原始的文件。
-
- 斷點續傳
斷點續傳是在下載/上傳時,將下載/上傳任務(一個文件或一個壓縮
包)人為的劃分為幾個部分,每一個部分采用一個線程進行上傳/下載,
如果碰到網絡故障,可以從已經上傳/下載的部分開始繼續上傳/下載
未完成的部分,而沒有必要從頭開始上傳/下載。
-
Redis啟動安裝
Redis安裝包分為 Windows 版和 Linux 版:
Windows版下載地址:https://github.com/microsoftarchive/redis/releases
Linux版下載地址: https://download.redis.io/releases/
我當前使用的Windows版本:

-
minio下載啟動
windows版本可以參考我之前的文檔:window10安裝minio_minio windows安裝-CSDN博客
啟動會提示:

以上是密碼設置問題需要修改如下:
set MINIO_ROOT_USER=admin
set MINIO_ROOT_PASSWORD=12345678
啟動成功后會輸出相應地址
-
上傳后端Java代碼
后端采用Spring boot項目結構,主要代碼如下:
1 /** 2 * 單文件上傳 3 * 直接將傳入的文件通過io流形式直接寫入(服務器)指定路徑下 4 * 5 * @param file 上傳的文件 6 * @return 7 */ 8 @Override 9 public ResultEntity<Boolean> singleFileUpload(MultipartFile file) { 10 //實際情況下,這些路徑都應該是服務器上面存儲文件的路徑 11 String filePath = System.getProperty("user.dir") + "\\file\\"; 12 File dir = new File(filePath); 13 if (!dir.exists()) dir.mkdir(); 14 15 if (file == null) { 16 return ResultEntity.error(false, "上傳文件為空!"); 17 } 18 InputStream fileInputStream = null; 19 FileOutputStream fileOutputStream = null; 20 try { 21 String filename = file.getOriginalFilename(); 22 fileOutputStream = new FileOutputStream(filePath + filename); 23 fileInputStream = file.getInputStream(); 24 25 byte[] buf = new byte[1024 * 8]; 26 int length; 27 while ((length = fileInputStream.read(buf)) != -1) {//讀取fis文件輸入字節流里面的數據 28 fileOutputStream.write(buf, 0, length);//通過fos文件輸出字節流寫出去 29 } 30 log.info("單文件上傳完成!文件路徑:{},文件名:{},文件大小:{}", filePath, filename, file.getSize()); 31 return ResultEntity.success(true, "單文件上傳完成!"); 32 } catch (IOException e) { 33 return ResultEntity.error(true, "單文件上傳失敗!"); 34 } finally { 35 try { 36 if (fileOutputStream != null) { 37 fileOutputStream.close(); 38 fileOutputStream.flush(); 39 } 40 if (fileInputStream != null) { 41 fileInputStream.close(); 42 } 43 } catch (Exception e) { 44 e.printStackTrace(); 45 } 46 } 47 } 48 49 /** 50 * 多文件上傳 51 * 直接將傳入的多個文件通過io流形式直接寫入(服務器)指定路徑下 52 * 寫入指定路徑下是通過多線程進行文件寫入的,文件寫入線程執行功能就和上面單文件寫入是一樣的 53 * 54 * @param files 上傳的所有文件 55 * @return 56 */ 57 @Override 58 public ResultEntity<Boolean> multipleFileUpload(MultipartFile[] files) { 59 //實際情況下,這些路徑都應該是服務器上面存儲文件的路徑 60 String filePath = System.getProperty("user.dir") + "\\file\\"; 61 File dir = new File(filePath); 62 if (!dir.exists()) dir.mkdir(); 63 64 if (files.length == 0) { 65 return ResultEntity.error(false, "上傳文件為空!"); 66 } 67 ArrayList<String> uploadFiles = new ArrayList<>(); 68 try { 69 70 ArrayList<Future<String>> futures = new ArrayList<>(); 71 //使用多線程來完成對每個文件的寫入 72 for (MultipartFile file : files) { 73 futures.add(partMergeTask.submit(new MultipleFileTaskExecutor(filePath, file))); 74 } 75 76 //這里主要用于監聽各個文件寫入線程是否執行結束 77 int count = 0; 78 while (count != futures.size()) { 79 for (Future<String> future : futures) { 80 if (future.isDone()) { 81 uploadFiles.add(future.get()); 82 count++; 83 } 84 } 85 Thread.sleep(1); 86 } 87 log.info("多文件上傳完成!文件路徑:{},文件信息:{}", filePath, uploadFiles); 88 return ResultEntity.success(true, "多文件上傳完成!"); 89 } catch (Exception e) { 90 log.error("多文件分片上傳失敗!", e); 91 return ResultEntity.error(true, "多文件上傳失敗!"); 92 } 93 94 } 95 96 /** 97 * 單文件分片上傳 98 * 直接將傳入的文件分片通過io流形式寫入(服務器)指定臨時路徑下 99 * 然后判斷是否分片都上傳完成,如果所有分片都上傳完成的話,就把臨時路徑下的分片文件通過流形式讀入合并并從新寫入到(服務器)指定文件路徑下 100 * 最后刪除臨時文件和臨時文件夾,臨時文件夾是通過文件的uuid進行命名的 101 * 102 * @param filePart 分片文件 103 * @param partIndex 當前分片值 104 * @param partNum 所有分片數 105 * @param fileName 當前文件名稱 106 * @param fileUid 當前文件uuid 107 * @return 108 */ 109 @Override 110 public ResultEntity<Boolean> singleFilePartUpload(MultipartFile filePart, Integer partIndex, Integer partNum, String fileName, String fileUid) { 111 //實際情況下,這些路徑都應該是服務器上面存儲文件的路徑 112 String filePath = System.getProperty("user.dir") + "\\file\\";//文件存放路徑 113 String tempPath = filePath + "temp\\" + fileUid;//臨時文件存放路徑 114 File dir = new File(tempPath); 115 if (!dir.exists()) dir.mkdirs(); 116 117 //生成一個臨時文件名 118 String tempFileNamePath = tempPath + "\\" + fileName + "_" + partIndex + ".part"; 119 try { 120 //將分片存儲到臨時文件夾中 121 filePart.transferTo(new File(tempFileNamePath)); 122 123 File tempDir = new File(tempPath); 124 File[] tempFiles = tempDir.listFiles(); 125 126 one: 127 if (partNum.equals(Objects.requireNonNull(tempFiles).length)) { 128 //需要校驗一下,表示已有異步程序正在合并了;如果是分布式這個校驗可以加入redis的分布式鎖來完成 129 if (isMergePart.get(fileUid) != null) { 130 break one; 131 } 132 isMergePart.put(fileUid, tempFiles.length); 133 System.out.println("所有分片上傳完成,預計總分片:" + partNum + "; 實際總分片:" + tempFiles.length); 134 135 FileOutputStream fileOutputStream = new FileOutputStream(filePath + fileName); 136 //這里如果分片很多的情況下,可以采用多線程來執行 137 for (int i = 0; i < partNum; i++) { 138 //讀取分片數據,進行分片合并 139 FileInputStream fileInputStream = new FileInputStream(tempPath + "\\" + fileName + "_" + i + ".part"); 140 byte[] buf = new byte[1024 * 8];//8MB 141 int length; 142 while ((length = fileInputStream.read(buf)) != -1) {//讀取fis文件輸入字節流里面的數據 143 fileOutputStream.write(buf, 0, length);//通過fos文件輸出字節流寫出去 144 } 145 fileInputStream.close(); 146 } 147 fileOutputStream.flush(); 148 fileOutputStream.close(); 149 150 // 刪除臨時文件夾里面的分片文件 如果使用流操作且沒有關閉輸入流,可能導致刪除失敗 151 for (int i = 0; i < partNum; i++) { 152 boolean delete = new File(tempPath + "\\" + fileName + "_" + i + ".part").delete(); 153 File file = new File(tempPath + "\\" + fileName + "_" + i + ".part"); 154 } 155 //在刪除對應的臨時文件夾 156 if (Objects.requireNonNull(tempDir.listFiles()).length == 0) { 157 tempDir.delete(); 158 } 159 isMergePart.remove(fileUid); 160 } 161 162 } catch (Exception e) { 163 log.error("單文件分片上傳失敗!", e); 164 return ResultEntity.error(false, "單文件分片上傳失敗"); 165 } 166 //通過返回成功的分片值,來驗證分片是否有丟失 167 return ResultEntity.success(true, partIndex.toString()); 168 } 169 170 /** 171 * 多文件分片上傳 172 * 先將所有文件分片讀入到(服務器)指定臨時路徑下,每個文件的分片文件的臨時文件夾都是已文件的uuid進行命名的 173 * 然后判斷對已經上傳所有分片的文件進行合并,此處是通過多線程對每一個文件的分片文件進行合并的 174 * 最后對已經合并完成的分片臨時文件和文件夾進行刪除 175 * 176 * @param filePart 分片文件 177 * @param partIndex 當前分片值 178 * @param partNum 總分片數 179 * @param fileName 當前文件名稱 180 * @param fileUid 當前文件uuid 181 * @return 182 */ 183 @Override 184 public ResultEntity<String> multipleFilePartUpload(MultipartFile filePart, Integer partIndex, Integer partNum, String fileName, String fileUid) { 185 //實際情況下,這些路徑都應該是服務器上面存儲文件的路徑 186 String filePath = System.getProperty("user.dir") + "\\file\\";//文件存放路徑 187 String tempPath = filePath + "temp\\" + fileUid;//臨時文件存放路徑 188 File dir = new File(tempPath); 189 if (!dir.exists()) dir.mkdirs(); 190 //生成一個臨時文件名 191 String tempFileNamePath = tempPath + "\\" + fileName + "_" + partIndex + ".part"; 192 try { 193 filePart.transferTo(new File(tempFileNamePath)); 194 195 File tempDir = new File(tempPath); 196 File[] tempFiles = tempDir.listFiles(); 197 //如果臨時文件夾中分片數量和實際分片數量一致的時候,就需要進行分片合并 198 one: 199 if (partNum.equals(tempFiles.length)) { 200 //需要校驗一下,表示已有異步程序正在合并了;如果是分布式這個校驗可以加入redis的分布式鎖來完成 201 if (isMergePart.get(fileUid) != null) { 202 break one; 203 } 204 isMergePart.put(fileUid, tempFiles.length); 205 System.out.println(fileName + ":所有分片上傳完成,預計總分片:" + partNum + "; 實際總分片:" + tempFiles.length); 206 207 //使用多線程來完成對每個文件的合并 208 Future<Integer> submit = partMergeTask.submit(new PartMergeTaskExecutor(filePath, tempPath, fileName, partNum)); 209 System.out.println("上傳文件名:" + fileName + "; 總大小:" + submit.get()); 210 isMergePart.remove(fileUid); 211 } 212 } catch (Exception e) { 213 log.error("{}:多文件分片上傳失敗!", fileName, e); 214 return ResultEntity.error("", "多文件分片上傳失敗"); 215 } 216 //通過返回成功的分片值,來驗證分片是否有丟失 217 return ResultEntity.success(partIndex.toString(), fileUid); 218 } 219 220 /** 221 * 多文件(分片)秒傳 222 * 通過對比已有的文件分片md5值和需要上傳文件分片的MD5值, 223 * 在文件分片合并的時候,對已有的文件進行地址索引,對沒有的文件進行臨時文件寫入 224 * 最后合并的時候根據不同的文件分片進行文件讀取寫入 225 * 226 * @param filePart 上傳沒有的分片文件 227 * @param fileInfo 當前分片文件相關信息 228 * @param fileOther 已存在文件分片相關信息 229 * @return 230 */ 231 @Override 232 public ResultEntity<String> multipleFilePartFlashUpload(MultipartFile filePart, String fileInfo, String fileOther) { 233 DiskFileIndexVo upFileInfo = JSONObject.parseObject(fileInfo, DiskFileIndexVo.class); 234 List<DiskFileIndexVo> notUpFileInfoList = JSON.parseArray(fileOther, DiskFileIndexVo.class); 235 //實際情況下,這些路徑都應該是服務器上面存儲文件的路徑 236 String filePath = System.getProperty("user.dir") + "\\file\\";//文件存放路徑 237 //正常情況下,這個臨時文件也應該放入(服務器)非臨時文件夾中,這樣方便下次其他文件上傳查找是否曾經上傳過類似的 238 //當前demo是單獨存放在臨時文件夾中,文件合并完成之后直接刪除的 239 String tempPath = filePath + "temp\\" + upFileInfo.getFileUid();//臨時文件存放路徑 240 241 File dir = new File(tempPath); 242 if (!dir.exists()) dir.mkdirs(); 243 //生成一個臨時文件名 244 String tempFileNamePath = tempPath + "\\" + upFileInfo.getFileName() + "_" + upFileInfo.getPartIndex() + ".part"; 245 246 try { 247 filePart.transferTo(new File(tempFileNamePath)); 248 249 File tempDir = new File(tempPath); 250 File[] tempFiles = tempDir.listFiles(); 251 notUpFileInfoList = notUpFileInfoList.stream().filter(e -> 252 upFileInfo.getFileUid().equals(e.getFileUid())).collect(Collectors.toList()); 253 //如果臨時文件夾中分片數量和實際分片數量一致的時候,就需要進行分片合并 254 one: 255 if ((upFileInfo.getPartNum() - notUpFileInfoList.size()) == tempFiles.length) { 256 //需要校驗一下,表示已有異步程序正在合并了;如果是分布式這個校驗可以加入redis的分布式鎖來完成 257 if (isMergePart.get(upFileInfo.getFileUid()) != null) { 258 break one; 259 } 260 isMergePart.put(upFileInfo.getFileUid(), tempFiles.length); 261 System.out.println(upFileInfo.getFileName() + ":所有分片上傳完成,預計總分片:" + upFileInfo.getPartNum() 262 + "; 實際總分片:" + tempFiles.length + "; 已存在分片數:" + notUpFileInfoList.size()); 263 264 //使用多線程來完成對每個文件的合并 265 Future<Integer> submit = partMergeTask.submit( 266 new PartMergeFlashTaskExecutor(filePath, upFileInfo, notUpFileInfoList)); 267 isMergePart.remove(upFileInfo.getFileUid()); 268 } 269 } catch (Exception e) { 270 log.error("{}:多文件(分片)秒傳失敗!", upFileInfo.getFileName(), e); 271 return ResultEntity.error("", "多文件(分片)秒傳失敗!"); 272 } 273 //通過返回成功的分片值,來驗證分片是否有丟失 274 return ResultEntity.success(upFileInfo.getPartIndex().toString(), upFileInfo.getFileUid()); 275 } 276 277 /** 278 * 根據傳入需要上傳的文件片段的md5值來對比服務器中的文件的md5值,將已有對應的md5值的文件過濾出來, 279 * 通知前端或者自行出來這些文件,即為不需要上傳的文件分片,并將已有的文件分片地址索引返回給前端進行出來 280 * 281 * @param upLoadFileListMd5 原本需要上傳文件的索引分片信息 282 * @return 283 */ 284 @Override 285 public ResultEntity<List<DiskFileIndexVo>> checkDiskFile(List<DiskFileIndexVo> upLoadFileListMd5) { 286 List<DiskFileIndexVo> notUploadFile; 287 try { 288 //后端服務器已經存在的分片md5值集合 289 List<DiskFileIndexVo> diskFileMd5IndexList = diskFileIndexVos; 290 291 notUploadFile = upLoadFileListMd5.stream().filter(uf -> diskFileMd5IndexList.stream().anyMatch( 292 df -> { 293 if (df.getFileMd5().equals(uf.getFileMd5())) { 294 uf.setFileIndex(df.getFileName());//不需要上傳文件的地址索引 295 return true; 296 } 297 return false; 298 })).collect(Collectors.toList()); 299 log.info("過濾出不需要上傳的文件分片:{}", notUploadFile); 300 } catch (Exception e) { 301 log.error("上傳文件檢測異常!", e); 302 return ResultEntity.error("上傳文件檢測異常!"); 303 } 304 return ResultEntity.success(notUploadFile); 305 } 306 307 /** 308 * 根據文件uuid(md5生成的)來判斷此文件在服務器中是否未上傳完整, 309 * 如果沒上傳完整,則返回相關上傳進度等信息 310 * 311 * @param pointFileIndexVo 312 * @return 313 */ 314 @Override 315 public ResultEntity<PointFileIndexVo> checkUploadFileIndex(PointFileIndexVo pointFileIndexVo) { 316 try { 317 List<String> list = uploadProgress.get(pointFileIndexVo.getFileMd5()); 318 if (list == null) list = new ArrayList<>(); 319 pointFileIndexVo.setParts(list); 320 System.out.println("已上傳部分:" + list); 321 return ResultEntity.success(pointFileIndexVo); 322 } catch (Exception e) { 323 log.error("上傳文件檢測異常!", e); 324 return ResultEntity.error("上傳文件檢測異常!"); 325 } 326 } 327 328 /** 329 * 單文件(分片)斷點上傳 330 * 331 * @param filePart 需要上傳的分片文件 332 * @param fileInfo 當前需要上傳的分片文件信息,如uuid,文件名,文件總分片數量等 333 * @return 334 */ 335 @Override 336 public ResultEntity<String> singleFilePartPointUpload(MultipartFile filePart, String fileInfo) { 337 PointFileIndexVo pointFileIndexVo = JSONObject.parseObject(fileInfo, PointFileIndexVo.class); 338 //實際情況下,這些路徑都應該是服務器上面存儲文件的路徑 339 String filePath = System.getProperty("user.dir") + "\\file\\";//文件存放路徑 340 String tempPath = filePath + "temp\\" + pointFileIndexVo.getFileMd5();//臨時文件存放路徑 341 File dir = new File(tempPath); 342 if (!dir.exists()) dir.mkdirs(); 343 344 //生成一個臨時文件名 345 String tempFileNamePath = tempPath + "\\" + pointFileIndexVo.getFileName() + "_" + pointFileIndexVo.getPartIndex() + ".part"; 346 try { 347 //將分片存儲到臨時文件夾中 348 filePart.transferTo(new File(tempFileNamePath)); 349 350 List<String> partIndex = uploadProgress.get(pointFileIndexVo.getFileMd5()); 351 if (Objects.isNull(partIndex)) { 352 partIndex = new ArrayList<>(); 353 } 354 partIndex.add(pointFileIndexVo.getPartIndex().toString()); 355 uploadProgress.put(pointFileIndexVo.getFileMd5(), partIndex); 356 357 File tempDir = new File(tempPath); 358 File[] tempFiles = tempDir.listFiles(); 359 360 one: 361 if (pointFileIndexVo.getPartNum().equals(Objects.requireNonNull(tempFiles).length)) { 362 //需要校驗一下,表示已有異步程序正在合并了;如果是分布式這個校驗可以加入redis的分布式鎖來完成 363 if (isMergePart.get(pointFileIndexVo.getFileMd5()) != null) { 364 break one; 365 } 366 isMergePart.put(pointFileIndexVo.getFileMd5(), tempFiles.length); 367 System.out.println("所有分片上傳完成,預計總分片:" + pointFileIndexVo.getPartNum() + "; 實際總分片:" + tempFiles.length); 368 //讀取分片數據,進行分片合并 369 FileOutputStream fileOutputStream = new FileOutputStream(filePath + pointFileIndexVo.getFileName()); 370 //這里如果分片很多的情況下,可以采用多線程來執行 371 for (int i = 0; i < pointFileIndexVo.getPartNum(); i++) { 372 FileInputStream fileInputStream = new FileInputStream(tempPath + "\\" + pointFileIndexVo.getFileName() + "_" + i + ".part"); 373 byte[] buf = new byte[1024 * 8];//8MB 374 int length; 375 while ((length = fileInputStream.read(buf)) != -1) {//讀取fis文件輸入字節流里面的數據 376 fileOutputStream.write(buf, 0, length);//通過fos文件輸出字節流寫出去 377 } 378 fileInputStream.close(); 379 } 380 fileOutputStream.flush(); 381 fileOutputStream.close(); 382 383 // 刪除臨時文件夾里面的分片文件 如果使用流操作且沒有關閉輸入流,可能導致刪除失敗 384 for (int i = 0; i < pointFileIndexVo.getPartNum(); i++) { 385 boolean delete = new File(tempPath + "\\" + pointFileIndexVo.getFileName() + "_" + i + ".part").delete(); 386 File file = new File(tempPath + "\\" + pointFileIndexVo.getFileName() + "_" + i + ".part"); 387 } 388 //在刪除對應的臨時文件夾 389 if (Objects.requireNonNull(tempDir.listFiles()).length == 0) { 390 tempDir.delete(); 391 } 392 isMergePart.remove(pointFileIndexVo.getFileMd5()); 393 uploadProgress.remove(pointFileIndexVo.getFileMd5()); 394 } 395 396 } catch (Exception e) { 397 log.error("單文件分片上傳失敗!", e); 398 return ResultEntity.error(pointFileIndexVo.getFileMd5(), "單文件分片上傳失敗"); 399 } 400 //通過返回成功的分片值,來驗證分片是否有丟失 401 return ResultEntity.success(pointFileIndexVo.getFileMd5(), pointFileIndexVo.getPartIndex().toString()); 402 } 403 404 /** 405 * 獲取(服務器)指定文件存儲路徑下所有文件MD5值 406 * 實際情況下,每一個文件的md5值都是單獨保存在數據庫或者其他存儲機制中的, 407 * 不需要每次都去讀取文件然后獲取md5值,這樣多次io讀取很耗性能 408 * 409 * @return 410 * @throws Exception 411 */ 412 @Bean 413 private List<DiskFileIndexVo> getDiskFileMd5Index() throws Exception { 414 String filePath = System.getProperty("user.dir") + "\\file\\part\\"; 415 File saveFileDir = new File(filePath); 416 if (!saveFileDir.exists()) saveFileDir.mkdirs(); 417 418 List<DiskFileIndexVo> diskFileIndexVoList = new ArrayList<>(); 419 File[] listFiles = saveFileDir.listFiles(); 420 if (listFiles == null) return diskFileIndexVoList; 421 422 for (File listFile : listFiles) { 423 String fileName = listFile.getName(); 424 FileInputStream fileInputStream = new FileInputStream(filePath + fileName); 425 String md5DigestAsHex = DigestUtils.md5DigestAsHex(fileInputStream); 426 427 DiskFileIndexVo diskFileIndexVo = new DiskFileIndexVo(); 428 diskFileIndexVo.setFileName(fileName); 429 diskFileIndexVo.setFileMd5(md5DigestAsHex); 430 diskFileIndexVoList.add(diskFileIndexVo); 431 fileInputStream.close(); 432 } 433 434 diskFileIndexVos = diskFileIndexVoList; 435 log.info("服務器磁盤所有文件 {}", diskFileIndexVoList); 436 return diskFileIndexVoList; 437 }
代碼結構圖:

-
前端代碼
整體的過程如下
前端將文件按照百分比進行計算,每次上傳文件的百分之一(文件分片),給文件分片做上序號及文件uuid,然后在循環里面對文件片段上傳的時候在將當前分片值一起傳給后端。
后端將前端每次上傳的文件,放入到緩存目錄;
前端將全部的文件內容都上傳完畢后,發送一個合并請求;
后端合并分片的之后對文件進行命名保存;
后端保存臨時分片的時候命名索引,方便合并的時候按照分片索引進行合并;
vue模板代碼:
1 <!-- 單文件分片上傳 --> 2 <div class="fileUploadStyle"> 3 <h3>單文件分片上傳</h3> 4 <el-upload ref="upload" name="files" action="#" :on-change="selectSinglePartFile" 5 :on-remove="removeSingleFilePart" :file-list="singleFilePart.fileList" :auto-upload="false"> 6 <el-button slot="trigger" size="small" type="primary">選取文件</el-button> 7 <el-button style="margin-left: 10px;" size="small" type="success" 8 @click="singleFilePartUpload">點擊進行單文件分片上傳</el-button> 9 <div slot="tip" class="el-upload__tip">主要用于測試單文件分片上傳</div> 10 </el-upload> 11 <el-progress :text-inside="true" class="progress" :stroke-width="26" :percentage="singlePartFileProgress" /> 12 </div> 13 <!-- 多文件分片上傳 --> 14 <div class="fileUploadStyle"> 15 <h3>多文件分片上傳</h3> 16 <el-upload ref="upload" name="files" action="#" :on-change="selectMultiplePartFile" 17 :on-remove="removeMultiplePartFile" :file-list="multipleFilePart.fileList" :auto-upload="false"> 18 <el-button slot="trigger" size="small" type="primary">選取文件</el-button> 19 <el-button style="margin-left: 10px;" size="small" type="success" 20 @click="multipleFilePartUpload">點擊進行多文件分片上傳</el-button> 21 <div slot="tip" class="el-upload__tip">主要用于測試多文件分片上傳</div> 22 </el-upload> 23 </div> 24 <!-- 多文件(分片)秒傳 --> 25 <div class="fileUploadStyle"> 26 <h3>多文件(分片MD5值)秒傳</h3> 27 <el-upload ref="upload" name="files" action="#" :on-change="selectMultiplePartFlashFile" 28 :on-remove="removeMultiplePartFlashFile" :file-list="multipleFilePartFlash.fileList" :auto-upload="false"> 29 <el-button slot="trigger" size="small" type="primary">選取文件</el-button> 30 <el-button style="margin-left: 10px;" size="small" type="success" 31 @click="multipleFilePartFlashUpload">點擊進行文件秒傳</el-button> 32 <div slot="tip" class="el-upload__tip">主要用于測試多文件(分片MD5值)秒傳</div> 33 </el-upload> 34 </div>
js屬性定義:

上傳部分代碼:

minio分片上傳:

上傳樣式:

-
功能演示及源碼
部分演示圖: 這里就以上傳minio為例,測試上傳minio以分片方式上

以8M進行分切 28M剛好分了四個區,我們使用redis客戶工具查看

最后成功上傳到minio中

而且看到上傳文件大小為:28M
文件上傳代碼其實功能也簡單也很明確,先將一個大文件分成n個小文件,然后給后端檢測這些分片是否曾經上傳中斷過,即對這些分片進行過濾出來,并將過濾出對應的分片定位值結果返回給前端處理出不需要上傳的分片和需要上傳的文件分片,這里主要還是區分到確定是這個文件的分區文件。
這里,為了方便大家直接能夠使用Java源碼,本文所有都采用Spring boot框架模式,另外使用了第三方插件,如果大家使用中沒有使用到minio可以不需要引入并把相關代碼移除即可,代碼使用了redis作為分區數量緩存,相對于Java內存更穩定些。
demo源碼下載gitee地址(代碼包含Java后端工程和vue2前端工程):java-file-upload-demo: java 多文件上傳、多文件分片上傳、多文件秒傳、minio分片上傳等功能

根據不同場景使用不同方案進行實現尤為必要。通常開發過程中,文件較小,直接將文件轉化為字節流上傳到服務器,但是文件較大時,用普通的方法上傳,顯然效果不是很好,當文件上傳一半中斷再次上傳時,發現需要重新開始,這種體驗不是很爽,下面介紹幾種好一點兒的上傳方式。
這里講講如何在Spring boot 編寫上傳代碼,如有問題可以在下留言,我并在文章末尾附上Java上傳源碼供大家下載
浙公網安備 33010602011771號