基於AJAX的文件上傳顯示進度條實現

基於Ajax的文件上傳要實現的功能要求,要在用戶提交了上傳按鈕請求後,客戶端其頁面要顯示文件上傳進度條。

      其整個功能時序圖如圖所示。


 簡單的說,要實現在客戶端顯示進度條,需要做的是:當客戶端提交上傳文件請求後,服務器在上傳文件的過程中,將上傳進度情況保存到Session中,客戶端週期性的發送請求來獲取保存在Session中值,以獲取上傳文件的進度信息。

1. 新建web工程AjaxUpload。

2. 將commons-fileupload-1.2.1-bin.zip包中的commons-fileupload-1.2.1.jar文件和commons-io-1.4-bin.zip包中的commons-io-1.4.jar文件拷貝到web工程下的WEB-INF\lib目錄下。

3. 由於本實例涉及到多個類,處理此類問題最好是給相應的類打包進行管理。在web工程src目錄下新建一個包com.ncu.upload。

4. 服務器端實現。

首先要創建一個用來保存文件上傳狀態的類 FileUploadStatus。其源碼如下:

package com.ncu.upload;  
  
import java.util.*;  
  
public class FileUploadStatus {  
    //上傳總量  
    private long uploadTotalSize=0;  
    //讀取上傳總量  
    private long readTotalSize=0;  
    //當前上傳文件號  
    private int currentUploadFileNum=0;  
    //成功讀取上傳文件數  
    private int successUploadFileCount=0;  
    //狀態  
    private String status="";  
    //處理起始時間  
    private long processStartTime=0l;  
    //處理終止時間  
    private long processEndTime=0l;  
    //處理執行時間  
    private long processRunningTime=0l;  
    //上傳文件URL列表  
    private List uploadFileUrlList=new ArrayList();  
    //取消上傳  
    private boolean cancel=false;  
    //上傳base目錄  
    private String baseDir="";  
      
    public String getBaseDir() {  
        return baseDir;  
    }  
    public void setBaseDir(String baseDir) {  
        this.baseDir = baseDir;  
    }  
    public boolean getCancel() {  
        return cancel;  
    }  
    public void setCancel(boolean cancel) {  
        this.cancel = cancel;  
    }  
    public List getUploadFileUrlList() {  
        return uploadFileUrlList;  
    }  
    public void setUploadFileUrlList(List uploadFileUrlList) {  
        this.uploadFileUrlList = uploadFileUrlList;  
    }  
    public long getProcessRunningTime() {  
        return processRunningTime;  
    }  
    public void setProcessRunningTime(long processRunningTime) {  
        this.processRunningTime = processRunningTime;  
    }  
    public long getProcessEndTime() {  
        return processEndTime;  
    }  
    public void setProcessEndTime(long processEndTime) {  
        this.processEndTime = processEndTime;  
    }  
    public long getProcessStartTime() {  
        return processStartTime;  
    }  
    public void setProcessStartTime(long processStartTime) {  
        this.processStartTime = processStartTime;  
    }  
    public long getReadTotalSize() {  
        return readTotalSize;  
    }  
    public void setReadTotalSize(long readTotalSize) {  
        this.readTotalSize = readTotalSize;  
    }  
    public int getSuccessUploadFileCount() {  
        return successUploadFileCount;  
    }  
    public void setSuccessUploadFileCount(int successUploadFileCount) {  
        this.successUploadFileCount = successUploadFileCount;  
    }  
    public int getCurrentUploadFileNum() {  
        return currentUploadFileNum;  
    }  
    public void setCurrentUploadFileNum(int currentUploadFileNum) {  
        this.currentUploadFileNum = currentUploadFileNum;  
    }  
    public String getStatus() {  
        return status;  
    }  
    public void setStatus(String status) {  
        this.status = status;  
    }  
    public long getUploadTotalSize() {  
        return uploadTotalSize;  
    }  
    public void setUploadTotalSize(long uploadTotalSize) {  
        this.uploadTotalSize = uploadTotalSize;  
    }  
      
}

 由於要在客戶端要顯示進度條,所以在上傳過程中服務器端需要監視和維護上傳狀態的信息,此過程需要處理的數據信息是:不斷更新Session中保存的FileUploadStatus實例的信息,如:已經上傳的字節數,上傳文件的總大小等。FileUpload現在的1.2版本爲監視上傳進度提供了內建的支持,可以直接繼承類ProgressListener,然後重載update()方法,在該方法中添加自己要處理的代碼,最後在文件上傳處理代碼(後面會講到)中通過爲ServletFileUpload對象註冊創建的監聽類。監聽類UploadListener的源代碼如下:

