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

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章