自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

大文件上傳:秒傳、斷點(diǎn)續(xù)傳、分片上傳

開發(fā) 后端
當(dāng)文件上傳到一半中斷后,繼續(xù)上傳卻只能重頭開始上傳,這種讓人不爽的體驗(yàn)。那有沒有比較好的上傳體驗(yàn)?zāi)?,答案有的,就是下邊要介紹的幾種上傳方式。

[[376196]]

 前言

文件上傳是一個(gè)老生常談的話題了,在文件相對比較小的情況下,可以直接把文件轉(zhuǎn)化為字節(jié)流上傳到服務(wù)器,但在文件比較大的情況下,用普通的方式進(jìn)行上傳,這可不是一個(gè)好的辦法,畢竟很少有人會(huì)忍受,當(dāng)文件上傳到一半中斷后,繼續(xù)上傳卻只能重頭開始上傳,這種讓人不爽的體驗(yàn)。那有沒有比較好的上傳體驗(yàn)?zāi)兀鸢赣械?,就是下邊要介紹的幾種上傳方式

詳細(xì)教程

秒傳

1、什么是秒傳

通俗的說,你把要上傳的東西上傳,服務(wù)器會(huì)先做MD5校驗(yàn),如果服務(wù)器上有一樣的東西,它就直接給你個(gè)新地址,其實(shí)你下載的都是服務(wù)器上的同一個(gè)文件,想要不秒傳,其實(shí)只要讓MD5改變,就是對文件本身做一下修改(改名字不行),例如一個(gè)文本文件,你多加幾個(gè)字,MD5就變了,就不會(huì)秒傳了.

2、本文實(shí)現(xiàn)的秒傳核心邏輯

a、利用redis的set方法存放文件上傳狀態(tài),其中key為文件上傳的md5,value為是否上傳完成的標(biāo)志位,

b、當(dāng)標(biāo)志位true為上傳已經(jīng)完成,此時(shí)如果有相同文件上傳,則進(jìn)入秒傳邏輯。如果標(biāo)志位為false,則說明還沒上傳完成,此時(shí)需要在調(diào)用set的方法,保存塊號文件記錄的路徑,其中key為上傳文件md5加一個(gè)固定前綴,value為塊號文件記錄路徑

分片上傳

1.什么是分片上傳

分片上傳,就是將所要上傳的文件,按照一定的大小,將整個(gè)文件分隔成多個(gè)數(shù)據(jù)塊(我們稱之為Part)來進(jìn)行分別上傳,上傳完之后再由服務(wù)端對所有上傳的文件進(jìn)行匯總整合成原始的文件。

2.分片上傳的場景

1.大文件上傳

2.網(wǎng)絡(luò)環(huán)境環(huán)境不好,存在需要重傳風(fēng)險(xiǎn)的場景

斷點(diǎn)續(xù)傳

1、什么是斷點(diǎn)續(xù)傳

斷點(diǎn)續(xù)傳是在下載或上傳時(shí),將下載或上傳任務(wù)(一個(gè)文件或一個(gè)壓縮包)人為的劃分為幾個(gè)部分,每一個(gè)部分采用一個(gè)線程進(jìn)行上傳或下載,如果碰到網(wǎng)絡(luò)故障,可以從已經(jīng)上傳或下載的部分開始繼續(xù)上傳或者下載未完成的部分,而沒有必要從頭開始上傳或者下載。本文的斷點(diǎn)續(xù)傳主要是針對斷點(diǎn)上傳場景。

2、應(yīng)用場景

斷點(diǎn)續(xù)傳可以看成是分片上傳的一個(gè)衍生,因此可以使用分片上傳的場景,都可以使用斷點(diǎn)續(xù)傳。

3、實(shí)現(xiàn)斷點(diǎn)續(xù)傳的核心邏輯

在分片上傳的過程中,如果因?yàn)橄到y(tǒng)崩潰或者網(wǎng)絡(luò)中斷等異常因素導(dǎo)致上傳中斷,這時(shí)候客戶端需要記錄上傳的進(jìn)度。在之后支持再次上傳時(shí),可以繼續(xù)從上次上傳中斷的地方進(jìn)行繼續(xù)上傳。