package com.ncu.upload;  
  
import javax.servlet.http.HttpSession;  
  
import org.apache.commons.fileupload.ProgressListener;  
  
public class UploadListener implements ProgressListener {  
      
    private HttpSession session=null;  
      
    public UploadListener (HttpSession session){  
        this.session=session;  
    }  
    /** 
     * 更新狀態 
     * @param pBytesRead 讀取字節總數 
     * @param pContentLength 數據總長度 
     * @param pItems 當前正在被讀取的field號 
     */  
    public void update(long pBytesRead, long pContentLength, int pItems) {  
        FileUploadStatus fuploadStatus = UploadServlet.takeOutFileUploadStatusBean(this.session);  
        fuploadStatus.setUploadTotalSize(pContentLength);  
        //讀取完成  
        if (pContentLength == -1) {  
            fuploadStatus.setStatus("完成對" + pItems + "個文件的讀取:讀取了 " + pBytesRead + "/"  + pContentLength+ " bytes.");  
            fuploadStatus.setReadTotalSize(pBytesRead);  
            fuploadStatus.setCurrentUploadFileNum(pItems);  
            fuploadStatus.setProcessEndTime(System.currentTimeMillis());  
            fuploadStatus.setProcessRunningTime(fuploadStatus.getProcessEndTime());  
        }else{//讀取過程中  
               fuploadStatus.setStatus("當前正在處理第" + pItems+"個文件:已經讀取了 " + pBytesRead + " / " + pContentLength+ " bytes.");  
               fuploadStatus.setReadTotalSize(pBytesRead);  
               fuploadStatus.setCurrentUploadFileNum(pItems);  
               fuploadStatus.setProcessRunningTime(System.currentTimeMillis());  
        }  
        //System.out.println("已經讀取:" + pBytesRead);  
        UploadServlet.storeFileUploadStatusBean(this.session, fuploadStatus);  
    }  
  
}  

有了前面兩個類的基礎,下來我們可以動手去實現真正處理整個操作Servlet類。源代碼如下。

package com.ncu.upload;  
  
import java.io.*;  
import java.util.List;  
  
import javax.servlet.ServletException;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
import javax.servlet.http.HttpSession;  
  
import org.apache.commons.fileupload.FileItem;  
import org.apache.commons.fileupload.FileUploadException;  
import org.apache.commons.fileupload.disk.DiskFileItemFactory;  
import org.apache.commons.fileupload.servlet.*;  
  
