酷炫的文件上传技术

1. JavaWeb:上传下载文件

http://blog.csdn.net/axi295309066/article/details/52984462

2. 课程概述

在Web应用系统开发中,文件上传功能是非常常用的功能,今天来主要讲讲JavaWeb中的文件上传功能的相关技术实现,并且随着互联网技术的飞速发展,用户对网站的体验要求越来越高,在文件上传功能的技术上也出现许多创新点,例如异步上传文件,拖拽式上传,黏贴上传,上传进度监控,文件缩略图,大文件断点续传,大文件秒传等等。

本课程需要的基础知识:

  • 了解基本的Http协议内容
  • 基本IO流操作技术
  • Servlet基础知识
    • javascript/jQuery技术基础知识

3. 文件上传的基础

对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,并且所有流数据都会随着Http请求携带到服务器端。所以,文件上传时的请求内容格式要能够基本看懂。

文件上传页面:

 <form action="/itheimaUpload/UploadServlet" method="post" enctype="multipart/form-data">
        请选择上传的文件:<input type="file" name="attach"/><br/>
        <input type="submit" value="提交"/>
    </form>

Http请求内容:

文件上传

文件上传

4. Java后台使用Servlet接收文件

如果使用Servlet获取上传文件的输入流然后再解析里面的请求参数是比较麻烦,所以一般后台选择采用Apache的开源工具common-fileupload这个文件上传组件。

//Java后台代码:Commons-fileUpload组件上传文件
public class UploadServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //1.配置缓存
        DiskFileItemFactory factory = new DiskFileItemFactory(1*1024*1024,new File("c:/tempFiles/"));

        //2.创建ServleFileUpload对象
        ServletFileUpload sfu = new ServletFileUpload(factory);

        //解决文件名称中文问题
        sfu.setHeaderEncoding("utf-8");

        //3.解析
        try {
            List<FileItem> list = sfu.parseRequest(request);

            //解析所有内容
            if(list!=null){
                for(FileItem item:list){
                    //判断是否为普通表单参数
                    if(item.isFormField()){
                        //普通表单参数
                        //获取表单的name属性名称
                        String fieldName = item.getFieldName();
                        //获取表单参数值
                        String value = item.getString("utf-8");
                    }else{

                        //文件
                        if(item.getName()!=null && !item.getName().equals(""))  {

                            //保存到服务器硬盘                  
                            FileUtils.copyInputStreamToFile(item.getInputStream(), new File("c:/targetFiles/"+item.getName()));
                            item.delete();
                        }
                    }
                }
            }
        } catch (FileUploadException e) {
            e.printStackTrace();
        }
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

5. 使用WebUploader上传组件

文件上传页面的前端我们可以选择使用一些比较好用的上传组件,例如百度的开源组件WebUploader,这个组件基本能满足文件上传的一些日常所需功能,如异步上传文件,拖拽式上传,黏贴上传,上传进度监控,文件缩略图,甚至是大文件断点续传,大文件秒传。

5.1 下载WebUpload组件

http://fex.baidu.com/webuploader/ 到WebUpload官网下载WebUpload包

WebUpload目录结构:

WebUpload目录结构

5.2 基本文件上传Demo(包含上传进度)

前端

1.1 在页面导入所需css,js

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

1.2 编写上传页面标签

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

1.3 编写webupload代码