為了避免客戶端在上傳之后的進(jìn)度數(shù)據(jù)被刪除而導(dǎo)致重新開始從頭上傳的問題,服務(wù)端也可以提供相應(yīng)的接口便于客戶端對已經(jīng)上傳的分片數(shù)據(jù)進(jìn)行查詢,從而使客戶端知道已經(jīng)上傳的分片數(shù)據(jù),從而從下一個(gè)分片數(shù)據(jù)開始繼續(xù)上傳。

4、實(shí)現(xiàn)流程步驟

a、方案一,常規(guī)步驟

  •  將需要上傳的文件按照一定的分割規(guī)則,分割成相同大小的數(shù)據(jù)塊;
  •  初始化一個(gè)分片上傳任務(wù),返回本次分片上傳唯一標(biāo)識;
  •  按照一定的策略(串行或并行)發(fā)送各個(gè)分片數(shù)據(jù)塊;
  •  發(fā)送完成后,服務(wù)端根據(jù)判斷數(shù)據(jù)上傳是否完整,如果完整,則進(jìn)行數(shù)據(jù)塊合成得到原始文件。

b、方案二、本文實(shí)現(xiàn)的步驟

  •  前端(客戶端)需要根據(jù)固定大小對文件進(jìn)行分片,請求后端(服務(wù)端)時(shí)要帶上分片序號和大小
  •  服務(wù)端創(chuàng)建conf文件用來記錄分塊位置,conf文件長度為總分片數(shù),每上傳一個(gè)分塊即向conf文件中寫入一個(gè)127,那么沒上傳的位置就是默認(rèn)的0,已上傳的就是Byte.MAX_VALUE 127(這步是實(shí)現(xiàn)斷點(diǎn)續(xù)傳和秒傳的核心步驟)
  • 服務(wù)器按照請求數(shù)據(jù)中給的分片序號和每片分塊大?。ǚ制笮∈枪潭ㄇ乙粯拥模┧愠鲩_始位置,與讀取到的文件片段數(shù)據(jù),寫入文件。

5、分片上傳/斷點(diǎn)上傳代碼實(shí)現(xiàn)

a、前端采用百度提供的webuploader的插件,進(jìn)行分片。因本文主要介紹服務(wù)端代碼實(shí)現(xiàn),webuploader如何進(jìn)行分片,具體實(shí)現(xiàn)可以查看如下鏈接:

http://fex.baidu.com/webuploader/getting-started.html

b、后端用兩種方式實(shí)現(xiàn)文件寫入,一種是用RandomAccessFile,如果對RandomAccessFile不熟悉的朋友,可以查看如下鏈接:

https://blog.csdn.net/dimudan2015/article/details/81910690

另一種是使用MappedByteBuffer,對MappedByteBuffer不熟悉的朋友,可以查看如下鏈接進(jìn)行了解:

https://www.jianshu.com/p/f90866dcbffc

后端進(jìn)行寫入操作的核心代碼

a、RandomAccessFile實(shí)現(xiàn)方式 

  1. @UploadMode(mode = UploadModeEnum.RANDOM_ACCESS)    
  2. @Slf4j    
  3. public class RandomAccessUploadStrategy extends SliceUploadTemplate {  
  4.   @Autowired    
  5.   private FilePathUtil filePathUtil;     
  6.    @Value("${upload.chunkSize}")    
  7.   private long defaultChunkSize;    
  8.      @Override    
  9.   public boolean upload(FileUploadRequestDTO param) {    
  10.     RandomAccessFile accessTmpFile = null;   
  11.      try {    
  12.       String uploadDirPath = filePathUtil.getPath(param);    
  13.       File tmpFile = super.createTmpFile(param);    
  14.       accessTmpFile = new RandomAccessFile(tmpFile, "rw");    
  15.       //這個(gè)必須與前端設(shè)定的值一致    
  16.       long chunkSize = Objects.isNull(param.getChunkSize()) ? defaultChunkSize * 1024 * 1024    
  17.           : param.getChunkSize();   
  18.        long offset = chunkSize * param.getChunk();    
  19.       //定位到該分片的偏移量    
  20.       accessTmpFile.seek(offset);    
  21.       //寫入該分片數(shù)據(jù)    
  22.       accessTmpFile.write(param.getFile().getBytes());    
  23.       boolean isOk = super.checkAndSetUploadProgress(param, uploadDirPath);    
  24.       return isOk;    
  25.     } catch (IOException e) {    
  26.       log.error(e.getMessage(), e);  
  27.      } finally {    
  28.       FileUtil.close(accessTmpFile);   
  29.     }    
  30.    return false;    
  31.   }     
  32. }   