/** 
 * Servlet implementation class for Servlet: UploadServlet 
 * 
 */  
 public class UploadServlet extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {  
   static final long serialVersionUID = 1L;  
     
     public static final String UPLOAD_STATUS="UPLOAD_STATUS";  
     public static final String UPLOAD_DIR="/upload";  
     
    public UploadServlet() {  
        super();  
    }    
      
    /** 
     * 從文件路徑中取出文件名 
     * @param filePath 
     * @return 
     */  
    private String takeOutFileName(String filePath){  
        int pos=filePath.lastIndexOf(File.separator);  
        if (pos>0){  
            return filePath.substring(pos+1);  
        }  
        else{  
            return filePath;  
        }  
    }  
      
    /** 
     * 從request中取出FileUploadStatus Bean 
     * @param request 
     * @return 
     */  
    public static FileUploadStatus takeOutFileUploadStatusBean(HttpSession session){  
        Object obj=session.getAttribute(UPLOAD_STATUS);  
        if (obj!=null){  
            return (FileUploadStatus)obj;  
        }  
        else{  
            return null;  
        }  
    }  
      
    /** 
     * 把FileUploadStatus Bean保存到session 
     * @param request 
     * @param uploadStatusBean 
     */  
    public static void storeFileUploadStatusBean(  
            HttpSession session,  
            FileUploadStatus uploadStatusBean){  
        session.setAttribute(UPLOAD_STATUS,uploadStatusBean);  
    }  
      
    /** 
     * 刪除已經上傳的文件 
     * @param request 
     */  
    private void deleteUploadedFile(HttpServletRequest request){  
        FileUploadStatus fUploadStatus=takeOutFileUploadStatusBean(request.getSession());  
        for(int i=0;i<fUploadStatus.getUploadFileUrlList().size();i++){  
            File uploadedFile = new File(request.getRealPath(UPLOAD_DIR)+  
                    File.separator+fUploadStatus.getUploadFileUrlList().get(i));  
            uploadedFile.delete();  
        }  
        fUploadStatus.getUploadFileUrlList().clear();  
        fUploadStatus.setStatus("刪除已上傳的文件");  
        storeFileUploadStatusBean(request.getSession(),fUploadStatus);  
    }  
      
    /** 
     * 上傳過程中出錯處理 
     * @param request 
     * @param errMsg 
     * @throws IOException  
     * @throws ServletException  
     */  
    private void uploadExceptionHandle(  
            HttpServletRequest request,  
            String errMsg) throws ServletException, IOException{  
        //首先刪除已經上傳的文件  
        deleteUploadedFile(request);  
        FileUploadStatus fUploadStatus=takeOutFileUploadStatusBean(request.getSession());  
        fUploadStatus.setStatus(errMsg);  
        storeFileUploadStatusBean(request.getSession(),fUploadStatus);  
    }  
      
    /** 
     * 初始化文件上傳狀態Bean 
     * @param request 
     * @return 
     */  
    private FileUploadStatus initFileUploadStatusBean(HttpServletRequest request){  
        FileUploadStatus fUploadStatus=new FileUploadStatus();  
        fUploadStatus.setStatus("正在準備處理");  
        fUploadStatus.setUploadTotalSize(request.getContentLength());  
        fUploadStatus.setProcessStartTime(System.currentTimeMillis());  
        fUploadStatus.setBaseDir(request.getContextPath()+UPLOAD_DIR);  
        return fUploadStatus;  
    }  
      
    /** 
     * 處理文件上傳 
     * @param request 
     * @param response 
     * @throws IOException  
     * @throws ServletException  
     */  
    private void processFileUpload(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{  
        DiskFileItemFactory factory = new DiskFileItemFactory();  
        //設置內存閥值,超過後寫入臨時文件  
        //factory.setSizeThreshold(10240000*5);  
        //設置臨時文件存儲位置  
        //factory.setRepository(new File(request.getRealPath("/upload/temp")));  
        ServletFileUpload upload = new ServletFileUpload(factory);  
        //設置單個文件的最大上傳size  
        //upload.setFileSizeMax(10240000*5);  
        //設置整個request的最大size  
        //upload.setSizeMax(10240000*5);  
        //註冊監聽類  
        upload.setProgressListener(new UploadListener(request.getSession()));  
        //保存初始化後的FileUploadStatus Bean  
        storeFileUploadStatusBean(request.getSession(),initFileUploadStatusBean(request));  
  
        try {  
            List items = upload.parseRequest(request);  
            //處理文件上傳  
            for(int i=0;i<items.size();i++){  
                FileItem item=(FileItem)items.get(i);  
  
                //取消上傳  
                if (takeOutFileUploadStatusBean(request.getSession()).getCancel()){  
                    deleteUploadedFile(request);  
                    break;  
                }  
                //保存文件  
                else if (!item.isFormField() && item.getName().length()>0){  
                    String fileName=takeOutFileName(item.getName());  
                    File uploadedFile = new File(request.getRealPath(UPLOAD_DIR)+File.separator+fileName);  
                    item.write(uploadedFile);  
                    //更新上傳文件列表  
                    FileUploadStatus fUploadStatus=takeOutFileUploadStatusBean(request.getSession());  
                    fUploadStatus.getUploadFileUrlList().add(fileName);  
                    storeFileUploadStatusBean(request.getSession(),fUploadStatus);  
                    Thread.sleep(500);  
                }  
            }  
          
        } catch (FileUploadException e) {  
            e.printStackTrace();  
            //uploadExceptionHandle(request,"上傳文件時發生錯誤:"+e.getMessage());  
        } catch (Exception e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
            //uploadExceptionHandle(request,"保存上傳文件時發生錯誤:"+e.getMessage());  
        }  
    }  
      
    /** 
     * 迴應上傳狀態查詢 
     * @param request 
     * @param response 
     * @throws IOException 
     */  
    private void responseFileUploadStatusPoll(HttpServletRequest request,HttpServletResponse response) throws IOException{  
        FileUploadStatus fUploadStatus=(FileUploadStatus)request.getSession().getAttribute(UPLOAD_STATUS);  
        //計算上傳完成的百分比  
        long percentComplete = (long)Math.floor(((double) fUploadStatus.getReadTotalSize()/(double) fUploadStatus.getUploadTotalSize())*100.0);  
        System.out.println("com:"+percentComplete);  
        response.setContentType("text/xml");  
        response.setCharacterEncoding("UTF-8");  
        response.setHeader("Cache-Control", "no-cache");  
        if ( ((long)fUploadStatus.getReadTotalSize() == (long)fUploadStatus.getUploadTotalSize()) || (fUploadStatus.getCancel() == true)){  
        response.getWriter().write(fUploadStatus.getStatus().toString()+"success");  
        }else{  
            response.getWriter().write(fUploadStatus.getStatus().toString()+"<div class=\"prog-border\"><div class=\"prog-bar\" style=\"width: "  
                                + percentComplete + "%;\"></div></div>");  
        }  
    }  
    /** 
     * 處理取消文件上傳 
     * @param request 
     * @param response 
     * @throws IOException 
     */  
    private void processCancelFileUpload(HttpServletRequest request,HttpServletResponse response) throws IOException{  
        FileUploadStatus fUploadStatus=(FileUploadStatus)request.getSession().getAttribute(UPLOAD_STATUS);  
        fUploadStatus.setCancel(true);  
        request.getSession().setAttribute(UPLOAD_STATUS, fUploadStatus);  
        responseFileUploadStatusPoll(request,response);  
  
    }  
      
    /** 
     * 在上傳文件列表中查找與文件名相關的id 
     * @param request 
     * @param fileName 文件名 
     * @return 找到返回id,否則返回-1 
     */  
    private int findFileIdInFileUploadedList(HttpServletRequest request,String fileName){  
        FileUploadStatus fileUploadStatus=takeOutFileUploadStatusBean(request.getSession());  
        for(int i=0;i<fileUploadStatus.getUploadFileUrlList().size();i++){  
            if (fileName.equals((String)fileUploadStatus.getUploadFileUrlList().get(i))){  
                return i;  
            }  
        }  
        return -1;  
    }  
      
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
        doPost(request,response);  
    }     
      
  
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
        boolean isMultipart = ServletFileUpload.isMultipartContent(request);  
          
        if (isMultipart) {  
            processFileUpload(request,response);  
        }else{  
            request.setCharacterEncoding("UTF-8");  
              
            if (request.getParameter("uploadStatus")!=null){  
                responseFileUploadStatusPoll(request,response);  
            }  
            if (request.getParameter("cancelUpload")!=null){  
                processCancelFileUpload(request,response);  
            }  
        }  
          
    }                 
}

