-
上傳說明
檔案上傳花樣百出,根據不同場景使用不同方案進行實現尤為必要。通常開發過程中,檔案較小,直接將檔案轉化為位元組流上傳到伺服器,但是檔案較大時,用普通的方法上傳,顯然效果不是很好,當檔案上傳一半中斷再次上傳時,發現需要重新開始,這種體驗不是很爽,下面介紹幾種好一點兒的上傳方式。
這裡講講如何在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分片上傳等功能