b、MappedByteBuffer實(shí)現(xiàn)方式 

  1. @UploadMode(mode = UploadModeEnum.MAPPED_BYTEBUFFER)    
  2. @Slf4j    
  3. public class MappedByteBufferUploadStrategy extends SliceUploadTemplate {    
  4.   @Autowired   
  5.    private FilePathUtil filePathUtil;    
  6.   @Value("${upload.chunkSize}")    
  7.   private long defaultChunkSize;      
  8.   @Override    
  9.   public boolean upload(FileUploadRequestDTO param) {    
  10.     RandomAccessFile tempRaf = null;    
  11.     FileChannel fileChannel = null;    
  12.     MappedByteBuffer mappedByteBuffer = null;    
  13.     try {    
  14.       String uploadDirPath = filePathUtil.getPath(param);    
  15.       File tmpFile = super.createTmpFile(param);    
  16.       tempRaf = new RandomAccessFile(tmpFile, "rw");    
  17.       fileChannel = tempRaf.getChannel();   
  18.        long chunkSize = Objects.isNull(param.getChunkSize()) ? defaultChunkSize * 1024 * 1024   
  19.           : param.getChunkSize();    
  20.       //寫入該分片數(shù)據(jù)    
  21.       long offset = chunkSize * param.getChunk();    
  22.       byte[] fileData = param.getFile().getBytes();    
  23.       mappedByteBuffer = fileChannel    
  24. .map(FileChannel.MapMode.READ_WRITE, offset, fileData.length); 
  25.        mappedByteBuffer.put(fileData);    
  26.       boolean isOk = super.checkAndSetUploadProgress(param, uploadDirPath);    
  27.       return isOk;   
  28.      } catch (IOException e) {    
  29.       log.error(e.getMessage(), e);    
  30.     } finally {   
  31.        FileUtil.freedMappedByteBuffer(mappedByteBuffer);    
  32.       FileUtil.close(fileChannel);    
  33.       FileUtil.close(tempRaf);   
  34.      }   
  35.      return false;    
  36.   }     
  37. }   

