zoukankan      html  css  js  c++  java
  • 大文件分块上传,断点续传

    本文对最近学习的Web文件上传方法做一些总结,主要用到了百度的Web Uploader。

    本文目录:

    1. Web文件上传的过程分析
    2. Java基础的文件上传方法
    3. 图片上传生成缩略图
    4. 拖拽上传
    5. 文件分块上传
    6. 文件断点续传

    1. 文件上传的过程分析

    Web文件上传通过Http请求进行传输,可以通过Java工具进行接收。要注意包含文件上传内容的表单的类型必须为enctype=”multipart/form-data”

    文件上传过程

    请求正文中的内容如下图:

    请求正文

    2. Java基础的文件上传方法

    最常见是使用Apache commons中的两个jar包实现Web文件上传
    jar包

    第一步是文件上传的jsp页面,上传文件和一个普通text输入框:

    <form action="${pageContext.request.contextPath}/UploaderServlet" method="post" enctype="multipart/form-data">
            选择要上传的文件:<input type="file" name="attach"/><br/>
            普通数据:<input type="text" name="info"><br/>
            <input type="submit" value="点击上传"/>
    </form>

    新建一个文件夹当做服务器保存上传的文件:F:uploader

    接下来是新建一个servlet用于保存数据,接收到前台数据之后,对数据信息进行判断是普通数据还是文件(文件就是type=”file”),然后分别进行处理。文件就把它保存到服务器,就是前面新建的文件夹。

    // 1. 创建DiskFileItemFactory对象,配置缓存信息
            DiskFileItemFactory factory = new DiskFileItemFactory();
    
            // 2. 创建ServletFileUpload对象
            ServletFileUpload sfu = new ServletFileUpload(factory);
    
            // 3. 设置文件名称的编码
            sfu.setHeaderEncoding("utf-8");
    
            // 4. 开始解析文件
            try {
                List<FileItem> items = sfu.parseRequest(request);
    
                // 服务器的目录
                String serverPath = "F:/uploader";
    
                // 5. 获取文件信息
                for (FileItem item : items) {
    
                    // 6. 判断是文件还是普通的数据
                    if (item.isFormField()) {
                        // 普通数据
                        String fileName = item.getFieldName();
    
                        if (fileName.equals("info")) {
                            // 获取文件信息
                            String info = item.getString("utf-8");
                            System.out.println(info);
                        }
                    } else {
                        // 文件
    
                        // 获取文件的名称
                        String name = item.getName();
    
                        // 获取文件的实际内容
                        InputStream is = item.getInputStream();
    
                        // 保存文件
                        FileUtils.copyInputStreamToFile(is, new File(serverPath
                                + "/" + name));
                    }
                }
            } catch (FileUploadException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

    完成之后上传测试文件.txt以及输入文本信息弄浪的鱼文件,再看http请求头已经包含上传信息,然后文件夹会有文件,控制台输出弄浪的鱼:

    上传信息

    3. 图片上传生成缩略图

    从这里开始到后面的内容都用到了百度的Web Uploader,所以先下载Web Uploader,然后导入到工程中再将jsp页面进行修改

    文件上传时会有三种状态:文件上传前,文件上传中和文件上传后。上传前可以进行一些初始操作,比如追加一些div用于显示上传信息;上传中显示上传的进度‘上传后显示上传完成。Web Uploader通过js对这三种状态进行监听,每个状态传递不同的参数就行。

    所以先导入js和css并且在jsp中引用,注意有些地方用了jquery:

    <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/css/webuploader.css">
    <script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-1.7.2.js"> </script>
    <script type="text/javascript" src="${pageContext.request.contextPath}/js/webuploader.js"> </script>

    然后是网页代码

            <div id="uploader">
                <!-- 显示文件列表 -->
                <ul id="fileList"></ul>
                <!-- 选择文件区域 -->
                <div id="filePicker">点击上传文件</div>
            </div>

    javascript初始化及注册三种状态监听

    <script type="text/javascript">
            //1.初始化WebUploader,以及配置全局参数
            var uploader = WebUploader.create({
    
                // swf文件路径
                swf : "${pageContext.request.contextPath}/js/Uploader.swf",
    
                // 文件接收服务端。
                server : "${pageContext.request.contextPath}/UploaderServlet",
    
                // 选择文件的按钮。可选。
                // 内部根据当前运行是创建,可能是input元素,也可能是flash.
                pick : '#filePicker',
    
                // 自动上传
                auto : true
            });
    
            //2. 选择文件后,文件信息队列展示
            //注册fileQueued事件:当文件加入队列后触发
            uploader.on("fileQueued",function(file){
                //追加文件信息div
                $("#fileList").append("<div id='" + file.id + "'class='fileInfo'><img/><span>" + file.name +
                        "</span><div class='state'>等待上传...</div><span class='text'><span></div>");
    
            });
    
            //3. 注册上传监听
            //percentage:当前上传进度0-1
            uploader.on("uploadProgress",function(file,percentage){
                var id=$("#"+file.id);
                //更新状态信息
                id.find("div.state").text("上传中...");
                //更新上传的百分比
                id.find("span.text").text(Math.round(percentage*100)+"%");
            });
    
            //4. 注册上传完毕监听
            //response:后台回送数据,json格式
            uploader.on("uploadProgress",function(file,percentage){
                //更新状态信息
                $("#"+file.id).find("div.state").text("上传完毕");
            });
        </script>
    ~~~
    
    生成缩略图,明显是在文件上传过程中生成的,所以在上传过程中的监听中添加生成缩略图的内容
    ~~~JavaScript
    uploader.on("fileQueued",function(file){
                //追加文件信息div
                $("#fileList").append("<div id='" + file.id + "'class='fileInfo'><img/><span>" + file.name +
                        "</span><div class='state'>等待上传...</div><span class='text'><span></div>");
    
                //生成缩略图:调用makeThumb()方法
                //error:制造缩略图失败
                //src:缩略图的路径
                uploader.makeThumb(file,function(error,src){
                    var id = $("#" + file.id);
                    //如果失败,则显示不能预览
                    if(error){
                        id.find("img").replaceWith("不能预览");
                    }
    
                    //成功,则显示缩略图到指定位置
                    id.find("img").attr("src",src);
                });
    
            });

    4. 拖拽、黏贴上传

    很多网站支持拖拽上传,拖到指定区域就能上传感觉很酷很爽,其实不难。只需要在全局参数中开启拖拽功能:

    var uploader = WebUploader.create({
    
                // swf文件路径
                swf : "${pageContext.request.contextPath}/js/Uploader.swf",
    
                // 文件接收服务端。
                server : "${pageContext.request.contextPath}/UploaderServlet",
    
                // 选择文件的按钮。可选。
                // 内部根据当前运行是创建,可能是input元素,也可能是flash.
                pick : '#filePicker',
    
                // 自动上传
                auto : true,
                //开启脱宅功能,指定拖拽区域
                dnd:"#dndArea",
                //禁止页面其他地方拖拽功能
                disableGlobalDnd:true,
                //开启黏贴功能
                paste:"#uploader"
    
            });

    拖拽上传

    5. 大文件分块上传

    分块上传:上传的文件可能比较大比如说有19M,如果单线程进行上传速度比较慢,但是如果把19M的文件分成多块采用多线程上传,最后将文件再次拼起来也能够完成文件上传的过程,而且速度更快了。

    MD5值:将文件分块再拼接用到了一个概念MD5值。简单讲就是一个文件会有一个MD5就像人的身份证一样,是它在网上的身份标识,根据MD5值就能判断是哪一个文件。拿百度云举例子,有时候上传一步电影能够做到秒传,那是因为百度云的服务器存了一份相同的电影,正好和你上传电影的MD5值相同也就是说是同一部电影,所以只要给你一个指向这份文件的索引就相当于上传了电影,达到了秒传。

    知道了这两个概念看一下分块上传图解:

    分块上传图解

    首先很好理解,在初始化过程中需要开启分块上传功能:

    var uploader = WebUploader.Uploader({
       // 其他同上,略
    
        // 开启分片上传。
        chunked: true
    });

    接下来可以结合上面的图来看了:
    第一步:肯定是先要获取MD5值,并且将值传递到后台。这样才能创建一个MD5命名的文件夹,用于保存分片。所以由前台js获取文件的MD5值,有一个后台程序用于创建文件夹。
    第二步:本应该是将文件分片,但是分片的操作不需要我们进行,Web Uploader帮我们做好了,我们要做的就是将分片索引名传递到后台,并保存到相应文件夹下。
    第三步:将分片根据索引排序,然后用I/O流合并分片,最后再给个文件名。所以这一步要有一个文件合并的后台程序,用Ajax传递数据到后台

    后面有整个源码,就不贴所有代码了。在原来基础上添加如下js,注意要放在web uploader初始化之前:

    //获取文件的标记
            var fileMd5;
    
            //5.监控文件的三个上传时间点
            //时间点一:所有分块进行上传之前(1.计算文件的MD5 2.判断是否秒传)
            //时间点二:如果分块上传,每个分块上传之前(选文后台该分块是否保存成功)
            //时间点三:分块上传成功(通知后台合并)
            WebUploader.Uploader.register({
                "before-send-file":"beforeSendFile",
                "before-send": "beforeSend",
                "after-send-file": "afterSendFile"
            },{
                //时间点一
                beforeSendFile:function(file){
                    //创建一个deffered
                    var deferred = WebUploader.Deferred();
    
                    //1.计算文件的唯一标记,用于断点续传和秒传
                    (new WebUploader.Uploader()).md5File(file,0,5*1024*1024)
                        .progress(function(percentage){
                            $("#"+file.id).find("div.state").text("正在获取文件信息...");
                        })
                        .then(function(val){
                            fileMd5 = val;
    
                            $("#"+file.id).find("div.state").text("成功获取文件信息");
    
                            //获取文件信息之后需要进入到下一步
                            deferred.resolve();
                        });
    
                    //返回deffered
                    return deferred.promise();
                },
    
              //时间点2:如果有分块上传,则 每个分块上传之前调用此函数
                //block:代表当前分块对象
                beforeSend:function(block){
                    //alert(fileMd5);
                    //1.请求后台是否保存过当前分块,如果存在,则跳过该分块文件,实现断点续传功能
                    var deferred = WebUploader.Deferred();              
                    //携带当前文件的唯一标记到后台,用于让后台创建保存该文件分块的目录
                    this.owner.options.formData.fileMd5 = fileMd5;
                    //进入下一步
                    deferred.resolve();
                    return deferred.promise();                    
                },
    
                //时间点三
                afterSendFile:function(file){
                    //1.如果分块上传,则通过后台合并所有分块文件
    
                    //请求后台合并文件
                    $.ajax(
                        {
                        type:"POST",
                        url:"${pageContext.request.contextPath}/UploaderCheckServlet?action=mergeChunks",
                        data:{
                            //文件唯一标记
                            fileMd5:fileMd5,
                            //文件名称
                            fileName:file.name
                        },
                        dataType:"json",
                        success:function(response){
                            alert(response.msg);
                        }
                        }
                    );
                },
            });

    6.断点续传

    断点续传:用百度云举例子,一个文件上传了一般有事把电脑关了,下次打开百度云上传会在原来的基础上继续上传,这个就是断点续传。上面已经知道了文件可以分块上传,所以有些块已经上传了而有些块没有。重新上传文件时,那些已经上传过的文件块就不需要上传了。

    前台js

    $.ajax(
                        {
                        type:"POST",
                        url:"${pageContext.request.contextPath}/UploaderCheckServlet?action=checkChunk",
                        data:{
                            //文件唯一标记
                            fileMd5:fileMd5,
                            //当前分块下标
                            chunk:block.chunk,
                            //当前分块大小
                            chunkSize:block.end-block.start
                        },
                        dataType:"json",
                        success:function(response){
                            if(response.ifExist){
                                //分块存在,跳过该分块
                                deferred.reject();
                            }else{
                                //分块不存在或者不完整,重新发送该分块内容
                                deferred.resolve();
                            }
                        }
                        }
                    );

    后台代码:

    if("checkChunk".equals(action)){
                System.out.println("checkChunk...");
                String fileMd5 = request.getParameter("fileMd5");
                String chunk = request.getParameter("chunk");
                String chunkSize = request.getParameter("chunkSize");
    
                File checkFile = new File(serverPath+"/"+fileMd5+"/"+chunk);
    
                response.setContentType("text/html;charset=utf-8");
                //检查文件是否存在,且大小是否一致
                if(checkFile.exists() && checkFile.length()==Integer.parseInt(chunkSize)){
                    response.getWriter().write("{"ifExist":1}");
                }else{
                    response.getWriter().write("{"ifExist":0}");
                }
    
            }

    源码地址:https://github.com/shuishui17/JavaUploader.git
    代码都贴上来有点冗长就放git了,git我不怎么会用,也就会把代码传传上去,接下来会好好学下怎么使用

  • 相关阅读:
    python
    django admin 扩展
    django-cms 代码研究(八)app hooks
    django-cms 代码研究(七)杂七杂八
    django revision
    django-cms 代码研究(六)plugin的深入分析
    django-cms 代码研究(五)深入(代码结构)
    c# 如何使用wlanapi连接电脑到wifi
    django-cms 代码研究(四)CMS_TEMPLATE标签
    django静态文件查找逻辑
  • 原文地址:https://www.cnblogs.com/shuiyj/p/13185265.html
Copyright © 2011-2022 走看看