断点续传解决方案

通常视频文件都比较大,所以对于媒资系统上传文件的需求要满足大文件的上传要求。http协议本身对上传文件大
小没有限制,但是客户的网络环境质量、电脑硬件环境等参差不齐,如果一个大文件快上传完了网断了,电断了没
有上传完成,需要客户重新上传,这是致命的,所以对于大文件上传的要求最基本的是断点续传。


断点续传

断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传,下载未完成的部分,而没有必要从头开始上传下载,断点续传可以提高节省操作时间,提高用户体验性。

 

 

上传流程

1. 上传前把文件分块

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

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

 

文件分块与合并

文件分块的流程如下:

1、获取源文件长度

2、根据设定的分块文件的大小计算出块数

3、从源文件读数据依次向每一个块文件写数据。

 

  @Test
    public void testChunk() throws IOException {
        //文件源目录
        File  sourceFile = new File("D:/develop/video/test.mp4");

        //文件分片下载路径
        String chunkPath = "D:/develop/video/chunk/";

        File fileFolder = new File(chunkPath);

        if (!fileFolder.exists()){
            //文件夹不存在
            fileFolder.mkdir();
        }

        //分块大小
        long chunkSize = 1024*1024*1;

        //分块数量
        long chunkNum = (long) Math.ceil(sourceFile.length() * 1.0 / chunkSize);  // 向上取整   15.1  =  16

        if (chunkNum <= 0){  //文件小,不用分片,默认分1片
            chunkNum = 1;
        }

        //创建缓冲区
        byte[] b = new byte[1024];

        //使用RandomAccessFile访问文件
        RandomAccessFile raf_read = new RandomAccessFile(sourceFile, "r");  // 参数 r 代表这 只读,写会报错

        //分块
        for(int i=0;i<chunkNum;i++){
            //创建分块文件
            File file = new File(chunkPath+i);
             boolean isSuccess = file.createNewFile();
             if (isSuccess){
                 //向分块文件中写数据
                 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、找到要合并的文件并按文件合并的先后进行排序。

2、创建合并文件

3、依次从合并的文件中读取数据向合并文件写入数

 @Test
    public void testMerge() throws IOException {

//        块文件目录
        File chunkFolder = new File("D:/develop/video/chunk/");

        //合并文件
        File mergeFile = new File("D:/develop/video/test1.mp4");

        if (mergeFile.exists()){ //文件存在,那就删除文件
            mergeFile.delete();
        }

        //创建新文件
        mergeFile.createNewFile();

        //用于写文件
        RandomAccessFile raf_write = new RandomAccessFile(mergeFile, "rw");

        //指针指向文件开头处  0
        raf_write.seek(0);

        //缓冲区
        byte[] b = new byte[1024];

        //获取文件列表
        File[] files = chunkFolder.listFiles();

        // 转成集合,便于排序
        List<File> fileList = new ArrayList<File>(Arrays.asList(files));

        //从小到大排序
        Collections.sort(fileList, new Comparator<File>() {
            @Override
            public int compare(File o1, File o2) {
                
                return Integer.parseInt(o1.getName()) < Integer.parseInt(o2.getName()) ? -1 : 1;
            }
        });

        //合并文件
        for (File chunkFile : fileList) {
            //读取每个文件
            RandomAccessFile raf_read = new RandomAccessFile(chunkFile,"rw");

            int len = -1;
            while((len = raf_read.read(b)) != -1){
                raf_write.write(b,0,len);
            }
            raf_read.close();
        }

        raf_write.close();
    }

 

RandomAccessFile类

RandomAccessFile是Java提供用来访问一些保存数据记录的文件的类,可以进行读取操作,也可以进行写入操作,写入的数据则以byte的形式存储;支持随机访问,也就是可以访问文件的任意位置(通过文件指针实现)。

我在网上找了一篇个人觉得比较好的介绍  RandomAccessFile的帖子   点我看帖

 

回到断点续传的话题,常用的断点续传方式有以下几种


1、通过Flash上传,比如SWFupload、Uploadify。

2、安装浏览器插件,变相的pc客户端,用的比较少。

3、Html5

因为html5能给出更好的客户体验,不用下载 flash啊,插件啥的....

使用WebUploader完成大文件上传功能的开发,WebUploader官网地址:

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

 

特性如下:

 

上传流程如下,

 

 

钩子方法

在webuploader中提供很多钩子方法,下边列出一些重要的:

 

1)before-send-file

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

2) before-send

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

3) after-send-file

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

注册钩子方法源代码:

WebUploader.Uploader.register(
    "before‐send‐file":"beforeSendFile",
    "before‐send":"beforeSend",
    "after‐send‐file":"afterSendFile"
}

 

构建WebUploader

