其整个功能时序图如图所示。
简单的说,要实现在客户端显示进度条,需要做的是:当客户端提交上传文件请求后,服务器在上传文件的过程中,将上传进度情况保存到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<fUploadStatus.getUploadFileUrlList().size();i++){
- FileuploadedFile=newFile(request.getRealPath(UPLOAD_DIR)+
- 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<items.size();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-generatedcatchblock
- e.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()+"<divclass=\"prog-border\"><divclass=\"prog-bar\"style=\"width:"
- +percentComplete+"%;\"></div></div>");
- }
- }
- /**
- *处理取消文件上传
- *@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<fileUploadStatus.getUploadFileUrlList().size();i++){
- if(fileName.equals((String)fileUploadStatus.getUploadFileUrlList().get(i))){
- returni;
- }
- }
- return-1;
- }
- protectedvoiddoGet(HttpServletRequestrequest,IOException{
- doPost(request,response);
- }
- protectedvoiddoPost(HttpServletRequestrequest,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的技术前面讲过,这里就不再细说,直接给出源代码如下:
- <!DOCTYPEhtmlPUBLIC"-//W3C//DTDHTML4.01Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">
- <html>
- <head>
- <Meta@H_610_3018@http-equiv="Content-Type"@H_610_3018@content="text/html;charset=UTF-8">
- <title>基于Ajax的上传文件显示进度条</title>
- <style>
- .prog-border{
- height:15px;
- width:205px;
- background:#fff;
- border:1pxsolid#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@H_610_3018@language="javascript"@H_610_3018@type="text/javascript">
- <!--
- //var@H_610_3018@userName=document.getElementById("userName").value;
- //创建跨浏览器的XMLHttpRequest对象
- vartimer;
- functionstartListener(){
- varxmlhttp;
- try{
- //IE5.0
- @H_610_3018@xmlhttp=newActiveXObject('Msxm12.XMLHTTP');
- }catch(e){
- try{
- //IE5.5及更高版本
- @H_610_3018@xmlhttp=newActiveXObject('Microsoft.XMLHTTP');
- }catch(e){
- try{
- //其他浏览器
- @H_610_3018@xmlhttp=newXMLHttpRequest();
- }catch(e){}
- }
- }
- var@H_610_3018@progressStatusText=document.getElementById("progressBar");
- xmlhttp.open("get","UploadServlet?@H_610_3018@uploadStatus=true",true);
- /**此处Header设置非常重要,必须设置Content-type类型,负责会报错误
- */
- xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
- @H_610_3018@xmlhttp.onreadystatechange=function(){
- if(@H_610_3018@xmlhttp.readyState==4){
- if(@H_610_3018@xmlhttp.status==200){
- @H_610_3018@progressStatusText.innerHTML="";
- @H_610_3018@progressStatusText.innerHTML=xmlhttp.responseText;
- var@H_610_3018@temp=xmlhttp.responseText.indexOf("success");
- if(temp>0){
- window.clearTimeout(timer);
- }else{
- @H_610_3018@timer=window.setTimeout(startListener,1000);
- }
- }
- }
- }
- xmlhttp.send(null);
- }
- functionstartUpload(){
- @H_610_3018@timer=window.setTimeout(startListener,1000);
- returntrue;
- }
- functioncancelUpload(){
- varxmlhttp;
- try{
- //IE5.0
- @H_610_3018@xmlhttp=newActiveXObject('Msxm12.XMLHTTP');
- }catch(e){
- try{
- //IE5.5及更高版本
- @H_610_3018@xmlhttp=newActiveXObject('Microsoft.XMLHTTP');
- }catch(e){
- try{
- //其他浏览器
- @H_610_3018@xmlhttp=newXMLHttpRequest();
- }catch(e){}
- }
- }
- var@H_610_3018@progressStatusText=document.getElementById("progressBar");
- xmlhttp.open("get","UploadServlet?@H_610_3018@cancelUpload=true",true);
- xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
- //xmlhttp.setRequestHeader("Content-type","multipart/form-data");
- @H_610_3018@xmlhttp.onreadystatechange=function(){
- if(@H_610_3018@xmlhttp.readyState==4){
- if(@H_610_3018@xmlhttp.status==200){
- @H_610_3018@progressStatusText.innerHTML="";
- @H_610_3018@progressStatusText.innerHTML=xmlhttp.responseText;
- }
- }
- }
- xmlhttp.send(null);
- returnfalse;
- }
- //-->
- </script>
- </head>
- <body>
- <div@H_610_3018@id="controlPanel">
- <!--这个是隐藏的<iframe>作为表单提交后处理的后台目标
- 通过表单form的target属性指定该<iframe>将返回信息显示在<iframe>框架中
- -->
- <iframe@H_610_3018@id='target_upload'@H_610_3018@name='target_upload'@H_610_3018@src=''@H_610_3018@style='display:none'></iframe>
- <form@H_610_3018@id="fileUploadForm"@H_610_3018@name="fileUploadForm"@H_610_3018@action="UploadServlet"
- @H_610_3018@enctype="multipart/form-data"@H_610_3018@method="post"@H_610_3018@onsubmit="returnstartUpload();"@H_610_3018@target="target_upload">
- <input@H_610_3018@type="file"@H_610_3018@name="file"@H_610_3018@id="file"@H_610_3018@size="40"/><br>
- <input@H_610_3018@type="submit"@H_610_3018@name="uploadButton"@H_610_3018@id="uploadButton"@H_610_3018@value="开始上传"/>
- <input@H_610_3018@type="button"@H_610_3018@name="cancelUploadButton"@H_610_3018@id="cancelUploadButton"@H_610_3018@value="取消上传"@H_610_3018@onclick="returncancelUpload();"/><br>
- </form>
- <div@H_610_3018@id="progressBar">
- </div>
- </div>
- </body>
- </html>
至此,整个文件上传的实现到此完成,读者可以在此基础上,发挥自己的创新能力,去完善此实例。
Good Luck!