如何使用大文件上传:秒传、断点续传、分片上传方法

技术如何使用大文件上传:秒传、断点续传、分片上传方法本篇内容介绍了“如何使用大文件上传:秒传、断点续传、分片上传方法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这

本文介绍了“如何使用上传大文件的方法:二次传输、断点续传、片段上传”的相关知识。很多人在实际案例的操作中会遇到这样的困难。让边肖带领你学习如何处理这些情况。希望大家认真阅读,学点东西!

秒传

1、什么是秒传

一般来说,当你上传要上传的东西时,服务器会先做MD5验证。如果服务器上有同样的东西,它会直接给你一个新地址。事实上,你下载的都是服务器上的同一个文件。如果想秒传,其实只要改MD5就行了,只需要修改文件本身(不能改名字)。例如,如果您在文本文件中添加几个单词,MD5将会改变,并且不会在几秒钟内通过。

2、本文实现的秒传核心逻辑

A.使用redis的set方法存储文件上传状态,其中key为文件上传的md5,value为上传是否完成的标志位。

b、当标志位为真表示上传已经完成,如果此时上传的是同一文件,则进入第二传输逻辑。如果标志位为假,则表示上传尚未完成。此时需要调用set方法保存块号文件记录的路径,key为上传的文件md5添加固定前缀,value为块号文件记录的路径。

分片上传

1.什么是分片上传

分段上传是指将要上传的文件按照一定的大小划分成多个数据块(我们称之为Part)单独上传。上传后,服务器会将所有上传的文件汇总整合成原始文件。

2.分片上传的场景

1.上传大文件。

2.网络环境不好,有需要重传风险的场景。

超文本传送协议(Hyper Text Transport Protocol的缩写)

1、什么是断点续传

断点续传是在下载或上传时人为地将下载或上传任务(一个文件或一个压缩包)分成几个部分,每个部分由一个线程上传或下载。如果出现网络故障,您可以从已经上传或下载的部分继续上传或下载未完成的部分,而不必从头开始上传或下载。本文的断点续传主要针对断点上传场景。

2、应用场景

断点续传可以看作是片段上传的衍生,所以可以使用片段上传场景,也可以使用断点续传。

3、实现断点续传的核心逻辑

在片段上传过程中,如果因为系统崩溃或网络中断等异常因素导致上传中断,客户端需要记录上传进度。以后支持再次上传时,可以从上次上传中断的地方继续上传。

为了避免上传后删除客户端的进度数据而导致从头开始重新上传的问题,服务器还可以提供相应的接口,方便客户端查询上传的分片数据,让客户端知道上传的分片数据,从下一个分片数据继续上传。

4、实现流程步骤

一、方案一、常规步骤。

将待上传的文件按照一定的划分规则划分为大小相同的数据块;

初始化一个片段上传任务,并返回该片段上传的唯一标识符;

按照一定的策略(串行或并行)发送每个分片的数据块;

发送后,服务器判断数据上传是否完成,如果完成,则合成数据块,得到原始文件。

b、方案二、本文实现的步骤。

前端(客户端)需要按照固定的大小对文件进行切片,后端(服务器)应该用切片序列号和大小进行请求。

在服务器上创建一个conf文件来记录块的位置。conf文件的长度是块的总数。上传的每个块都用127写入,因此未上传的位置默认为0,上传的位置为字节。MAX_VALUE 127(这一步是实现断点续传和二次传输的核心步骤)。

服务器根据请求数据中给定的片段号和每个片段的块大小(片段大小固定且相同)计算起始位置,并将其与读取的文件片段数据一起写入文件。

5、分片上传/断点上传代码实现

A.前端使用百度提供的网络上传器插件进行切片。本文主要介绍了服务器代码的实现,以及如何对网络上传器进行分片。具体实现可以在下面的链接:中找到。

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;      }    }

“如何使用大文件上传:秒传、断点续传、分片上传方法”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/37470.html

(0)

相关推荐

  • KEGG Genome数据库的原理是什么

    技术KEGG Genome数据库的原理是什么这期内容当中小编将会给大家带来有关KEGG Genome数据库的原理是什么,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。kegg Genom

    攻略 2021年12月2日
  • 12 请求与响应

    技术12 请求与响应 12 请求与响应1.请求Request# 请求对象
    # from rest_framework.request import Requestdef __init__(self, r

    礼包 2021年12月23日
  • 如何执行系统监控工具dstat

    技术如何执行系统监控工具dstat如何执行系统监控工具dstat,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。在监控方向,推荐一个工具,dstatdstat

    攻略 2021年12月9日
  • 怎样结合Jexus+Kestrel 部署asp.net core生产环境

    技术怎样结合Jexus+Kestrel 部署asp.net core生产环境本篇文章为大家展示了怎样结合Jexus+Kestrel 部署asp.net core生产环境,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过

    攻略 2021年11月19日
  • 如何创建一个django项目

    技术如何创建一个django项目如何创建一个django项目,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。django 非常强大,尤其是dj

    攻略 2021年10月21日
  • OpenFeign服务调用时携带Token

    技术OpenFeign服务调用时携带Token OpenFeign服务调用时携带TokenOpenFeign服务调用时携带Token:场景:众所周知,OpenFigen主要的作用是替我们发送Http请求

    礼包 2021年12月6日