<script type="text/javascript">
        //1.初始化WebUpload,以及配置全局的参数
        var uploader = WebUploader.create(
            {
            //flashk控件的地址
            swf: "${pageContext.request.contextPath}/js/Uploader.swf",
            //后台提交地址
            server:"${pageContext.request.contextPath}/UploadServlet",
            //选择文件控件的标签
            pick:"#filePicker",
            //自动上传文件
            auto:true,
            }
        );

        //2.选择文件后,文件信息队列展示
        // 注册fileQueued事件:当文件加入队列后触发
        // file: 代表当前选择的文件
        uploader.on("fileQueued",function(file){
            //追加文件信息div
            $("#fileList").append("<div id='"+file.id+"' class='fileInfo'><span>"+file.name+"</span><div class='state'>等待上传...</div><span class='text'></span></div>");
        });


        //3.注册上传进度监听
        //file: 正在上传的文件
        //percentage: 当前进度的比例。最大为1.例如:0.2
        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.注册上传完毕监听
        //file:上传完毕的文件
        //response:后台回送的数据,以json格式返回
        uploader.on("uploadSuccess",function(file,response){
            //更新状态信息
            $("#"+file.id).find("div.state").text("上传完毕");
        });

2)后端Servlet代码

  DiskFileItemFactory factory = new DiskFileItemFactory();
        ServletFileUpload sfu = new ServletFileUpload(factory);
        sfu.setHeaderEncoding("utf-8");
        try {
            List<FileItem> items = sfu.parseRequest(request);
            for(FileItem item:items){

                if(item.isFormField()){
                    //普通信息
                }else{
                    //文件信息
                    //判断只有文件才需要进行保存处理
                    System.out.println("接收的文件名称:"+item.getName());
                    //拷贝文件到后台的硬盘
                    FileUtils.copyInputStreamToFile(item.getInputStream(), new File(serverPath+"/"+item.getName()));
                    System.out.println("文件保存成功");
                }
            }
        } catch (FileUploadException e) {
            e.printStackTrace();
        }

5.3 生成图片缩略图

关键点:调用uploader.makeThumb()方法生成缩略图

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

5.4 拖拽,黏贴上传

1)页面添加拖拽区域的div

<!-- 上传div -->
    <div id="uploader">
        <!-- 文件拖拽区域 -->
        <div id="dndArea">
            <p>将文件直接拖拽到这里即可自动上传</p>
        </div>

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

2)在webuploader的全局配置参数添加拖拽功能的参数

//1.初始化WebUpload,以及配置全局的参数
        var uploader = WebUploader.create(
            {
            //flashk控件的地址
            swf: "${pageContext.request.contextPath}/js/Uploader.swf",
            //后台提交地址
            server:"${pageContext.request.contextPath}/UploadServlet",
            //选择文件控件的标签
            pick:"#filePicker",
            //自动上传文件
            auto:true,
            //开启拖拽功能,指定拖拽区域
            dnd:"#dndArea",
            //禁用页面其他地方的拖拽功能,防止页面直接打开文件
            disableGlobalDnd:true
            //开启黏贴功能
            paste:"#uploader"
            }
        );

5.5 大文件分块上传

1)在webuploader全局参数中添加分块上传参数

//1.初始化WebUpload,以及配置全局的参数
        var uploader = WebUploader.create(
            {
            //flashk控件的地址
            swf: "${pageContext.request.contextPath}/js/Uploader.swf",
            //后台提交地址
            server:"${pageContext.request.contextPath}/UploadServlet",
            //选择文件控件的标签
            pick:"#filePicker",
            //自动上传文件
            auto:true,
            //开启拖拽功能,指定拖拽区域
            dnd:"#dndArea",
            //禁用页面其他地方的拖拽功能,防止页面直接打开文件
            disableGlobalDnd:true,
            //开启黏贴功能
            paste:"#uploader",

            //分块上传设置
            //是否分块上传
            chunked:true,
            //每块文件大小(默认5M)
            chunkSize:5*1024*1024,
            //开启几个并发线程(默认3个)
            threads:3,
            //在上传当前文件时,准备好下一个文件
            prepareNextFile:true
            }
        );

2)监控上传文件的三个时间点

添加以上三个配置后,会发现当文件超过5M时,webuploader自动把文件会分几个请求发送给后台

文件上传

每个分块请求,包含的信息:

文件上传

可以监听文件分块上传的三个重要的时间点。

文件上传