使用webUploader前需要创建webUploader对象。

指定上传分块的地址:/api/media/upload/uploadchunk

// 创建uploader对象,配置参数
this.uploader = WebUploader.create({
    swf:"/static/plugins/webuploader/dist/Uploader.swf",//上传文件的flash文件,浏览器不支持h5时启动
    flashserver:"/api/media/upload/uploadchunk",//上传分块的服务端地址,注意跨域问题    
    fileVal:"file",//文件上传域的
    namepick:"#picker",//指定选择文件的按钮容器
    auto:false,//手动触发上传
    disableGlobalDnd:true,//禁掉整个页面的拖拽功能
    chunked:true,// 是否分块上传
    chunkSize:1*1024*1024, // 分块大小(默认5M)
    threads:3, // 开启多个线程(默认3个)
    prepareNextFile:true// 允许在文件传输时提前把下一个文件准备好
})

 

before-send-file

文件开始上传前前端请求服务端准备上传工作

参考源代码如下:

type:"POST",
url:"/api/media/upload/register",
data:{// 文件唯一表示
    fileMd5:this.fileMd5,
    fileName: file.name,
    fileSize:file.size,
    mimetype:file.type,
    fileExt:file.ext
}

 

before-send

上传分块前前端请求服务端校验分块是否存在。

参考源代码如下:

type:"POST",
url:"/api/media/upload/checkchunk",
data:{// 文件唯一表示
    fileMd5:this.fileMd5,
        // 当前分块下标
    chunk:block.chunk,
    // 当前分块大小
    chunkSize:block.end‐block.start
}

after-send-file

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

参考代码如下:

type:"POST",
url:"/api/media/upload/mergechunks",
data:{
    fileMd5:this.fileMd5,
    fileName: file.name,
    fileSize:file.size,
    mimetype:file.type,
    fileExt:file.ext
}

 

 

下面是页面效果

页面代码

<template>
<div><br/>
  操作步骤:<br/>
  1、点击“选择文件”,选择要上传的文件<br/>
  2、点击“开始上传”,开始上传文件<br/>
  3、如需重新上传请重复上边的步骤。<br/><br/>

  <div id="uploader" class="wu-example">
    <div class="btns" style="float:left;padding-right: 20px">
      <div id="picker">选择文件</div>
    </div>
    <div id="ctlBtn" class="webuploader-pick" @click="upload()">开始上传</div>

  </div>
  <!--用来存放文件信息-->
  <div id="thelist" class="uploader-list" >
    <div v-if="uploadFile.id" :id='uploadFile.id'><span>{{uploadFile.name}}</span>&nbsp;<span class='percentage'>{{percentage}}%</span></div>

  </div>
