断点续传解决方案

断点续传解决方案

口水话介绍

对于上传和下载文件,虽然http没有限制文件的大小,但是受制于环境影响因素可能造成上传或者下载中断,为了避免重新下载或者上传所带来的用户砸电脑行为的发生,一般我们会对大文件的上传和下载才去断点续传的方案

上传流程:

  1. 上传前先将文件分成块

  2. 一块一块的上传,上传中断后重新上传,已上传的分块则不用再次上传

  3. 各分块全部上传完成,最后合并文件

下载流程同理

Java代码演示文件的分块与合并

  • 文件的分块:大致思路

    1. 获取源文件的大小

    2. 设定一个单个分块的大小

    3. 从源文件中读取数据依次向分块中写入数据

 @Test
 public void ChunkTest() throws IOException {
     File sourceFile = new File("F:\\mySpace\\myavi\\merge.mp4");
    
     String chunkPath = "F:\\mySpace\\myavi\\chunk\\";
     File chunkFolder = new File(chunkPath);
     if(!chunkFolder.exists()){
         chunkFolder.mkdirs();
     } 
     
     //分块大小1兆
     long chunkSize = 1024*1024*1;
     //分块数量:文件大小 / 单个分块的大小,我们设置的1兆,对其向上取整
     long chunkNum = (long) Math.ceil(sourceFile.length() * 1.0 / chunkSize );
     if(chunkNum<=0){
         chunkNum = 1;
     }
     //缓冲区大小
     byte[] b = new byte[1024];
     //使用RandomAccessFile访问文件
     RandomAccessFile raf_read = new RandomAccessFile(sourceFile, "r");
     //分块
     for(int i=0;i<chunkNum;i++){
         //创建分块文件
         File file = new File(chunkPath+i);
         boolean newFile = file.createNewFile();
         if(newFile){
             //向分块文件中写数据
             RandomAccessFile raf_write = new RandomAccessFile(file, "rw");
             int len = -1;
             while((len = raf_read.read(b))!=-1){
                 raf_write.write(b,0,len);
                 if(file.length()>chunkSize){
                     break;
                 }
             } 
             raf_write.close();
         }
     } 
     raf_read.close();
 }
  • 文件的合并:大致思路

    1. 这里需要注意的就是:分块与合并的顺序必须一致

@Test
 public void mergeTest() {
     try {
         //待合并的块文件目录,其下的所有文件采集
         File chunkFoler = new File("F:\\mySpace\\myavi\\chunk\\");
         File[] chunkFileArray = chunkFoler.listFiles();
         
         //合并结果文件的创建
         File mergeFile = new File("F:\\mySpace\\myavi\\chunk\\merge.mp4");
         if (mergeFile.exists()){
             mergeFile.delete();
         }
         mergeFile.createNewFile();
             
         //创建读写文件对象
         RandomAccessFile write = new RandomAccessFile(mergeFile, "rw");
         //指针指向文件顶端
         write.seek(0);
         //创建缓冲区
         byte[] b = new byte[1024];
         
         //因为我们分块的时候是按照1 2 3 4 这样的顺序分的,所以合并的时候也要按照这个顺序来
         //但是我们的listFiles()得到的所有的文件是没有顺序的,是乱的,所以需要排序
         List<File> fileList = Arrays.asList(chunkFileArray);
         Collections.sort(fileList, new Comparator<File>() {
             @Override
             public int compare(File o1, File o2) {
                 if (Integer.parseInt(o1.getName()) < Integer.parseInt(o2.getName())){
                     return -1;
                 }
                 return 1;
             }
         });
         for (File file : fileList) {
             int len = -1;
             RandomAccessFile read = new RandomAccessFile(file, "rw");
             while ((len = read.read(b)) != -1){
                 write.write(b,0,len);
             }
         } 
     }catch (Exception e){
         e.printStackTrace();
     }
 }

Web Uploader + 后端思路

这次我们使用Html5来演示分块上传,Web Uploader完成大文件上传功能演示

官网:http://fexteam.gz01.bdysite.com/webuploader/

  • 上传流程:

勾子方法

webupload中提供了很多的勾子方法,比如:

before-send-file

  • 在开始对文件分块儿之前调用,可以做一些上传文件前的准备工作,比如检查文件目录是否创建完成等。

before-send

  • 在上传文件分块之前调用此方法,可以请求服务端检查分块是否存在,如果已存在则此分块儿不再上传。

after-send-file

  • 在所有分块上传完成后触发,可以请求服务端合并分块文件。

后端思路

业务层代码我就不上了,我就写个思路:

首先我们理一下思路,Webuploader在上传过程会调用四个API接口,他们分别是:

  • 文件上传前的注册

  • 分块文件上传前的分块检查

  • 分块文件的上传

  • 上传完成,通知合并,以及后续保存数据到数据、删除分块数据等...

解释一下:

  • 我们的文件储存方式的路径分三级目录的,根据文件MD5值确定他的资源存放路径

  • 比如MD5值为:c5b1545123343

  • 那么该资源的第一级目录为Md5的第一个字符:c

  • 第二级目录为Md5的第二个字符:5

  • 第三级目录为MD5的值:c5b1545123343

  • 第一个模块:文件上传前的注册和相关资源文件目录的初始化

根据MD5的值去查询该路径下是否有资源

根据MD5去数据查询是否有资源数据

在都没有的情况下,创建我们的资源三级目录

  • 第二个模块:分块上传前检查分块文件是否存在

根据MD5得到相应的资源存放路径

三级目录 + 待检查的分块id信息,得到具体的分块文件数据

这里我们分块都是以 1、2、3这样的方式上传的的,核实待上传分块是否已经存在

  • 第三个模块:分块文件上传

根据MD5得到三级目录,根据分块数据id得到文件名称,确定资源的绝对路径

然后就是流的:IOUtils.copy(inputStream, fileOutputStream); 写入数据

  • 第四个模块:分块数据的合并已经后续其他操作

根据MD5得带确定的资源存放路径,获取该路径下的所有分块数据:File[ ]

获取到的所有分块并不是按照我们想要的顺序排列的,合成会失败

转成集合,根据名字(1、2、3...),将文件排序

创建合成后的文件的路径以及名称

然后就是文件的合并,可以参考上面我写的Demo代码

合并后我们会对这个合并文件的MD5和参数MD5再做一个校验:

if (fileMd5.equals(DigestUtils.md5Hex(new FileInputStream(mergeFilePath))))

然后就是将相关数据保存到数据库,以及删除分块文件、通过MQ异步对该视频进行编码,生成基于HLS协议的近实时流方式播放的视频文件(FFmpeg ?生成m3u8 / ts)...

转载于:https://www.cnblogs.com/msi-chen/p/11432579.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值