基於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