您好,登錄后才能下訂單哦!
本篇內容介紹了“如何使用大文件上傳:秒傳、斷點續傳、分片上傳方法”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
秒傳
1、什么是秒傳
通俗的說,你把要上傳的東西上傳,服務器會先做MD5校驗,如果服務器上有一樣的東西,它就直接給你個新地址,其實你下載的都是服務器上的同一個文件,想要不秒傳,其實只要讓MD5改變,就是對文件本身做一下修改(改名字不行),例如一個文本文件,你多加幾個字,MD5就變了,就不會秒傳了.
2、本文實現的秒傳核心邏輯
a、利用redis的set方法存放文件上傳狀態,其中key為文件上傳的md5,value為是否上傳完成的標志位,
b、當標志位true為上傳已經完成,此時如果有相同文件上傳,則進入秒傳邏輯。如果標志位為false,則說明還沒上傳完成,此時需要在調用set的方法,保存塊號文件記錄的路徑,其中key為上傳文件md5加一個固定前綴,value為塊號文件記錄路徑
分片上傳
1.什么是分片上傳
分片上傳,就是將所要上傳的文件,按照一定的大小,將整個文件分隔成多個數據塊(我們稱之為Part)來進行分別上傳,上傳完之后再由服務端對所有上傳的文件進行匯總整合成原始的文件。
2.分片上傳的場景
1.大文件上傳
2.網絡環境環境不好,存在需要重傳風險的場景
斷點續傳
1、什么是斷點續傳
斷點續傳是在下載或上傳時,將下載或上傳任務(一個文件或一個壓縮包)人為的劃分為幾個部分,每一個部分采用一個線程進行上傳或下載,如果碰到網絡故障,可以從已經上傳或下載的部分開始繼續上傳或者下載未完成的部分,而沒有必要從頭開始上傳或者下載。本文的斷點續傳主要是針對斷點上傳場景。
2、應用場景
斷點續傳可以看成是分片上傳的一個衍生,因此可以使用分片上傳的場景,都可以使用斷點續傳。
3、實現斷點續傳的核心邏輯
在分片上傳的過程中,如果因為系統崩潰或者網絡中斷等異常因素導致上傳中斷,這時候客戶端需要記錄上傳的進度。在之后支持再次上傳時,可以繼續從上次上傳中斷的地方進行繼續上傳。
為了避免客戶端在上傳之后的進度數據被刪除而導致重新開始從頭上傳的問題,服務端也可以提供相應的接口便于客戶端對已經上傳的分片數據進行查詢,從而使客戶端知道已經上傳的分片數據,從而從下一個分片數據開始繼續上傳。
4、實現流程步驟
a、方案一,常規步驟
將需要上傳的文件按照一定的分割規則,分割成相同大小的數據塊;
初始化一個分片上傳任務,返回本次分片上傳唯一標識;
按照一定的策略(串行或并行)發送各個分片數據塊;
發送完成后,服務端根據判斷數據上傳是否完整,如果完整,則進行數據塊合成得到原始文件。
b、方案二、本文實現的步驟
前端(客戶端)需要根據固定大小對文件進行分片,請求后端(服務端)時要帶上分片序號和大小
服務端創建conf文件用來記錄分塊位置,conf文件長度為總分片數,每上傳一個分塊即向conf文件中寫入一個127,那么沒上傳的位置就是默認的0,已上傳的就是Byte.MAX_VALUE 127(這步是實現斷點續傳和秒傳的核心步驟)
服務器按照請求數據中給的分片序號和每片分塊大小(分片大小是固定且一樣的)算出開始位置,與讀取到的文件片段數據,寫入文件。
5、分片上傳/斷點上傳代碼實現
a、前端采用百度提供的webuploader的插件,進行分片。因本文主要介紹服務端代碼實現,webuploader如何進行分片,具體實現可以查看如下鏈接:
http://fex.baidu.com/webuploader/getting-started.html
b、后端用兩種方式實現文件寫入,一種是用RandomAccessFile,如果對RandomAccessFile不熟悉的朋友,可以查看如下鏈接:
https://blog.csdn.net/dimudan2015/article/details/81910690
另一種是使用MappedByteBuffer,對MappedByteBuffer不熟悉的朋友,可以查看如下鏈接進行了解:
https://www.jianshu.com/p/f90866dcbffc
后端進行寫入操作的核心代碼
a、RandomAccessFile實現方式
@UploadMode(mode = UploadModeEnum.RANDOM_ACCESS) @Slf4j public class RandomAccessUploadStrategy extends SliceUploadTemplate { @Autowired private FilePathUtil filePathUtil; @Value("${upload.chunkSize}") private long defaultChunkSize; @Override public boolean upload(FileUploadRequestDTO param) { RandomAccessFile accessTmpFile = null; try { String uploadDirPath = filePathUtil.getPath(param); File tmpFile = super.createTmpFile(param); accessTmpFile = new RandomAccessFile(tmpFile, "rw"); //這個必須與前端設定的值一致 long chunkSize = Objects.isNull(param.getChunkSize()) ? defaultChunkSize * 1024 * 1024 : param.getChunkSize(); long offset = chunkSize * param.getChunk(); //定位到該分片的偏移量 accessTmpFile.seek(offset); //寫入該分片數據 accessTmpFile.write(param.getFile().getBytes()); boolean isOk = super.checkAndSetUploadProgress(param, uploadDirPath); return isOk; } catch (IOException e) { log.error(e.getMessage(), e); } finally { FileUtil.close(accessTmpFile); } return false; } }
b、MappedByteBuffer實現方式
@UploadMode(mode = UploadModeEnum.MAPPED_BYTEBUFFER) @Slf4j public class MappedByteBufferUploadStrategy extends SliceUploadTemplate { @Autowired private FilePathUtil filePathUtil; @Value("${upload.chunkSize}") private long defaultChunkSize; @Override public boolean upload(FileUploadRequestDTO param) { RandomAccessFile tempRaf = null; FileChannel fileChannel = null; MappedByteBuffer mappedByteBuffer = null; try { String uploadDirPath = filePathUtil.getPath(param); File tmpFile = super.createTmpFile(param); tempRaf = new RandomAccessFile(tmpFile, "rw"); fileChannel = tempRaf.getChannel(); long chunkSize = Objects.isNull(param.getChunkSize()) ? defaultChunkSize * 1024 * 1024 : param.getChunkSize(); //寫入該分片數據 long offset = chunkSize * param.getChunk(); byte[] fileData = param.getFile().getBytes(); mappedByteBuffer = fileChannel .map(FileChannel.MapMode.READ_WRITE, offset, fileData.length); mappedByteBuffer.put(fileData); boolean isOk = super.checkAndSetUploadProgress(param, uploadDirPath); return isOk; } catch (IOException e) { log.error(e.getMessage(), e); } finally { FileUtil.freedMappedByteBuffer(mappedByteBuffer); FileUtil.close(fileChannel); FileUtil.close(tempRaf); } return false; } }
c、文件操作核心模板類代碼
@Slf4j public abstract class SliceUploadTemplate implements SliceUploadStrategy { public abstract boolean upload(FileUploadRequestDTO param); protected File createTmpFile(FileUploadRequestDTO param) { FilePathUtil filePathUtil = SpringContextHolder.getBean(FilePathUtil.class); param.setPath(FileUtil.withoutHeadAndTailDiagonal(param.getPath())); String fileName = param.getFile().getOriginalFilename(); String uploadDirPath = filePathUtil.getPath(param); String tempFileName = fileName + "_tmp"; File tmpDir = new File(uploadDirPath); File tmpFile = new File(uploadDirPath, tempFileName); if (!tmpDir.exists()) { tmpDir.mkdirs(); } return tmpFile; } @Override public FileUploadDTO sliceUpload(FileUploadRequestDTO param) { boolean isOk = this.upload(param); if (isOk) { File tmpFile = this.createTmpFile(param); FileUploadDTO fileUploadDTO = this.saveAndFileUploadDTO(param.getFile().getOriginalFilename(), tmpFile); return fileUploadDTO; } String md5 = FileMD5Util.getFileMD5(param.getFile()); Map<Integer, String> map = new HashMap<>(); map.put(param.getChunk(), md5); return FileUploadDTO.builder().chunkMd5Info(map).build(); } /** * 檢查并修改文件上傳進度 */ public boolean checkAndSetUploadProgress(FileUploadRequestDTO param, String uploadDirPath) { String fileName = param.getFile().getOriginalFilename(); File confFile = new File(uploadDirPath, fileName + ".conf"); byte isComplete = 0; RandomAccessFile accessConfFile = null; try { accessConfFile = new RandomAccessFile(confFile, "rw"); //把該分段標記為 true 表示完成 System.out.println("set part " + param.getChunk() + " complete"); //創建conf文件文件長度為總分片數,每上傳一個分塊即向conf文件中寫入一個127,那么沒上傳的位置就是默認0,已上傳的就是Byte.MAX_VALUE 127 accessConfFile.setLength(param.getChunks()); accessConfFile.seek(param.getChunk()); accessConfFile.write(Byte.MAX_VALUE); //completeList 檢查是否全部完成,如果數組里是否全部都是127(全部分片都成功上傳) byte[] completeList = FileUtils.readFileToByteArray(confFile); isComplete = Byte.MAX_VALUE; for (int i = 0; i < completeList.length && isComplete == Byte.MAX_VALUE; i++) { //與運算, 如果有部分沒有完成則 isComplete 不是 Byte.MAX_VALUE isComplete = (byte) (isComplete & completeList[i]); System.out.println("check part " + i + " complete?:" + completeList[i]); } } catch (IOException e) { log.error(e.getMessage(), e); } finally { FileUtil.close(accessConfFile); } boolean isOk = setUploadProgress2Redis(param, uploadDirPath, fileName, confFile, isComplete); return isOk; } /** * 把上傳進度信息存進redis */ private boolean setUploadProgress2Redis(FileUploadRequestDTO param, String uploadDirPath, String fileName, File confFile, byte isComplete) { RedisUtil redisUtil = SpringContextHolder.getBean(RedisUtil.class); if (isComplete == Byte.MAX_VALUE) { redisUtil.hset(FileConstant.FILE_UPLOAD_STATUS, param.getMd5(), "true"); redisUtil.del(FileConstant.FILE_MD5_KEY + param.getMd5()); confFile.delete(); return true; } else { if (!redisUtil.hHasKey(FileConstant.FILE_UPLOAD_STATUS, param.getMd5())) { redisUtil.hset(FileConstant.FILE_UPLOAD_STATUS, param.getMd5(), "false"); redisUtil.set(FileConstant.FILE_MD5_KEY + param.getMd5(), uploadDirPath + FileConstant.FILE_SEPARATORCHAR + fileName + ".conf"); } return false; } } /** * 保存文件操作 */ public FileUploadDTO saveAndFileUploadDTO(String fileName, File tmpFile) { FileUploadDTO fileUploadDTO = null; try { fileUploadDTO = renameFile(tmpFile, fileName); if (fileUploadDTO.isUploadComplete()) { System.out .println("upload complete !!" + fileUploadDTO.isUploadComplete() + " name=" + fileName); //TODO 保存文件信息到數據庫 } } catch (Exception e) { log.error(e.getMessage(), e); } finally { } return fileUploadDTO; } /** * 文件重命名 * * @param toBeRenamed 將要修改名字的文件 * @param toFileNewName 新的名字 */ private FileUploadDTO renameFile(File toBeRenamed, String toFileNewName) { //檢查要重命名的文件是否存在,是否是文件 FileUploadDTO fileUploadDTO = new FileUploadDTO(); if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) { log.info("File does not exist: {}", toBeRenamed.getName()); fileUploadDTO.setUploadComplete(false); return fileUploadDTO; } String ext = FileUtil.getExtension(toFileNewName); String p = toBeRenamed.getParent(); String filePath = p + FileConstant.FILE_SEPARATORCHAR + toFileNewName; File newnewFile = new File(filePath); //修改文件名 boolean uploadFlag = toBeRenamed.renameTo(newFile); fileUploadDTO.setMtime(DateUtil.getCurrentTimeStamp()); fileUploadDTO.setUploadComplete(uploadFlag); fileUploadDTO.setPath(filePath); fileUploadDTO.setSize(newFile.length()); fileUploadDTO.setFileExt(ext); fileUploadDTO.setFileId(toFileNewName); return fileUploadDTO; } }
“如何使用大文件上傳:秒傳、斷點續傳、分片上傳方法”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。