基于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。其源码如下:
- packagecom.ncu.upload;
- importjava.util.*;
- publicclassFileUploadStatus{//上传总量
- privatelonguploadTotalSize=0;//读取上传总量
- privatelongreadTotalSize=0;//当前上传文件号
- privateintcurrentUploadFileNum=0;//成功读取上传文件数
- privateintsuccessUploadFileCount=0;//状态
- privateStringstatus="";//处理起始时间
- privatelongprocessStartTime=0l;//处理终止时间
- privatelongprocessEndTime=0l;//处理执行时间
- privatelongprocessRunningTime=0l;//上传文件URL列表
- privateListuploadFileUrlList=newArrayList();//取消上传
- privatebooleancancel=false;//上传base目录
- privateStringbaseDir="";
- publicStringgetBaseDir(){returnbaseDir;
- }publicvoidsetBaseDir(StringbaseDir){
- this.baseDir=baseDir;}
- publicbooleangetCancel(){returncancel;
- }publicvoidsetCancel(booleancancel){
- this.cancel=cancel;}
- publicListgetUploadFileUrlList(){returnuploadFileUrlList;
- }publicvoidsetUploadFileUrlList(ListuploadFileUrlList){
- this.uploadFileUrlList=uploadFileUrlList;}
- publiclonggetProcessRunningTime(){returnprocessRunningTime;
- }publicvoidsetProcessRunningTime(longprocessRunningTime){
- this.processRunningTime=processRunningTime;}
- publiclonggetProcessEndTime(){returnprocessEndTime;
- }publicvoidsetProcessEndTime(longprocessEndTime){
- this.processEndTime=processEndTime;}
- publiclonggetProcessStartTime(){returnprocessStartTime;
- }publicvoidsetProcessStartTime(longprocessStartTime){
- this.processStartTime=processStartTime;}
- publiclonggetReadTotalSize(){returnreadTotalSize;
- }publicvoidsetReadTotalSize(longreadTotalSize){
- this.readTotalSize=readTotalSize;}
- publicintgetSuccessUploadFileCount(){returnsuccessUploadFileCount;
- }publicvoidsetSuccessUploadFileCount(intsuccessUploadFileCount){
- this.successUploadFileCount=successUploadFileCount;}
- publicintgetCurrentUploadFileNum(){returncurrentUploadFileNum;
- }publicvoidsetCurrentUploadFileNum(intcurrentUploadFileNum){
- this.currentUploadFileNum=currentUploadFileNum;}
- publicStringgetStatus(){returnstatus;
- }publicvoidsetStatus(Stringstatus){
- this.status=status;}
- publiclonggetUploadTotalSize(){returnuploadTotalSize;
- }publicvoidsetUploadTotalSize(longuploadTotalSize){
- this.uploadTotalSize=uploadTotalSize;}
- }
由于要在客户端要显示进度条,所以在上传过程中服务器端需要监视和维护上传状态的信息,此过程需要处理的数据信息是:不断更新Session中保存的FileUploadStatus实例的信息,如:已经上传的字节数,上传文件的总大小等。FileUpload现在的1.2版本为监视上传进度提供了内建的支持,可以直接继承类ProgressListener,然后重载update()方法,在该方法中添加自己要处理的代码,最后在文件上传处理代码(后面会讲到)中通过为ServletFileUpload对象注册创建的监听类。监听类UploadListener的源代码如下:
- packagecom.ncu.upload;
- importjavax.servlet.http.HttpSession;
- importorg.apache.commons.fileupload.ProgressListener;
- publicclassUploadListenerimplementsProgressListener{
- privateHttpSessionsession=null;
- publicUploadListener(HttpSessionsession){this.session=session;
- }/**
- *更新状态*@parampBytesRead读取字节总数
- *@parampContentLength数据总长度*@parampItems当前正在被读取的field号
- */publicvoidupdate(longpBytesRead,longpContentLength,intpItems){
- FileUploadStatusfuploadStatus=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类。源代码如下。
- packagecom.ncu.upload;
- importjava.io.*;importjava.util.List;
- importjavax.servlet.ServletException;
- importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;
- importjavax.servlet.http.HttpSession;
- importorg.apache.commons.fileupload.FileItem;importorg.apache.commons.fileupload.FileUploadException;
- importorg.apache.commons.fileupload.disk.DiskFileItemFactory;importorg.apache.commons.fileupload.servlet.*;
- /**
- *ServletimplementationclassforServlet:UploadServlet*
- */publicclassUploadServletextendsjavax.servlet.http.HttpServletimplementsjavax.servlet.Servlet{
- staticfinallongserialVersionUID=1L;
- publicstaticfinalStringUPLOAD_STATUS="UPLOAD_STATUS";publicstaticfinalStringUPLOAD_DIR="/upload";
- publicUploadServlet(){
- super();}
- /**
- *从文件路径中取出文件名*@paramfilePath
- *@return*/
- privateStringtakeOutFileName(StringfilePath){intpos=filePath.lastIndexOf(File.separator);
- if(pos>0){returnfilePath.substring(pos+1);
- }else{
- returnfilePath;}
- }
- /***从request中取出FileUploadStatusBean
- *@paramrequest*@return
- */publicstaticFileUploadStatustakeOutFileUploadStatusBean(HttpSessionsession){
- Objectobj=session.getAttribute(UPLOAD_STATUS);if(obj!=null){
- return(FileUploadStatus)obj;}
- else{returnnull;
- }}
- /**
- *把FileUploadStatusBean保存到session*@paramrequest
- *@paramuploadStatusBean*/
- publicstaticvoidstoreFileUploadStatusBean(HttpSessionsession,
- FileUploadStatusuploadStatusBean){session.setAttribute(UPLOAD_STATUS,uploadStatusBean);
- }
- /***删除已经上传的文件
- *@paramrequest*/
- privatevoiddeleteUploadedFile(HttpServletRequestrequest){FileUploadStatusfUploadStatus=takeOutFileUploadStatusBean(request.getSession());
- for(inti=0;i
File.separator+fUploadStatus.getUploadFileUrlList().get(i));uploadedFile.delete(); - }fUploadStatus.getUploadFileUrlList().clear();
- fUploadStatus.setStatus("删除已上传的文件");storeFileUploadStatusBean(request.getSession(),fUploadStatus);
- }
- /***上传过程中出错处理
- *@paramrequest*@paramerrMsg
- *@throwsIOException*@throwsServletException
- */privatevoiduploadExceptionHandle(
- HttpServletRequestrequest,StringerrMsg)throwsServletException,IOException{
- //首先删除已经上传的文件deleteUploadedFile(request);
- FileUploadStatusfUploadStatus=takeOutFileUploadStatusBean(request.getSession());fUploadStatus.setStatus(errMsg);
- storeFileUploadStatusBean(request.getSession(),fUploadStatus);}
- /**
- *初始化文件上传状态Bean*@paramrequest
- *@return*/
- privateFileUploadStatusinitFileUploadStatusBean(HttpServletRequestrequest){FileUploadStatusfUploadStatus=newFileUploadStatus();
- fUploadStatus.setStatus("正在准备处理");fUploadStatus.setUploadTotalSize(request.getContentLength());
- fUploadStatus.setProcessStartTime(System.currentTimeMillis());fUploadStatus.setBaseDir(request.getContextPath()+UPLOAD_DIR);
- returnfUploadStatus;}
- /**
- *处理文件上传*@paramrequest
- *@paramresponse*@throwsIOException
- *@throwsServletException*/
- privatevoidprocessFileUpload(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{DiskFileItemFactoryfactory=newDiskFileItemFactory();
- //设置内存阀值,超过后写入临时文件//factory.setSizeThreshold(10240000*5);
- //设置临时文件存储位置//factory.setRepository(newFile(request.getRealPath("/upload/temp")));
- ServletFileUploadupload=newServletFileUpload(factory);//设置单个文件的最大上传size
- //upload.setFileSizeMax(10240000*5);//设置整个request的最大size
- //upload.setSizeMax(10240000*5);//注册监听类
- upload.setProgressListener(newUploadListener(request.getSession()));//保存初始化后的FileUploadStatusBean
- storeFileUploadStatusBean(request.getSession(),initFileUploadStatusBean(request));
- try{Listitems=upload.parseRequest(request);
- //处理文件上传for(inti=0;i
FileItemitem=(FileItem)items.get(i); - //取消上传if(takeOutFileUploadStatusBean(request.getSession()).getCancel()){
- deleteUploadedFile(request);break;
- }//保存文件
- elseif(!item.isFormField()&&item.getName().length()>0){StringfileName=takeOutFileName(item.getName());
- FileuploadedFile=newFile(request.getRealPath(UPLOAD_DIR)+File.separator+fileName);item.write(uploadedFile);
- //更新上传文件列表FileUploadStatusfUploadStatus=takeOutFileUploadStatusBean(request.getSession());
- fUploadStatus.getUploadFileUrlList().add(fileName);storeFileUploadStatusBean(request.getSession(),fUploadStatus);
- Thread.sleep(500);}
- }
- }catch(FileUploadExceptione){e.printStackTrace();
- //uploadExceptionHandle(request,"上传文件时发生错误:"+e.getMessage());}catch(Exceptione){
- //TODOAuto-generatedcatchblocke.printStackTrace();
- //uploadExceptionHandle(request,"保存上传文件时发生错误:"+e.getMessage());}
- }
- /***回应上传状态查询
- *@paramrequest*@paramresponse
- *@throwsIOException*/
- privatevoidresponseFileUploadStatusPoll(HttpServletRequestrequest,HttpServletResponseresponse)throwsIOException{FileUploadStatusfUploadStatus=(FileUploadStatus)request.getSession().getAttribute(UPLOAD_STATUS);
- //计算上传完成的百分比longpercentComplete=(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()+"
vclass=\"prog-border\"> +percentComplete+"%;\">");} - }/**
- *处理取消文件上传*@paramrequest
- *@paramresponse*@throwsIOException
- */privatevoidprocessCancelFileUpload(HttpServletRequestrequest,HttpServletResponseresponse)throwsIOException{
- FileUploadStatusfUploadStatus=(FileUploadStatus)request.getSession().getAttribute(UPLOAD_STATUS);fUploadStatus.setCancel(true);
- request.getSession().setAttribute(UPLOAD_STATUS,fUploadStatus);responseFileUploadStatusPoll(request,response);
- }
- /**
- *在上传文件列表中查找与文件名相关的id*@paramrequest
- *@paramfileName文件名*@return 找到返回id,否则返回-1
- */privateintfindFileIdInFileUploadedList(HttpServletRequestrequest,StringfileName){
- FileUploadStatusfileUploadStatus=takeOutFileUploadStatusBean(request.getSession());for(inti=0;i
if(fileName.equals((String)fileUploadStatus.getUploadFileUrlList().get(i))){returni; - }}
- return-1;}
- protectedvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{
- doPost(request,response);}
- protectedvoiddoPost(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{booleanisMultipart=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的技术前面讲过,这里就不再细说,直接给出源代码如下:
至此,整个文件上传的实现到此完成,读者可以在此基础上,发挥自己的创新能力,去完善此实例。