zoukankan      html  css  js  c++  java
  • 浅谈文件断点续传和WebUploader的基本结合


    0、写在前面的话

    上篇博客已经是在8月了,期间到底发生了什么,只有我自己知道,反正就是心情特别糟糕,生活状态工作状态学习状态都十分不好,还有心思进取吗,No!现在状态好起来了,生活又充满了希望 :D 

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

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

    1、断点续传的两种方式

    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提到的是类似的,不过每次传输的数据单位量更大一些,完整文件交给后端进行合并。

    2、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发送同一文件时如何处理?

    3、参考链接


  • 相关阅读:
    POJ 1041(欧拉路)
    POJ 1904(强连通分量)Tarjan
    POJ 1486(二分图匹配)二分图的完全匹配的必须边
    POJ 1780(欧拉路)
    POJ 1386(欧拉路)
    HDU 3496(DP)
    PKU2387Til the Cows Come Home(SPFA+邻接表)
    HDU1863畅通工程(prim)
    ACM国内外OJ网站大集合
    HDU1175连连看(BFS)
  • 原文地址:https://www.cnblogs.com/deng-cc/p/10117956.html
Copyright © 2011-2022 走看看