before-send-file : 在所有分块发送之前调用
before-send: 如果有分块,在每个分块发送之前调用
after-send-file: 在所有分块发送完成之后调用

//5.监控文件上传的三个时间点(注意:该段代码必须放在WebUploader.create之前)
        //时间点1::所有分块进行上传之前(1.可以计算文件的唯一标记;2.可以判断是否秒传)   
        //时间点2: 如果分块上传,每个分块上传之前(1.询问后台该分块是否已经保存成功,用于断点续传)
        //时间点3:所有分块上传成功之后(1.通知后台进行分块文件的合并工作)
        WebUploader.Uploader.register({
            "before-send-file":"beforeSendFile",
            "before-send":"beforeSend",
            "after-send-file":"afterSendFile"
        },{
            //时间点1::所有分块进行上传之前调用此函数
            beforeSendFile:function(){
                //1.计算文件的唯一标记,用于断点续传和秒传
                //2.请求后台是否保存过该文件,如果存在,则跳过该文件,实现秒传功能
            },
            //时间点2:如果有分块上传,则 每个分块上传之前调用此函数
            beforeSend:function(){
                //1.请求后台是否保存过当前分块,如果存在,则跳过该分块文件,实现断点续传功能
            },
            //时间点3:所有分块上传成功之后调用此函数
            afterSendFile:function(){
                //1.如果分块上传,则通过后台合并所有分块文件
            }
        });

before-send-file逻辑:

//利用md5File()方法计算文件的唯一标记符

//该函数接收一个deferred
            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){
                        uniqueFileTag = val;

                        $("#"+file.id).find("div.state").text("成功获取文件信息");
                        //只有文件信息获取成功,才进行下一步操作
                        deferred.resolve();
                    });


                //alert(uniqueFileTag);
                //2.请求后台是否保存过该文件,如果存在,则跳过该文件,实现秒传功能

                //返回deffered
                return deferred.promise();
            }

before-send逻辑:

//向后台发送当前文件的唯一标记,用于后台创建保存分块文件的目录

beforeSend:function(){

    //携带当前文件的唯一标记到后台,用于让后台创建保存该文件分块的目录
    this.owner.options.formData.fileMd5 = fileMd5;
}

3)后台需要保存所有分块文件

//为每个文件创建一个目录,并保存这个文件的所有分块文件
                  //判断是否已经分块上传
                    if(chunks!=null){
                        System.out.println("分块处理...");
                        //进行分块上传了

                        //建立一个临时目录,用于保存所有分块文件
                        File chunksDir = new File(serverPath+"/"+fileMd5);
                        if(!chunksDir.exists()){
                            chunksDir.mkdir();
                        }

                        if(chunk!=null){
                            //保存分块文件
                            File chunkFile = new File(chunksDir.getPath()+"/"+chunk);
                            FileUtils.copyInputStreamToFile(item.getInputStream(), chunkFile);
                        }

4)前台通知后台合并所有分块文件

//前台通知后台合并文件
after-send-file逻辑:

afterSendFile:function(file){
                //1.如果分块上传,则通过后台合并所有分块文件

                //请求后台合并文件
                $.ajax(
                    {
                    type:"POST",
                    url:"${pageContext.request.contextPath}/UploadCheckServlet?action=mergeChunks",
                    data:{
                        //文件唯一标记
                        fileMd5:fileMd5,
                        //文件名称
                        fileName:file.name
                    },
                    dataType:"json",
                    success:function(response){
                        alert(response.msg);
                    }
                    }
                );

            }

//后台合并所有分块文件

