当前位置: 首页 > 科技观察

10G大文件,即时传输,断点续传,分割上传

时间:2023-03-18 18:36:31 科技观察

超大文件上传是一个常见的话题。当文件比较小时,可以直接将文件转成字节流上传到服务器。但是在文件比较大的情况下,用正常的方式上传就不是什么好办法了。毕竟,很少有人会容忍他们。一次不愉快的经历。有没有更好的上传体验?答案是肯定的。以下是下面要介绍的几种上传方式。如果服务器上有相同的东西,它会直接给你一个新地址。其实你下载的是服务器上的同一个文件。如果不想秒传,其实只要改了MD5,就需要修改文件本身(改名字不行),比如文本文件,如果加几个多的话,MD5会变,秒传不完。2、本文实现的二次传输的核心逻辑使用redis的set方法存储文件上传状态,其中key是文件上传的md5,value是上传是否完成的标志位,b.当标志位为真时,上传完成。如果此时有相同的文件上传,则进入第二次传输逻辑。如果标志位为false,表示上传还没有完成。这时候需要调用set方法保存块号文件记录的路径,其中key是上传的文件md5加上固定前缀,value是块号文件记录路径分片上传1.什么是部分上传?Part上传就是将要上传的文件按照一定的大小分成多个数据块(我们称之为Part),分别上传。上传完成后,服务器将所有上传的文件汇总整合为原始文件。2.分片上传场景1)、大文件上传2)、网络环境不好有重传风险的场景1.什么是续传?,下载或上传任务(一个文件或压缩包)被人为地分成几个部分,每个部分使用一个线程来上传或下载。如果遇到网络故障,可以从已经上传或下载的部分继续上传或下载未完成的部分,而不必从头开始上传或下载。本文断点续传主要针对断点续传场景。2.应用场景断点续传可以看作是分片上传的衍生,所以断点续传可以用在任何可以使用分片上传的场景。3.实现断点续传的核心逻辑在分片上传的过程中,如果由于系统崩溃或网络中断等异常因素导致上传中断,客户端需要记录上传的进度。稍后支持再次上传时,您可以从上次上传中断的地方继续上传。为了避免客户端上传后的进度数据被删除,重新从头开始上传的问题,服务端也可以提供相应的接口,方便客户端查询上传的分片数据,让客户端知道上传的数据。分片数据,以便从下一个分片数据继续上传。4、实现过程步骤a,方案1,常规步骤将待上传文件按照一定的切分规则切分成大小相同的数据块;初始化一个分片上传任务,返回该分片上传的唯一标识;按照一定的策略(串行或并行)发送每个分片数据块;发送完成后,服务器判断上传的数据是否完整,如果完整则合成数据块得到原始文件。b.方案二、本文实现的步骤前端(client)需要按照固定大小对文件进行分片,请求后端(server)时,必须带上分片号和大小。服务端创建一个conf文件记录blocks的位置,conf文件的长度就是分片的总数,每上传一个block,就往conf文件中写入一个127,那么还没有上传的位置就是默认0,上传的是Byte。resume和二次传输的核心步骤)服务器根据请求数据中给出的段号和每个段的大小(段大小固定且相同)计算起始位置,写入导入文件。5.分片上传/断点上传代码的实现前端采用百度提供的webuploader插件进行分片。因为本文主要介绍服务端代码实现,webuploader如何分片,具体实现可以查看以下链接:http://fex.baidu.com/webuploader/getting-started.htmlb,后端使用了两种方法实现文件写入,一种是使用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公共类RandomAccessUploadStrategy扩展SliceUploadTemplate{@AutowiredprivateFilePathUtilfilePathUtil;@Value("${upload.chunkSize}")privatelongdefaultChunkSize;@Overridepublicbooleanupload(FileUploadRequestDTOparam){RandomAccessFileaccessTmpFile=null;尝试{StringuploadDirPath=)filePathU;文件tmpFile=super.createTmpFile(param);accessTmpFile=newRandomAccessFile(tmpFile,"rw");//这个必须和前端设置的值一致longchunkSize=Objects.isNull(param.getC大块尺寸())?defaultChunkSize*1024*1024:param.getChunkSize();longoffset=chunkSize*param.getChunk();//定位到该片段的偏移量accessTmpFile.seek(offset);//写入该片段数据accessTmpFile.write(param.getFile().getBytes());booleanisOk=super.checkAndSetUploadProgress(param,uploadDirPath);返回正常;}catch(IOExceptione){log.error(e.getMessage(),e);}最后{FileUtil.close(accessTmpFile);}返回假;}b、MappedByteBuffer实现方式@UploadMode(mode=UploadModeEnum.MAPPED_BYTEBUFFER)@Slf4jpublicclassMappedByteBufferUploadStrategyextendsSliceUploadTemplate{@AutowiredprivateFilePathUtilfilePathUtil;@Value("${upload.chunkSize}")privatelongdefaultChunkSize;@Overridepublicbooleanupload(FileUploadRequestDTOparam){RandomAccessFiletempRaf=null;文件通道fileChannel=null;MappedByteBuffermappedByteBuffer=null;特y{StringuploadDirPath=filePathUtil.getPath(param);文件tmpFile=super.createTmpFile(param);tempRaf=newRandomAccessFile(tmpFile,"rw");fileChannel=tempRaf.getChannel();longchunkSize=Objects.isNull(param.getChunkSize())?defaultChunkSize*1024*1024:param.getChunkSize();//写入该切片数据longoffset=chunkSize*param.getChunk();byte[]fileData=param.getFile().getBytes();mappedByteBuffer=fileChannel.map(FileChannel.MapMode.READ_WRITE,offset,fileData.length);mappedByteBuffer.put(文件数据);booleanisOk=super.checkAndSetUploadProgress(param,uploadDirPath);返回正常;}catch(IOExceptione){log.error(e.getMessage(),e);}最后{FileUtil.freedMappedByteBuffer(mappedByteBuffer);FileUtil.close(文件通道);FileUtil.close(tempRaf);}返回假;}}c、文件操作核心模块类代码@Slf4jpublicabstractclassSliceUploadTemplate实现SliceUploadStrategy{publicabstractbooleanupload(FileUploadRequestDTOparam);protectedFilecreateTmpFile(FileUploadRequestDTOparam){FilePathUtilfilePathUtil=SpringContextHolder.getBean(FilePathUtil.class);param.setPath(FileUtil.withoutHeadAndTailDiagonal(param.getPath()));StringfileName=param.getFile().getOriginalFilename();StringuploadDirPath=filePathUtil.getPath(param);字符串tempFileName=文件名+"_tmp";文件tmpDir=新文件(uploadDirPath);文件tmpFile=newFile(uploadDirPath,tempFileName);如果(!tmpDir.exists()){tmpDir.mkdirs();}返回tmpFile;}@OverridepublicFileUploadDTOsliceUpload(FileUploadRequestDTOparam){booleanisOk=this.upload(param);如果(isOk){文件tmpFile=this.createTmpFile(参数);FileUploadDTOfileUploadDTO=this.saveAndFileUploadDTO(param.getFile().getOriginalFilename(),tmp文件);返回文件上传DTO;}Stringmd5=FileMD5Util.getFileMD5(param.getFile());Mapmap=newHashMap<>();map.put(param.getChunk(),md5);返回FileUploadDTO.builder().chunkMd5Info(map).build();}/***检查和修改文件上传进度*/publicbooleancheckAndSetUploadProgress(FileUploadRequestDTOparam,StringuploadDirPath){StringfileName=param.getFile().getOriginalFilename();文件confFile=newFile(uploadDirPath,fileName+".conf");字节完成=0;RandomAccessFileaccessConfFile=null;尝试{accessConfFile=newRandomAccessFile(confFile,"rw");//将此段标记为true表示完成System.out.println("setpart"+param.getChunk()+"complete");//创建一个conf文件,长度为总片数,每上传一个片就写入conf文件A127,则未上传的位置默认为0,已上传的位置是Byte.MAX_VALUE127accessConfFile.setLength(param.getChunks());accessConfFile.seek(param.getChunk());访问配置文件.write(字节.MAX_VALUE);//completeList检查是否全部完成,如果数组全部为127(所有部分上传成功)byte[]completeList=FileUtils.readFileToByteArray(confFile);isComplete=Byte.MAX_VALUE;for(inti=0;i