至此,服務器端的代碼已經基本完成。

 

5. 客戶端實現

由於在上傳文件時需要在同一頁面顯示對應的進度條控件,因此,在提交表單時當前頁面不能被刷新。我們可以通過將表單提交至一個隱藏的 iframe 中來實現。關於Ajax的技術前面講過,這裏就不再細說,直接給出源代碼如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">  
<html>  
<head>  
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">  
<title>基於Ajax的上傳文件顯示進度條</title>  
 <style>  
  .prog-border {  
  height: 15px;  
  width: 205px;  
  background: #fff;  
  border: 1px solid #000;  
  margin: 0;  
  padding: 0;  
  }  
  .prog-bar {  
  height: 11px;  
  margin: 2px;  
  padding: 0px;  
  background: #178399;  
  font-size: 10pt;  
  }  
  body{  
    font-family: Arial, Helvetica, sans-serif;  
    font-size: 10pt;  
  }  
  </style>  
<script language="javascript" type="text/javascript">  
<!--  
    //var userName=document.getElementById("userName").value;  
    //創建跨瀏覽器的XMLHttpRequest對象  
    var timer;  
function startListener(){  
    var xmlhttp;  
    try{  
    //IE 5.0   
        xmlhttp = new ActiveXObject('Msxm12.XMLHTTP');  
    }catch(e){  
        try{  
        //IE 5.5 及更高版本  
            xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');  
        }catch(e){  
            try{  
            //其他瀏覽器  
                xmlhttp = new XMLHttpRequest();  
            }catch(e){}  
        }  
    }  
    var progressStatusText = document.getElementById("progressBar");  
    xmlhttp.open("get","UploadServlet?uploadStatus=true",true);  
    /**此處Header設置非常重要,必須設置Content-type類型,負責會報錯誤  
    */  
     xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");  
     xmlhttp.onreadystatechange = function(){  
        if(xmlhttp.readyState == 4){  
            if(xmlhttp.status == 200){  
            progressStatusText.innerHTML = "";  
            progressStatusText.innerHTML = xmlhttp.responseText;  
            var temp = xmlhttp.responseText.indexOf("success");  
            if (  temp > 0 ){  
            window.clearTimeout(timer);  
            }else{  
            timer = window.setTimeout(startListener,1000);  
            }  
            }  
        }  
    }  
    xmlhttp.send(null);  
}  
function startUpload(){  
    timer = window.setTimeout(startListener,1000);  
    return true;  
}  
function cancelUpload(){  
    var xmlhttp;  
    try{  
    //IE 5.0   
        xmlhttp = new ActiveXObject('Msxm12.XMLHTTP');  
    }catch(e){  
        try{  
        //IE 5.5 及更高版本  
            xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');  
        }catch(e){  
            try{  
            //其他瀏覽器  
                xmlhttp = new XMLHttpRequest();  
            }catch(e){}  
        }  
    }  
    var progressStatusText = document.getElementById("progressBar");  
    xmlhttp.open("get","UploadServlet?cancelUpload=true",true);  
     xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");  
    //xmlhttp.setRequestHeader("Content-type", "multipart/form-data");  
    xmlhttp.onreadystatechange = function(){  
        if(xmlhttp.readyState == 4){  
            if(xmlhttp.status == 200){  
            progressStatusText.innerHTML = "";  
            progressStatusText.innerHTML = xmlhttp.responseText;  
            }  
        }  
    }  
    xmlhttp.send(null);  
    return false;  
}  
//-->  
</script>  
</head>  
<body>  
<div id="controlPanel">  
    <!-- 這個是隱藏的<iframe>作爲表單提交後處理的後臺目標  
        通過表單form的target屬性指定該<iframe>將返回信息顯示在<iframe>框架中  
  -->  
  <iframe id='target_upload' name='target_upload' src='' style='display: none'></iframe>  
    <form id="fileUploadForm" name="fileUploadForm" action="UploadServlet"   
        enctype="multipart/form-data" method="post" οnsubmit="return startUpload();" target="target_upload">  
    <input type="file" name="file" id="file" size="40"/><br>  
    <input type="submit" name="uploadButton" id="uploadButton" value="開始上傳"/>  
    <input type="button" name="cancelUploadButton" id="cancelUploadButton" value="取消上傳" οnclick="return cancelUpload();"/><br>  
    </form>     
    <div id="progressBar">  
   </div>    
</div>  
</body>  
</html>

 至此,整個文件上傳的實現到此完成,讀者可以在此基礎上,發揮自己的創新能力,去完善此實例。

Good Luck!

http://plkong.iteye.com/blog/238159
發佈了1 篇原創文章 · 獲贊 21 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章