大文件分块上传,断点续传

本文对最近学习的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我不怎么会用,也就会把代码传传上去,接下来会好好学下怎么使用

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值