c、文件操作核心模板類代碼 

  1. @Slf4j    
  2. public abstract class SliceUploadTemplate implements SliceUploadStrategy {      
  3.   public abstract boolean upload(FileUploadRequestDTO param);   
  4.    protected File createTmpFile(FileUploadRequestDTO param) {   
  5.      FilePathUtil filePathUtil = SpringContextHolder.getBean(FilePathUtil.class);    
  6.     param.setPath(FileUtil.withoutHeadAndTailDiagonal(param.getPath()));    
  7.     String fileName = param.getFile().getOriginalFilename();    
  8.     String uploadDirPath = filePathUtil.getPath(param);    
  9.     String tempFileName = fileName + "_tmp";    
  10.     File tmpDir = new File(uploadDirPath);    
  11.     File tmpFile = new File(uploadDirPath, tempFileName);   
  12.      if (!tmpDir.exists()) {    
  13.       tmpDir.mkdirs();    
  14.     }    
  15.     return tmpFile;   
  16.   }    
  17.    @Override    
  18.   public FileUploadDTO sliceUpload(FileUploadRequestDTO param) {  
  19.      boolean isOk = this.upload(param);    
  20.     if (isOk) {    
  21.       File tmpFile = this.createTmpFile(param);    
  22.       FileUploadDTO fileUploadDTO = this.saveAndFileUploadDTO(param.getFile().getOriginalFilename(), tmpFile);    
  23.       return fileUploadDTO;    
  24.     }    
  25.     String md5 = FileMD5Util.getFileMD5(param.getFile());   
  26.      Map<Integer, String> map = new HashMap<>();    
  27.     map.put(param.getChunk(), md5);    
  28.     return FileUploadDTO.builder().chunkMd5Info(map).build();   
  29.   }    
  30.    /**    
  31.    * 檢查并修改文件上傳進(jìn)度    
  32.    */    
  33.   public boolean checkAndSetUploadProgress(FileUploadRequestDTO param, String uploadDirPath) {    
  34.      String fileName = param.getFile().getOriginalFilename();    
  35.     File confFile = new File(uploadDirPath, fileName + ".conf");    
  36.     byte isComplete = 0;    
  37.     RandomAccessFile accessConfFile = null;    
  38.     try {    
  39.       accessConfFile = new RandomAccessFile(confFile, "rw");    
  40.       //把該分段標(biāo)記為 true 表示完成    
  41.       System.out.println("set part " + param.getChunk() + " complete");    
  42.       //創(chuàng)建conf文件文件長度為總分片數(shù),每上傳一個(gè)分塊即向conf文件中寫入一個(gè)127,那么沒上傳的位置就是默認(rèn)0,已上傳的就是Byte.MAX_VALUE 127    
  43.       accessConfFile.setLength(param.getChunks());    
  44.       accessConfFile.seek(param.getChunk());    
  45.       accessConfFile.write(Byte.MAX_VALUE);    
  46.        //completeList 檢查是否全部完成,如果數(shù)組里是否全部都是127(全部分片都成功上傳)    
  47.       byte[] completeList = FileUtils.readFileToByteArray(confFile);    
  48.       isComplete = Byte.MAX_VALUE;    
  49.       for (int i = 0; i < completeList.length && isComplete == Byte.MAX_VALUE; i++) {    
  50.         //與運(yùn)算, 如果有部分沒有完成則 isComplete 不是 Byte.MAX_VALUE    
  51.         isComplete = (byte) (isComplete & completeList[i]);    
  52.         System.out.println("check part " + i + " complete?:" + completeList[i]);    
  53.       }   
  54.      } catch (IOException e) {    
  55.       log.error(e.getMessage(), e);   
  56.     } finally {    
  57.       FileUtil.close(accessConfFile);    
  58.     }    
  59.  boolean isOk = setUploadProgress2Redis(param, uploadDirPath, fileName, confFile, isComplete);    
  60.     return isOk;    
  61.   }    
  62.    /**    
  63.    * 把上傳進(jìn)度信息存進(jìn)redis    
  64.    */    
  65.   private boolean setUploadProgress2Redis(FileUploadRequestDTO param, String uploadDirPath,  
  66.        String fileName, File confFile, byte isComplete) {   
  67.      RedisUtil redisUtil = SpringContextHolder.getBean(RedisUtil.class);    
  68.     if (isComplete == Byte.MAX_VALUE) {    
  69.       redisUtil.hset(FileConstant.FILE_UPLOAD_STATUS, param.getMd5(), "true");    
  70.       redisUtil.del(FileConstant.FILE_MD5_KEY + param.getMd5());    
  71.       confFile.delete();    
  72.       return true;    
  73.     } else {    
  74.       if (!redisUtil.hHasKey(FileConstant.FILE_UPLOAD_STATUS, param.getMd5())) {    
  75.         redisUtil.hset(FileConstant.FILE_UPLOAD_STATUS, param.getMd5(), "false");    
  76.         redisUtil.set(FileConstant.FILE_MD5_KEY + param.getMd5(),    
  77.             uploadDirPath + FileConstant.FILE_SEPARATORCHAR + fileName + ".conf");    
  78.       }    
  79.        return false;    
  80.     }    
  81.   }    
  82. /**    
  83.    * 保存文件操作    
  84.    */    
  85.   public FileUploadDTO saveAndFileUploadDTO(String fileName, File tmpFile) {    
  86.      FileUploadDTO fileUploadDTO = null;   
  87.      try {    
  88.        fileUploadDTO = renameFile(tmpFile, fileName);    
  89.       if (fileUploadDTO.isUploadComplete()) {    
  90.         System.out    
  91.             .println("upload complete !!" + fileUploadDTO.isUploadComplete() + " name=" + fileName);    
  92.         //TODO 保存文件信息到數(shù)據(jù)庫    
  93.        }    
  94.      } catch (Exception e) {    
  95.       log.error(e.getMessage(), e);    
  96.     } finally {    
  97.      }    
  98.     return fileUploadDTO;    
  99.   }    
  100. /**    
  101.    * 文件重命名    
  102.    *    
  103.    * @param toBeRenamed 將要修改名字的文件    
  104.    * @param toFileNewName 新的名字    
  105.    */    
  106.   private FileUploadDTO renameFile(File toBeRenamed, String toFileNewName) {    
  107.     //檢查要重命名的文件是否存在,是否是文件    
  108.     FileUploadDTO fileUploadDTO = new FileUploadDTO();    
  109.     if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {   
  110.       log.info("File does not exist: {}", toBeRenamed.getName());    
  111.       fileUploadDTO.setUploadComplete(false);    
  112.       return fileUploadDTO;    
  113.     }    
  114.     String ext = FileUtil.getExtension(toFileNewName);    
  115.     String p = toBeRenamed.getParent();    
  116.     String filePath = p + FileConstant.FILE_SEPARATORCHAR + toFileNewName;    
  117.     File newnewFile = new File(filePath);    
  118.     //修改文件名    
  119.     boolean uploadFlag = toBeRenamed.renameTo(newFile);   
  120.      fileUploadDTO.setMtime(DateUtil.getCurrentTimeStamp());    
  121.     fileUploadDTO.setUploadComplete(uploadFlag);    
  122.     fileUploadDTO.setPath(filePath);    
  123.     fileUploadDTO.setSize(newFile.length());    
  124.     fileUploadDTO.setFileExt(ext);    
  125.     fileUploadDTO.setFileId(toFileNewName);   
  126.      return fileUploadDTO;    
  127.   }    
  128. }   

