06月30, 2019

断点续传

java的学习刚好学到媒资管理,上来和大家分享一下,也算是一个学习笔记吧。

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

什么是断点续传

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

alt

上传流程

  • 上传前先把文件分成块
  • 一块一块的上传,上传中断后重新上传,已上传的分块则不用再上传
  • 各分块上传完成最后合并文件

文件下载则同理。

理解文件分块上传的原理

以下以java代码为例

文件分块

文件分块的流程如下:

  • 获取源文件长度
  • 根据设定的分块文件的大小计算出块数
  • 从源文件读数据依次向每一个块文件写数据。
@Test
public void testChunk() throws IOException {

    File sourceFile = new File("/Users/zhangpu/Downloads/lucene.avi");

    // 块文件目录
    String chunkPath = "/Users/zhangpu/Downloads/chunks/";

    File chunkFolder = new File(chunkPath);
    if(!chunkFolder.exists()){
        chunkFolder.mkdir();
    }

    // 先定义块文件大小
    long chunkFileSize = 1*1024*1024;

    long chunkFileNum = (long)Math.ceil(sourceFile.length() * 1.0 /chunkFileSize);

    RandomAccessFile raf_read = new RandomAccessFile(sourceFile, "r");

    //缓冲区大小
    byte[] b = new byte[1024];
    for(int i = 0; i < chunkFileNum; i ++) {
        File chunkFile = new File(chunkPath + i);
        RandomAccessFile raf_write = new RandomAccessFile(chunkFile, "rw");
        int len = -1;
        while((len = raf_read.read(b)) != -1) {

            raf_write.write(b, 0, len);
            if (chunkFile.length() > chunkFileSize) {
                break;
            }
        }
        raf_write.close();
    }

    raf_read.close();

}

运行该test,就可以得到下图的结果:

alt

文件合并

文件合并流程:

  • 找到要合并的文件并按文件合并的先后进行排序。
  • 创建合并文件
  • 依次从合并的文件中读取数据向合并文件写入
@Test
public void testMergeFile() throws IOException {
    File chunkFolder = new File("/Users/zhangpu/Downloads/chunks/");

    File[] files = chunkFolder.listFiles();
    List<File> fileList = Arrays.asList(files);
    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;
        }
    });

    File mergeFile = new File("/Users/zhangpu/Downloads/lucene_merge.avi");

    boolean newFile = mergeFile.createNewFile();

    RandomAccessFile raf_write = new RandomAccessFile(mergeFile, "rw");

    //缓冲区大小
    byte[] b = new byte[1024];
    for(File chunkFile: fileList) {
        RandomAccessFile raf_read = new RandomAccessFile(chunkFile, "r");
        int len = -1;
        while((len = raf_read.read(b)) != -1) {
            raf_write.write(b, 0, len);
        }
        raf_read.close();
    }
    raf_write.close();
}

前端页面

如何在web页面实现断点续传

常见的方案有:

  • 通过Flash上传,比如SWFupload、Uploadify。
  • 安装浏览器插件,变相的pc客户端,用的比较少。
  • Html5

项目采用的是:webuploader

流程

使用的流程如下图:

alt

钩子方法

alt

本项目使用如下钩子方法:

  • before-send-file

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

  • before-send

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

  • 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时启动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 // 允许在文件传输时提前把下一个文件准备好
})

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

API接口

定义文件上传的Api接口,此接收是前端WebUploader调用服务端的接口。

@Api(value = "媒资管理接口", description = "媒资管理接口,提供文件上传、处理等接口")
public interface MediaUploadControllerApi {

    // 文件上传前的准备工作,校验文件是否存在
    @ApiOperation("文件上传注册")
    public ResponseResult register(String fileMd5,
                                   String fileName,
                                   Long fileSize,
                                   String mimetype,
                                   String fileExt);

    // 校验分块是否存在
    @ApiOperation("分块检查")
    public CheckChunkResult checkchunk(String fileMd5,
                                       Integer chunk,
                                       Integer chunkSize);
    @ApiOperation("上传分块")
    public ResponseResult uploadchunk(MultipartFile file,
                                      Integer chunk,
                                      String fileMd5);
    @ApiOperation("合并文件")
    public ResponseResult mergechunks(String fileMd5,
                                      String fileName,
                                      Long fileSize,
                                      String mimetype,
                                      String fileExt);

}

服务端接口代码编写

(略)

其实服务端要写的东西还是有些多的,比如最后一步mergechunks,要做以下几个事

  • 合并文件
  • 校验合并后的文件的md5值是否与传过来的文件md5一样
  • 保存到数据库

结语

相信大家看了前面的一些内容,对于断点续传有了更为清晰的认知吧。。

本文链接:www.my-fe.pub/post/continuous-transmission-of-breakpoints.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。