apache commons-fileupload实现进度条大文件批量上传

实现带进度条的文件上传有多种实现方式,之前看到一种是通过flash插件的方式实现上传(推荐SWFUpload,它是一个flash和js相结合的上传插件),这里我们采用Apache上传组件commons-fileupload来接收浏览器上传的文件,该组件自带了文件上传进度的监听器。
在这里我们主要使用了三个类DiskFileUpload、FileItem和FileUploadException,下面分别来介绍一下:

一、DiskFileUpload
DiskFileUpload是Apache上传组件的核心类,我们通过这个类与Apache文件上传组件进行交互。下面介绍几个常用的方法:

setSizeMax方法用于设置请求消息实体内容的最大允许大小,以防止客户端故意通过上传特大的文件来塞满服务器端的存储空间,单位为字节。如果请求消息中的实体内容的大小超过了setSizeMax方法的设置值,该方法会抛出FileUploadException异常。

setSizeThreshold方法用于Apache上传组件在解析和处理上传数据中的每个字段内容时,需要临时保存解析出的数据。因为Java虚拟机默认可以使用的内存空间是有限的,超出限制时会发生“java.lang.OutOfMemoryError”错误。如果上传文件很大,在内存中将无法保存该文件的内容,Apache上传组件将用临时文件来保存这些数据。但如果上传的文件很小,显然直接将其保存在内存中更加有效。

setSizeThreshold方法用于设置是否使用临时文件保存解析出的数据的那个临界值,该方法传入的参数的单位是字节。

setRepositoryPath方法用于设置setSizeThreshold方法中提到的临时文件的存放目录,这里要求使用绝对路径。如果不设置存放路径,那么临时文件将被存储在“java.io.tmpdir”这个JVM环境属性所指定的目录中。

parseRequest方法是DiskFileUpload类的重要方法,它是对HTTP请求消息进行解析的入口方法。如果请求消息中的实体内容的类型不是”multipart/form-data”,该方法将抛出FileUploadException异常。parseRequest方法解析出FORM表单中的每个字段的数据,并将它们分别包装成独立的FileItem对象,然后将这些FileItem对象加入一个List集合中返回。parseRequest方法还有一个重载方法,该方法集中处理上述所有方法的功能,parseRequest(HttpServletRequest req, int size Threshold, long sizeMax, String path),这两个parseRequest方法都会抛出FileUploadException异常。

isMultipartContent方法用于判断请求消息中的内容是否是”multipart/form-data”类型,是返回true,否则返回false。isMultipartContent是一个静态方法,不用创建DiskFileUpload类的实例对象即可被调用。

setHeaderEncoding方法:由于浏览器在提交FORM表单时,会将普通表单中填写的文本内容传递给服务器,对于文件上传字段,除了传递原始的文件内容外,还要传递其文件路径名等信息。如果在使用Apache文件上传组件时遇到了中文字符的乱码问题,一般都是没有正确调用setHeaderEncoding方法的原因。

二、FileItem
FileItem类用来封装单个表单字段元素的数据,一个表单字段元素对应一个FileItem对象,通过调用FileItem对象的方法可以获得相关表单字段元素的数据。FileItem是一个接口,在应用程序中使用的实际上是该接口的一个实现类,该实现类的名称并不重要,程序可以采用FileItem接口类型来对它进行引用和访问。所以我们这里介绍的其实是FileItem实现类,该类还实现了Serializable接口,以支持序列化操作。下面介绍几个常用的方法:

isFormField方法用于判断FileItem类对象封装的数据是否属于一个普通表单字段,还是属于一个文件表单字段,如果是普通表单字段则返回true,如果是文件表单字段则返回false

getName方法用于获得文件上传字段中的文件名,如果FileItem类对象对应的是普通的表单字段,getName方法将返回null,即使用户没有通过网页表单中的文件字段传递任何文件,但只要设置了文件表单字段的name属性,浏览器也会将文件字段的信息传递给服务器,只是文件名和文件内容部分都为空,但这个表单字段仍然对应一个FileItem对象,此时getName方法返回结果为”“字符串,这一部分在使用的时候需要注意一下。值得注意的是,如果用户使用windows系统上传文件,浏览器将传递该文件的完整路径;如果使用linux或Unix系统上传文件,浏览器将只传递该文件的名称部分。