總結(jié)

在實(shí)現(xiàn)分片上傳的過程,需要前端和后端配合,比如前后端的上傳塊號的文件大小,前后端必須得要一致,否則上傳就會(huì)有問題。其次文件相關(guān)操作正常都是要搭建一個(gè)文件服務(wù)器的,比如使用fastdfs、hdfs等。

本示例代碼在電腦配置為4核內(nèi)存8G情況下,上傳24G大小的文件,上傳時(shí)間需要30多分鐘,主要時(shí)間耗費(fèi)在前端的md5值計(jì)算,后端寫入的速度還是比較快。如果項(xiàng)目組覺得自建文件服務(wù)器太花費(fèi)時(shí)間,且項(xiàng)目的需求僅僅只是上傳下載,那么推薦使用阿里的oss服務(wù)器,其介紹可以查看官網(wǎng):

https://help.aliyun.com/product/31815.html

阿里的oss它本質(zhì)是一個(gè)對象存儲(chǔ)服務(wù)器,而非文件服務(wù)器,因此如果有涉及到大量刪除或者修改文件的需求,oss可能就不是一個(gè)好的選擇。

文末提供一個(gè)oss表單上傳的鏈接demo,通過oss表單上傳,可以直接從前端把文件上傳到oss服務(wù)器,把上傳的壓力都推給oss服務(wù)器:

https://www.cnblogs.com/ossteam/p/4942227.html 

 

責(zé)任編輯:龐桂玉 來源: Java知音
相關(guān)推薦

2022-06-15 09:01:45

大文件秒傳分片上傳

2024-11-12 09:54:23

2022-08-05 08:40:37

架構(gòu)

2020-04-02 20:07:17

前端vuenote.js

2021-01-18 05:19:11

數(shù)字指紋

2024-07-02 10:18:18

2017-08-08 08:45:44

前端文件斷點(diǎn)續(xù)傳

2023-03-09 12:04:38

Spring文件校驗(yàn)

2011-03-04 16:41:57

FileZilla

2023-06-20 19:57:13

2013-07-22 14:02:17

iOS開發(fā)ASIHTTPRequ

2025-04-10 08:03:31

Spring系統(tǒng)

2009-08-28 15:38:49

C#實(shí)現(xiàn)斷點(diǎn)續(xù)傳

2013-03-22 14:42:01

OSS開放存儲(chǔ)服務(wù)云計(jì)算

2009-11-16 11:41:19

PHP上傳大文件

2022-06-13 14:06:33

大文件上傳前端

2009-07-21 15:38:31

2017-12-20 15:11:48

iOS緩存文件斷點(diǎn)機(jī)制

2009-07-20 16:09:39

2009-12-07 09:45:23

PHP上傳大文件設(shè)置
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號