</div>
</template>
<script>
  import $ from '../../../../static/plugins/jquery/dist/jquery.js'
  import webuploader from '../../../../static/plugins/webuploader/dist/webuploader.js'
  import '../../../../static/css/webuploader/webuploader.css'
  export default{
    data(){
      return{
        uploader:{},
        uploadFile:{},
        percentage:0,
        fileMd5:''
      }
    },
    methods:{
      //开始上传
      upload(){
        if(this.uploadFile && this.uploadFile.id){
          this.uploader.upload(this.uploadFile.id);
        }else{
          alert("请选择文件");
        }
      }
    },
    mounted(){
//      var fileMd5;
//      var uploadFile;
      WebUploader.Uploader.register({
          "before-send-file":"beforeSendFile",
          "before-send":"beforeSend",
          "after-send-file":"afterSendFile"
        },{
          beforeSendFile:function(file) {
            // 创建一个deffered,用于通知是否完成操作
            var deferred = WebUploader.Deferred();
            // 计算文件的唯一标识,用于断点续传
            (new WebUploader.Uploader()).md5File(file, 0, 100*1024*1024)
              .then(function(val) {

                this.fileMd5 = val;
                this.uploadFile = file;
//                alert(this.fileMd5 )
                //向服务端请求注册上传文件
                $.ajax(
                  {
                    type:"POST",
                    url:"/api/media/upload/register",
                    data:{
                      // 文件唯一表示
                      fileMd5:this.fileMd5,
                      fileName: file.name,
                      fileSize:file.size,
                      mimetype:file.type,
                      fileExt:file.ext
                    },
                    dataType:"json",
                    success:function(response) {
                      if(response.success) {
                        //alert('上传文件注册成功开始上传');
                        deferred.resolve();
                      } else {
                        alert(response.message);
                        deferred.reject();
                      }
                    }
                  }
                );
              }.bind(this));

            return deferred.promise();
          }.bind(this),
          beforeSend:function(block) {
            var deferred = WebUploader.Deferred();
            // 每次上传分块前校验分块,如果已存在分块则不再上传,达到断点续传的目的
            $.ajax(
              {
                type:"POST",
                url:"/api/media/upload/checkchunk",
                data:{
                  // 文件唯一表示
                  fileMd5:this.fileMd5,
                  // 当前分块下标
                  chunk:block.chunk,
                  // 当前分块大小
                  chunkSize:block.end-block.start
                },
                dataType:"json",
                success:function(response) {
                  if(response.fileExist) {
                    // 分块存在,跳过该分块
                    deferred.reject();
                  } else {
                    // 分块不存在或不完整,重新发送
                    deferred.resolve();
                  }
                }
              }
            );
            //构建fileMd5参数,上传分块时带上fileMd5
            this.uploader.options.formData.fileMd5 = this.fileMd5;
            this.uploader.options.formData.chunk = block.chunk;
            return deferred.promise();
          }.bind(this),
          afterSendFile:function(file) {
            // 合并分块
            $.ajax(
              {
                type:"POST",
                url:"/api/media/upload/mergechunks",
                data:{
                  fileMd5:this.fileMd5,
                  fileName: file.name,
                  fileSize:file.size,
                  mimetype:file.type,
                  fileExt:file.ext
                },
                success:function(response){
                  //在这里解析合并成功结果
                  if(response && response.success){
                      alert("上传成功")
                  }else{
                      alert("上传失败")
                  }
                }
              }
            );
          }.bind(this)
        }
      );
      // 创建uploader对象,配置参数
      this.uploader = WebUploader.create(
        {
          swf:"/static/plugins/webuploader/dist/Uploader.swf",//上传文件的flash文件,浏览器不支持h5时启动flash
          server:"/api/media/upload/uploadchunk",//上传分块的服务端地址,注意跨域问题
          fileVal:"file",//文件上传域的name
          pick:"#picker",//指定选择文件的按钮容器
          auto:false,//手动触发上传
          disableGlobalDnd:true,//禁掉整个页面的拖拽功能
          chunked:true,// 是否分块上传
          chunkSize:1*1024*1024, // 分块大小(默认5M)
          threads:3, // 开启多个线程(默认3个)
          prepareNextFile:true// 允许在文件传输时提前把下一个文件准备好
        }
      );

      // 将文件添加到队列
      this.uploader.on("fileQueued", function(file) {
          this.uploadFile = file;
          this.percentage = 0;

        }.bind(this)
      );
      //选择文件后触发
      this.uploader.on("beforeFileQueued", function(file) {
//     this.uploader.removeFile(file)
        //重置uploader
        this.uploader.reset()
        this.percentage = 0;
      }.bind(this));

      // 监控上传进度
      // percentage:代表上传文件的百分比
      this.uploader.on("uploadProgress", function(file, percentage) {
          this.percentage = Math.ceil(percentage * 100);
      }.bind(this));
      //上传失败触发
      this.uploader.on("uploadError", function(file,reason) {
        console.log(reason)
        alert("上传文件失败");
      });
      //上传成功触发
      this.uploader.on("uploadSuccess", function(file,response ) {
        console.log(response)
//        alert("上传文件成功!");
      });
      //每个分块上传请求后触发
      this.uploader.on( 'uploadAccept', function( file, response ) {
          if(!(response && response.success)){//分块上传失败,返回false
              return false;
          }
      });

    }
  }

</script>
<style scoped>


</style>

 

 

后台的话需要提供四个接口,

分别是 

文件上传注册

校验文件是否存在,创建文件存放目录啥的,做初始化工作 

post 接口 url:"/api/media/upload/register"  返回值   {"success":"boolean","message":"操作信息提示"}    

参数

(String fileMd5,
String fileName,
Long fileSize,
String mimetype,
String fileExt)

 

分块检查

效验文件分块目录是否存在,及分块是否存在,如果存在分块,就不上传该分块....

type:"POST", --方法类型
url:"/api/media/upload/checkchunk",  -- url 

参数  

(String fileMd5,
Integer chunk,
Integer chunkSize)

返回值

{"fileExist":"boolean"}  -- 文件存在,返回true,则返回false

 

上传分块

服务器上传各个分块请求的url
url -- 路径
server:"/api/media/upload/uploadchunk"

参数 

MultipartFile file,
Integer chunk,       //分片序号,第几片分片
String fileMd5

返回值

{"success":"boolean"}  //是否上传成功

 

合并分块

执行完上传分块,最后请求合并分块的钩子

 

type:"POST", --方法类型 url:"/api/media/upload/mergechunks",

参数

String fileMd5,
String fileName,
Long fileSize,
String mimetype,
String fileExt

 

返回值

{"success":"boolean"}  //是否合并成功,

 

 

 

把以上的接口实现就能实现断点传续啦,

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值