getFiledName方法用于返回表单字段元素的name属性值

write方法用于将FileItem对象中保存的主体内容保存到某个指定的文件中,如果FileItem对象中的主体内容是保存在某个临时文件中,该方法顺利完成后,临时文件有可能会被清除。该方法也可将普通表单字段内容写入到一个文件中,但它主要用途是将上传的文件内容保存在本地文件系统中。

getString方法用于将FileItem对象中保存的主体内容作为一个字符串返回,它有两个重载的定义形式:public java.lang.String getString()和public java.lang.String getString(java.lang.String encoding)throwsjava.io.UnsupportedEncodingException,前者使用缺省的字符集编码将主体内容转换成字符串,后者使用参数指定的字符集编码将主体内容转换成字符串。如果在读取普通表单字段元素的内容时出现了中文乱码现象,请调用第二个getString方法,并为之传递正确的字符集编码名称。

getContentType方法用于获得上传文件的类型,如果FileItem类对象对应的是普通表单字段,该方法将返回null。

isInMemory方法用于判断FileItem类对象封装的主体内容是存储在内存中,还是存储在临时文件中,如果存储在内存中则返回true,否则返回false。

delete方法用来清空FileItem类对象中存放的主体内容,如果主体内容被保存在临时文件中,delete方法将删除该临时文件。尽管Apache组件使用了多种方式来尽量及时清理临时文件,但系统出现异常时,仍有可能造成有的临时文件被永久保存在了硬盘中。在有些情况下,可以调用这个方法来及时删除临时文件。

三、FileUploadException
在文件上传过程中,可能发生各种各样的异常,例如网络中断、数据丢失等等。为了对不同异常进行合适的处理,Apache文件上传组件还开发了四个异常类,其中FileUploadException是其他异常类的父类,其他几个类只是被间接调用的底层类,对于Apache组件调用人员来说,只需对FileUploadException异常类进行捕获和处理即可。

下面上代码,JSP页面:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script type="text/javascript" src="../common/js/jquery-1.7.2.js"></script>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Add New File Upload</title>
<script type="text/javascript"> 
    j = 1; 
    $(document).ready(function(){        
        $("#addNewFile").click(function(){ 
            document.getElementById("newUpload").innerHTML+='<div id="div_'+j+'"><input  name="file_'+j+'" type="file"  /><input type="button" value="删除"  onclick="addNewFile('+j+')"/></div>';
            j = j + 1; 
        }); 
    });  

    function addNewFile(o){ 
        document.getElementById("newUpload").removeChild(document.getElementById("div_"+o)); 
    }
</script>
</head>
<body>
    <form id="uploadForm" name="uploadForm" action="/Demo/uploadfile/uploadfile.do" enctype="multipart/form-data" method="post"> 
        <div id="newUpload">
            <input type="file" name="file"> 
        </div>

        <input type="button" id="addNewFile" value="增加一行" > 
        <input type="button" onclick="uploadFile();" name="uploadButton" id="uploadButton" value="上传">             
    </form>  
    <div style="width:273px;">
        <div id="show" style="background:#0ff;height:26px;width:0%;"></div>
    </div>
    <span id="msg"></span>
</body>
<script type="text/javascript">
    var progress;
    var uploadProcessTimer = null;

    function uploadFile(){
        //每隔100ms执行callback
        uploadProcessTimer = window.setInterval("getFileUploadProcess()", 100);
        document.forms[0].submit();
    }

    function getFileUploadProcess(){
        $.ajax({
            type:"GET",
            url:"/Demo/uploadfile/status.do",
            dataType:"text",
            cache:false,
            success:function(data){
                if(data=="100%"){
                    window.clearInterval(uploadProcessTimer);
                }else{
                    progress=data;
                    $("#show").width(data);
                    $("#msg").text(data);
                }
            }
        });
    };

</script>
</html>

Controller:

