浅谈文件断点续传和WebUploader的基本结合


0、写在前面的话

上篇博客已是在8月了,期间到底发生了什么,只有我本身知道,反正就是心情特别糟糕,生活状态工做状态学习状态都十分很差,还有心思进取吗,No!如今状态好起来了,生活又充满了但愿 :D 

前两周在写视频管理相关的功能,说是要在原来的项目上进行拓展。结果今天领导给我说客户那边还没定,只作技术上研究就好了,不用写具体功能代码(我都写了好吗?)因而忽然时间有腾出来,今天整理一下把内容写一些。

要努力努力,为了更好的人为了更好的生活。

一、断点续传的两种方式

1.1 RandomAccessFile

客户端给一个已经上传的位置标记,而后服务器端就能够在指定的位置进行处理。这个断点位置的读取,就要用到RandomAccessFile类,该类不一样于InputStream和OutputStream,它既能够对文件进行读也能够进行写,两个重要方法:
  • long getFilePoint():返回文件记录指针的当前位置,不指定指针的位置默认是0
  • void seek(long pos):设置文件指针偏移,即将文件记录指针定位到pos位置

至于position位置如何去处理,就看各自的想法了。1)你能够将位置存在浏览器(好比localStorage),下次传输的时候前端从残缺位置切割文件 blob.slice() 只传输剩余的部分,后端直接接收接着写入服务器便可;2)也能够前端把文件完整传输,同时带上position参数,由后端经过 RandomAccessFile 在指定位置开始读取内容便可。

至于客户端和服务端之间文件的一致性,多使用md5进行校验。

1.2 分片处理

H5中新增了File API,能够经过使用 slice() 方法生成只有某段文件内容。这个方法就为断点续传提供了新的方式,就是分片处理,假设一个文件是100M大小,那么每次传输我只须要传送10M,按序发送10次请求便可。某个分片传送失败,那么从这个分片再继续发送便可,后端则对分片文件进行合并成完整文件。

其实方式和1.1提到的是相似的,不过每次传输的数据单位量更大一些,完整文件交给后端进行合并。

二、WebUploader的分片段点续传

WebUploader的选项中支持直接开启分片上传:
var uploader = WebUploader.Uploader({
    swf: 'path_of_swf/Uploader.swf',

    // 开起分片上传
    chunked: true,
    // 分片大小,默认5M
    chunkSize: 5242880,
    // 分片出错后(如网络缘由等形成出错)的重传次数
    chunkRetry: 2,
    // 上传并发数
    threads: 1
});

开启分片上传后,插件会自动分片上传文件,接下来只须要在配置文件跳过和后端处理便可。官方回应在分片发送前会有监听的事件 uploadBeforeSend,在这个方法的callback里面若是返回的是一个promise,且此promise被reject了,那么此分片就跳过了。( 实际上该方式在自测和咨询网友时发现,并无什么用,即使按照官方说明,分片也没有跳过,仍然日后端进行了请求发送,同时也附带有文件
webUploader.on('uploadBeforeSend', function(block, data){
    data.fileMd5 = block.file.wholeMd5;
    var deferred = WebUploader.Deferred();
    var chunk = data.chunk;
    var existChunks = block.file.existChunks;
    //后端返回了已存在分片的数组,这里判断要发送的分片是否已存在
    if(existChunks && existChunks.indexOf(chunk) != -1) {
        //console.log("分片存在,已跳过:" + chunk);
        deferred.reject();
    } else {
        deferred.resolve();
    }
    return deferred.promise();
});

分片是否存在的判断,也有不一样的方式,一种你能够每次计算分片的md5值发送给后端,若是服务器已存在则跳过,不然就发送;另外一种就是只向服务器查询一次获取已经存在的分片,而后在浏览器端进行比对,但如此须要考虑分片是否并发传输,进行相应处理。

我采用的方式是:先对文件进行md5计算,在服务器端建立和md5值同名的文件夹,每次上传的分片存放在对应文件夹,文件名即分片的序号,好比某文件夹中可能存在文件 0, 1, 2, 3... 前端发送分片前请求后端数据,后端将已经存在的分片名数组返回前端,前端进行跳过处理,同时后端在接收分片也要作是否存在的判断,已存在的话就再也不进行读写操做,直到最后分片到达,则进行分片的按序合并便可。

public boolean uploadChunk() throws ChunkUploadException {
    HttpServletRequest request = ServletActionContext.getRequest();
    //封装源文件信息
    FileInfo srcFileInfo = VideoUtil.getUploadFileInfoByStruts(request, "file");
    //获取同时上传的文件其余属性
    Map<String, String> params = getVideoParams(request);

    if (params.get("fileMd5") == null || "".equals(params.get("fileMd5"))) {
        throw new ChunkUploadException("文件md5值未传递");
    }
    //存放
    File temp = new File(getTempPath(params.get("fileMd5")) + "/" + srcFileInfo.getCurChunk());
    if (!temp.exists()) {
        try {
            VideoUtil.copy(srcFileInfo.getFile(), temp);
        } catch (IOException e) {
            throw new ChunkUploadException("分片上传失败: chunkNum" + params.get("chunk"));
        }
    }
    //若是是最后分片
    return !srcFileInfo.isChunked() || srcFileInfo.getCurChunk() == srcFileInfo.getChunkSize() - 1;
}

public String upload() {
    boolean isLastChunk = false;
    try {
        isLastChunk = uploadChunk();
    } catch (ChunkUploadException e) {
        e.printStackTrace();
        AjaxSupport.sendFailText(null, e.getMessage());
        return AJAX_RESULT;
    }

    //不是最后的分片,直接返回成功响应
    if (!isLastChunk) {
        AjaxSupport.sendSuccessText("chunk uploaded", "success");
        return AJAX_RESULT;
    } 
    //最后切片
    else {
        HttpServletRequest request = ServletActionContext.getRequest();
        //封装源文件信息
        FileInfo srcFileInfo = VideoUtil.getUploadFileInfoByStruts(request, "file");
        //获取同时上传的文件其余属性
        Map<String, String> params = getVideoParams(request);
        //获取合并文件的文件名
        String filename = UUID.randomUUID().toString() + "." + srcFileInfo.getFileType();

        //合并文件
        File tempDir = new File(getTempPath(params.get("fileMd5")));
        File[] tempfileArr = tempDir.listFiles();
        File storeFile = new File(getStorePath() + "/" + filename);
        try {
            VideoUtil.merge(tempfileArr, storeFile);
        }
    ...

最后,实际上这种方式断点续传仍然存在不少细节没有考虑,好比多线程,同个浏览器两个tab发送同一文件时如何处理?

三、参考连接

相关文章
相关标签/搜索