if("mergeChunks".equals(action)){
            System.out.println("开始合并文件...");
            //合并文件
            String fileMd5 = request.getParameter("fileMd5");
            String fileName = request.getParameter("fileName");

            //读取目录里面的所有文件
            File f = new File(serverPath+"/"+fileMd5);
            File[] fileArray = f.listFiles(new FileFilter(){

                //排除目录,只要文件
                public boolean accept(File pathname) {
                    if(pathname.isDirectory()){
                        return false;
                    }
                    return true;
                }
            });

            //转成集合,便于排序
            List<File> fileList = new ArrayList<File>(Arrays.asList(fileArray));

            //从小到大排序
            Collections.sort(fileList, new Comparator<File>() {

                public int compare(File o1, File o2) {
                    if(Integer.parseInt(o1.getName()) < Integer.parseInt(o2.getName())){
                        return -1;
                    }
                    return 1;
                }

            });

            File outputFile = new File(serverPath+"/"+fileName);

            //创建文件
             outputFile.createNewFile();

             //输出流
             FileChannel outChannel = new FileOutputStream(outputFile).getChannel();

             //合并
             FileChannel inChannel;
             for(File file : fileList){
                 inChannel = new FileInputStream(file).getChannel();
                 inChannel.transferTo(0, inChannel.size(), outChannel);
                 inChannel.close();

                 //删除分片
                 file.delete();
             }

             //清除文件夹
             File tempFile = new File(serverPath+"/"+fileMd5);
             if(tempFile.isDirectory() && tempFile.exists()){
                 tempFile.delete();
             }

             //关闭流
             outChannel.close();
             response.setContentType("text/html;charset=utf-8");
            response.getWriter().write("{\"msg\":\"合并成功\"}");
        }

5.6 大文件断点续传

在实现了分块上传的基础上,实现断点续传就非常简单了!!!

前端:

//时间点2:如果有分块上传,则 每个分块上传之前调用此函数
            //block:代表当前分块对象
            beforeSend:function(block){
                //1.请求后台是否保存过当前分块,如果存在,则跳过该分块文件,实现断点续传功能
                var deferred = WebUploader.Deferred();

                //请求后台是否保存完成该文件信息,如果保存过,则跳过,如果没有,则发送该分块内容
                $.ajax(
                    {
                    type:"POST",
                    url:"${pageContext.request.contextPath}/UploadCheckServlet?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();
                        }
                    }
                    }
                );

                //携带当前文件的唯一标记到后台,用于让后台创建保存该文件分块的目录
                this.owner.options.formData.fileMd5 = fileMd5;
                return deferred.promise();          
            },

后台:

//检查该分块是否存在或者完整保存
    private void checkChunk(HttpServletRequest request,
            HttpServletResponse response) throws IOException,
            FileNotFoundException {
        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}");
        }
    }

5.7 文件秒传

在所有分块请求之前,就已经可以进行实现秒传功能!!!

前端:

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("成功获取文件信息");


                        //2.请求后台是否保存过该文件,如果存在,则跳过该文件,实现秒传功能
                        $.ajax(
                            {
                            type:"POST",
                            url:"${pageContext.request.contextPath}/UploadCheckServlet?action=fileCheck",
                            data:{
                                //文件唯一标记
                                fileMd5:fileMd5
                            },
                            dataType:"json",
                            success:function(response){
                                if(response.ifExist){
                                    $("#"+file.id).find("div.state").text("秒传成功");
                                    //如果存在,则跳过该文件,秒传成功
                                    deferred.reject();
                                }else{
                                    //继续上传
                                    deferred.resolve();
                                }
                            }
                            }
                        );

                    });

                //返回deffered
                return deferred.promise();
            },

后台:

    //检查文件的md5数据是否跟在数据库存在
    private void fileCheck(HttpServletRequest request,
            HttpServletResponse response) throws IOException,
            FileNotFoundException {
        String fileMd5 = request.getParameter("fileMd5");

        //模拟数据库
        Map<String,String> database = new HashMap<String,String>();
        database.put("576018603f4091782b68b78af85704a1", "01.课程回顾.itcast");

        response.setContentType("text/html;charset=utf-8");
        if(database.containsKey(fileMd5)){
            response.getWriter().write("{\"ifExist\":1}");
        }else{
            response.getWriter().write("{\"ifExist\":0}");
        }
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值