@RequestMapping("/uploadfile.do") 
public void uploadFile(HttpServletRequest request, HttpServletResponse response) throws IllegalStateException, IOException {   
    try{
        String path = "/upload/temp";
        int flag = 1;

        DiskFileItemdiskFileItemFactory  diskFileItemdiskFileItemFactory = new DiskFileItemdiskFileItemFactory ();
        //设置内存缓冲区。超过后写入临时文件
        diskFileItemFactory .setSizeThreshold(10240000);
        //设置临时文件存储位置;
        //diskFileItemFactory .setRepository设置这个是上传大文件的时候,不是把上传的所有数据一直存在内存中,而是当达到diskFileItemFactory.setSizeThreshold所设置的值后就存入临时文件,利用这个特性,可以使上传大文件的时候不会占用大量内存,可以提供用户的并发使用量
        diskFileItemFactory.setRepository(new File(request.getSession().getServletContext().getRealPath(path)));

        ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
        servletFileUpload.setHeaderEncoding("utf-8");//设置文件上传的编码格式
        servletFileUpload.setFileSizeMax(5*102400000);//单个文件最大上传值
        servletFileUpload.setSizeMax(10*102400000);//整个request最大值
        //servletFileUpload.setProgressListener(); 这是设置的一个监听器,就是通过它来获取已经上传的文件的大小
        servletFileUpload.setProgressListener(new UploadProgressListener(request));//进度条

        SimpleDateFormat simple = new SimpleDateFormat("yyyy/MM");  //获取日期格式
        String dirPath = simple.format(new Date());
        //保存的文件路径
        String savePath = request.getSession().getServletContext().getRealPath("/"+dirPath);
        File saveDir = new File(savePath);
        if(!saveDir.exists()){
            saveDir.mkdirs();
        }

        try {
            List<FileItem> itemList = new ArrayList<FileItem>();
            itemList = servletFileUpload.parseRequest(request);
            if(itemList.size() != 0){
                for(int i=0; i<itemList.size(); i++){
                    FileItem fileItem = (FileItem)itemList.get(i);
                    if(!fileItem.isFormField() && fileItem.getName().length()>0){
                        Long size = fileItem.getSize();  //文件大小
                        String fileName = takeOutFileName(fileItem.getName());
                        String newFileName = UUID.randomUUID().toString().replaceAll("-","")+fileName.substring(fileName.lastIndexOf("."));
                        File uploadedFile = new File(savePath, newFileName);

                        try {
                            fileItem.write(uploadedFile);
                            System.out.println("文件名:"+newFileName+"文件大小为:"+size);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        flag++;
                    }
                }
            }
        } catch (FileUploadException e) {
            e.printStackTrace();
        }
        request.getRequestDispatcher("/success.jsp").forward(request, response);                         
    }catch(Exception e){
        e.printStackTrace();
    }
}

//获取上传进度
@RequestMapping("/status.do") 
public void uploadStatus(HttpServletRequest request, HttpServletResponse response) throws IOException{
    response.setContentType("text/html;charset=utf-8");
    HttpSession session = request.getSession();
    Object status = session.getAttribute("key");  //获取上传进度
    if(status == null) return;
    PrintWriter out = response.getWriter();
    out.write(status.toString());
    out.flush();
    out.close();
}

SpringMVC没有实现监听器,所以如果要监听的话得自己扩展CommonsMultipartResolver类,实现自己的监听器。

ProgressListener是一个接口,我们只需要实现它的update方法,参数pBytesRead表示已经上传到服务器的字节数,pContentLength表示所有文件的总大小,pItems表示第几个文件。

public class UploadProgressListener implements ProgressListener {

    private HttpSession session;
    private long kiloBytes=-1;

    public UploadProgressListener(HttpServletRequest request){
        this.session = request.getSession();
    }

    public void update(long pBytesRead, long pContentLength, int pItems) {
        Long KBytes=pBytesRead/1024;
        if(kiloBytes==KBytes){
            return;
        }
        kiloBytes=KBytes;
        System.out.println("正在读取项目"+KBytes);
        if(pContentLength==-1){
            System.out.println("目前已经读取了"+pBytesRead+"字节数据");
        }else{
            System.out.println("目前已经读取了"+pContentLength+"中的"+pBytesRead);
        }
        //获取上传进度的百分比
        double read = (double)(KBytes)/(pContentLength/1024);
        NumberFormat nf = NumberFormat.getPercentInstance();
        session.setAttribute("key", nf.format(read));
        System.out.println("进度时间:"+nf.format(read));
    }

}

猜你喜欢

转载自blog.csdn.net/cnetfcwra/article/details/86689155