基于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。其源码如下:

Java代码
  1. packagecom.ncu.upload;
  2. importjava.util.*;
  3. publicclassFileUploadStatus{
  4. //上传总量
  5. privatelonguploadTotalSize=0;
  6. //读取上传总量
  7. privatelongreadTotalSize=0;
  8. //当前上传文件
  9. privateintcurrentUploadFileNum=0;
  10. //成功读取上传文件
  11. privateintsuccessUploadFileCount=0;
  12. //状态
  13. privateStringstatus="";
  14. //处理起始时间
  15. privatelongprocessStartTime=0l;
  16. //处理终止时间
  17. privatelongprocessEndTime=0l;
  18. //处理执行时间
  19. privatelongprocessRunningTime=0l;
  20. //上传文件URL列表
  21. privateListuploadFileUrlList=newArrayList();
  22. //取消上传
  23. privatebooleancancel=false;
  24. //上传base目录
  25. privateStringbaseDir="";
  26. publicStringgetBaseDir(){
  27. returnbaseDir;
  28. }
  29. publicvoidsetBaseDir(StringbaseDir){
  30. this.baseDir=baseDir;
  31. }
  32. publicbooleangetCancel(){
  33. returncancel;
  34. }
  35. publicvoidsetCancel(booleancancel){
  36. this.cancel=cancel;
  37. }
  38. publicListgetUploadFileUrlList(){
  39. returnuploadFileUrlList;
  40. }
  41. publicvoidsetUploadFileUrlList(ListuploadFileUrlList){
  42. this.uploadFileUrlList=uploadFileUrlList;
  43. }
  44. publiclonggetProcessRunningTime(){
  45. returnprocessRunningTime;
  46. }
  47. publicvoidsetProcessRunningTime(longprocessRunningTime){
  48. this.processRunningTime=processRunningTime;
  49. }
  50. publiclonggetProcessEndTime(){
  51. returnprocessEndTime;
  52. }
  53. publicvoidsetProcessEndTime(longprocessEndTime){
  54. this.processEndTime=processEndTime;
  55. }
  56. publiclonggetProcessStartTime(){
  57. returnprocessStartTime;
  58. }
  59. publicvoidsetProcessStartTime(longprocessStartTime){
  60. this.processStartTime=processStartTime;
  61. }
  62. publiclonggetReadTotalSize(){
  63. returnreadTotalSize;
  64. }
  65. publicvoidsetReadTotalSize(longreadTotalSize){
  66. this.readTotalSize=readTotalSize;
  67. }
  68. publicintgetSuccessUploadFileCount(){
  69. returnsuccessUploadFileCount;
  70. }
  71. publicvoidsetSuccessUploadFileCount(intsuccessUploadFileCount){
  72. this.successUploadFileCount=successUploadFileCount;
  73. }
  74. publicintgetCurrentUploadFileNum(){
  75. returncurrentUploadFileNum;
  76. }
  77. publicvoidsetCurrentUploadFileNum(intcurrentUploadFileNum){
  78. this.currentUploadFileNum=currentUploadFileNum;
  79. }
  80. publicStringgetStatus(){
  81. returnstatus;
  82. }
  83. publicvoidsetStatus(Stringstatus){
  84. this.status=status;
  85. }
  86. publiclonggetUploadTotalSize(){
  87. returnuploadTotalSize;
  88. }
  89. publicvoidsetUploadTotalSize(longuploadTotalSize){
  90. this.uploadTotalSize=uploadTotalSize;
  91. }
  92. }

由于要在客户端要显示进度条,所以在上传过程中服务器端需要监视和维护上传状态的信息,此过程需要处理的数据信息是:不断更新Session中保存的FileUploadStatus实例的信息,如:已经上传的字节数,上传文件的总大小等。FileUpload现在的1.2版本为监视上传进度提供了内建的支持,可以直接继承类ProgressListener,然后重载update()方法,在该方法添加自己要处理的代码,最后在文件上传处理代码(后面会讲到)中通过为ServletFileUpload对象注册创建的监听类。监听类UploadListener的源代码如下:

Java代码
  1. packagecom.ncu.upload;
  2. importjavax.servlet.http.HttpSession;
  3. importorg.apache.commons.fileupload.ProgressListener;
  4. publicclassUploadListenerimplementsProgressListener{
  5. privateHttpSessionsession=null;
  6. publicUploadListener(HttpSessionsession){
  7. this.session=session;
  8. }
  9. /**
  10. *更新状态
  11. *@parampBytesRead读取字节总数
  12. *@parampContentLength数据总长度
  13. *@parampItems当前正在被读取的field号
  14. */
  15. publicvoidupdate(longpBytesRead,longpContentLength,intpItems){
  16. FileUploadStatusfuploadStatus=UploadServlet.takeOutFileUploadStatusBean(this.session);
  17. fuploadStatus.setUploadTotalSize(pContentLength);
  18. //读取完成
  19. if(pContentLength==-1){
  20. fuploadStatus.setStatus("完成对"+pItems+"个文件的读取:读取了"+pBytesRead+"/"+pContentLength+"bytes.");
  21. fuploadStatus.setReadTotalSize(pBytesRead);
  22. fuploadStatus.setCurrentUploadFileNum(pItems);
  23. fuploadStatus.setProcessEndTime(System.currentTimeMillis());
  24. fuploadStatus.setProcessRunningTime(fuploadStatus.getProcessEndTime());
  25. }else{//读取过程中
  26. fuploadStatus.setStatus("当前正在处理第"+pItems+"个文件:已经读取了"+pBytesRead+"/"+pContentLength+"bytes.");
  27. fuploadStatus.setReadTotalSize(pBytesRead);
  28. fuploadStatus.setCurrentUploadFileNum(pItems);
  29. fuploadStatus.setProcessRunningTime(System.currentTimeMillis());
  30. }
  31. //System.out.println("已经读取:"+pBytesRead);
  32. UploadServlet.storeFileUploadStatusBean(this.session,fuploadStatus);
  33. }
  34. }

有了前面两个类的基础,下来我们可以动手去实现真正处理整个操作Servlet类。源代码如下。

Java代码
  1. packagecom.ncu.upload;
  2. importjava.io.*;
  3. importjava.util.List;
  4. importjavax.servlet.ServletException;
  5. importjavax.servlet.http.HttpServletRequest;
  6. importjavax.servlet.http.HttpServletResponse;
  7. importjavax.servlet.http.HttpSession;
  8. importorg.apache.commons.fileupload.FileItem;
  9. importorg.apache.commons.fileupload.FileUploadException;
  10. importorg.apache.commons.fileupload.disk.DiskFileItemFactory;
  11. importorg.apache.commons.fileupload.servlet.*;
  12. /**
  13. *ServletimplementationclassforServlet:UploadServlet
  14. *
  15. */
  16. publicclassUploadServletextendsjavax.servlet.http.HttpServletimplementsjavax.servlet.Servlet{
  17. staticfinallongserialVersionUID=1L;
  18. publicstaticfinalStringUPLOAD_STATUS="UPLOAD_STATUS";
  19. publicstaticfinalStringUPLOAD_DIR="/upload";
  20. publicUploadServlet(){
  21. super();
  22. }
  23. /**
  24. *从文件路径中取出文件
  25. *@paramfilePath
  26. *@return
  27. */
  28. privateStringtakeOutFileName(StringfilePath){
  29. intpos=filePath.lastIndexOf(File.separator);
  30. if(pos>0){
  31. returnfilePath.substring(pos+1);
  32. }
  33. else{
  34. returnfilePath;
  35. }
  36. }
  37. /**
  38. *从request中取出FileUploadStatusBean
  39. *@paramrequest
  40. *@return
  41. */
  42. publicstaticFileUploadStatustakeOutFileUploadStatusBean(HttpSessionsession){
  43. Objectobj=session.getAttribute(UPLOAD_STATUS);
  44. if(obj!=null){
  45. return(FileUploadStatus)obj;
  46. }
  47. else{
  48. returnnull;
  49. }
  50. }
  51. /**
  52. *把FileUploadStatusBean保存到session
  53. *@paramrequest
  54. *@paramuploadStatusBean
  55. */
  56. publicstaticvoidstoreFileUploadStatusBean(
  57. HttpSessionsession,
  58. FileUploadStatusuploadStatusBean){
  59. session.setAttribute(UPLOAD_STATUS,uploadStatusBean);
  60. }
  61. /**
  62. *删除已经上传文件
  63. *@paramrequest
  64. */
  65. privatevoiddeleteUploadedFile(HttpServletRequestrequest){
  66. FileUploadStatusfUploadStatus=takeOutFileUploadStatusBean(request.getSession());
  67. for(inti=0;i<fUploadStatus.getUploadFileUrlList().size();i++){
  68. FileuploadedFile=newFile(request.getRealPath(UPLOAD_DIR)+
  69. File.separator+fUploadStatus.getUploadFileUrlList().get(i));
  70. uploadedFile.delete();
  71. }
  72. fUploadStatus.getUploadFileUrlList().clear();
  73. fUploadStatus.setStatus("删除上传文件");
  74. storeFileUploadStatusBean(request.getSession(),fUploadStatus);
  75. }
  76. /**
  77. *上传过程中出错处理
  78. *@paramrequest
  79. *@paramerrMsg
  80. *@throwsIOException
  81. *@throwsServletException
  82. */
  83. privatevoiduploadExceptionHandle(
  84. HttpServletRequestrequest,
  85. StringerrMsg)throwsServletException,IOException{
  86. //首先删除已经上传文件
  87. deleteUploadedFile(request);
  88. FileUploadStatusfUploadStatus=takeOutFileUploadStatusBean(request.getSession());
  89. fUploadStatus.setStatus(errMsg);
  90. storeFileUploadStatusBean(request.getSession(),fUploadStatus);
  91. }
  92. /**
  93. *初始化文件上传状态Bean
  94. *@paramrequest
  95. *@return
  96. */
  97. privateFileUploadStatusinitFileUploadStatusBean(HttpServletRequestrequest){
  98. FileUploadStatusfUploadStatus=newFileUploadStatus();
  99. fUploadStatus.setStatus("正在准备处理");
  100. fUploadStatus.setUploadTotalSize(request.getContentLength());
  101. fUploadStatus.setProcessStartTime(System.currentTimeMillis());
  102. fUploadStatus.setBaseDir(request.getContextPath()+UPLOAD_DIR);
  103. returnfUploadStatus;
  104. }
  105. /**
  106. *处理文件上传
  107. *@paramrequest
  108. *@paramresponse
  109. *@throwsIOException
  110. *@throwsServletException
  111. */
  112. privatevoidprocessFileUpload(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{
  113. DiskFileItemFactoryfactory=newDiskFileItemFactory();
  114. //设置内存阀值,超过后写入临时文件
  115. //factory.setSizeThreshold(10240000*5);
  116. //设置临时文件存储位置
  117. //factory.setRepository(newFile(request.getRealPath("/upload/temp")));
  118. ServletFileUploadupload=newServletFileUpload(factory);
  119. //设置单个文件的最大上传size
  120. //upload.setFileSizeMax(10240000*5);
  121. //设置整个request的最大size
  122. //upload.setSizeMax(10240000*5);
  123. //注册监听类
  124. upload.setProgressListener(newUploadListener(request.getSession()));
  125. //保存初始化后的FileUploadStatusBean
  126. storeFileUploadStatusBean(request.getSession(),initFileUploadStatusBean(request));
  127. try{
  128. Listitems=upload.parseRequest(request);
  129. //处理文件上传
  130. for(inti=0;i<items.size();i++){
  131. FileItemitem=(FileItem)items.get(i);
  132. //取消上传
  133. if(takeOutFileUploadStatusBean(request.getSession()).getCancel()){
  134. deleteUploadedFile(request);
  135. break;
  136. }
  137. //保存文件
  138. elseif(!item.isFormField()&&item.getName().length()>0){
  139. StringfileName=takeOutFileName(item.getName());
  140. FileuploadedFile=newFile(request.getRealPath(UPLOAD_DIR)+File.separator+fileName);
  141. item.write(uploadedFile);
  142. //更新上传文件列表
  143. FileUploadStatusfUploadStatus=takeOutFileUploadStatusBean(request.getSession());
  144. fUploadStatus.getUploadFileUrlList().add(fileName);
  145. storeFileUploadStatusBean(request.getSession(),fUploadStatus);
  146. Thread.sleep(500);
  147. }
  148. }
  149. }catch(FileUploadExceptione){
  150. e.printStackTrace();
  151. //uploadExceptionHandle(request,"上传文件时发生错误:"+e.getMessage());
  152. }catch(Exceptione){
  153. //TODOAuto-generatedcatchblock
  154. e.printStackTrace();
  155. //uploadExceptionHandle(request,"保存上传文件时发生错误:"+e.getMessage());
  156. }
  157. }
  158. /**
  159. *回应上传状态查询
  160. *@paramrequest
  161. *@paramresponse
  162. *@throwsIOException
  163. */
  164. privatevoidresponseFileUploadStatusPoll(HttpServletRequestrequest,HttpServletResponseresponse)throwsIOException{
  165. FileUploadStatusfUploadStatus=(FileUploadStatus)request.getSession().getAttribute(UPLOAD_STATUS);
  166. //计算上传完成的百分比
  167. longpercentComplete=(long)Math.floor(((double)fUploadStatus.getReadTotalSize()/(double)fUploadStatus.getUploadTotalSize())*100.0);
  168. System.out.println("com:"+percentComplete);
  169. response.setContentType("text/xml");
  170. response.setCharacterEncoding("UTF-8");
  171. response.setHeader("Cache-Control","no-cache");
  172. if(((long)fUploadStatus.getReadTotalSize()==(long)fUploadStatus.getUploadTotalSize())||(fUploadStatus.getCancel()==true)){
  173. response.getWriter().write(fUploadStatus.getStatus().toString()+"success");
  174. }else{
  175. response.getWriter().write(fUploadStatus.getStatus().toString()+"<divclass=\"prog-border\"><divclass=\"prog-bar\"style=\"width:"
  176. +percentComplete+"%;\"></div></div>");
  177. }
  178. }
  179. /**
  180. *处理取消文件上传
  181. *@paramrequest
  182. *@paramresponse
  183. *@throwsIOException
  184. */
  185. privatevoidprocessCancelFileUpload(HttpServletRequestrequest,HttpServletResponseresponse)throwsIOException{
  186. FileUploadStatusfUploadStatus=(FileUploadStatus)request.getSession().getAttribute(UPLOAD_STATUS);
  187. fUploadStatus.setCancel(true);
  188. request.getSession().setAttribute(UPLOAD_STATUS,fUploadStatus);
  189. responseFileUploadStatusPoll(request,response);
  190. }
  191. /**
  192. *在上传文件列表中查找与文件名相关的id
  193. *@paramrequest
  194. *@paramfileName文件
  195. *@return 找到返回id,否则返回-1
  196. */
  197. privateintfindFileIdInFileUploadedList(HttpServletRequestrequest,StringfileName){
  198. FileUploadStatusfileUploadStatus=takeOutFileUploadStatusBean(request.getSession());
  199. for(inti=0;i<fileUploadStatus.getUploadFileUrlList().size();i++){
  200. if(fileName.equals((String)fileUploadStatus.getUploadFileUrlList().get(i))){
  201. returni;
  202. }
  203. }
  204. return-1;
  205. }
  206. protectedvoiddoGet(HttpServletRequestrequest,IOException{
  207. doPost(request,response);
  208. }
  209. protectedvoiddoPost(HttpServletRequestrequest,IOException{
  210. booleanisMultipart=ServletFileUpload.isMultipartContent(request);
  211. if(isMultipart){
  212. processFileUpload(request,response);
  213. }else{
  214. request.setCharacterEncoding("UTF-8");
  215. if(request.getParameter("uploadStatus")!=null){
  216. responseFileUploadStatusPoll(request,response);
  217. }
  218. if(request.getParameter("cancelUpload")!=null){
  219. processCancelFileUpload(request,response);
  220. }
  221. }
  222. }
  223. }

至此,服务器端的代码已经基本完成。

5.客户端实现

由于在上传文件时需要在同一页面显示对应的进度条控件,因此,在提交表单时当前页面不能被刷新。我们可以通过将表单提交至一个隐藏的iframe中来实现。关于Ajax的技术前面讲过,这里就不再细说,直接给出源代码如下:

  1. <!DOCTYPEhtmlPUBLIC"-//W3C//DTDHTML4.01Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">
  2. <html>
  3. <head>
  4. <Meta@H_610_3018@http-equiv="Content-Type"@H_610_3018@content="text/html;charset=UTF-8">
  5. <title>基于Ajax的上传文件显示进度条</title>
  6. <style>
  7. .prog-border{
  8. height:15px;
  9. width:205px;
  10. background:#fff;
  11. border:1pxsolid#000;
  12. margin:0;
  13. padding:0;
  14. }
  15. .prog-bar{
  16. height:11px;
  17. margin:2px;
  18. padding:0px;
  19. background:#178399;
  20. font-size:10pt;
  21. }
  22. body{
  23. font-family:Arial,Helvetica,sans-serif;
  24. font-size:10pt;
  25. }
  26. </style>
  27. <script@H_610_3018@language="javascript"@H_610_3018@type="text/javascript">
  28. <!--
  29. //var@H_610_3018@userName=document.getElementById("userName").value;
  30. //创建跨浏览器的XMLHttpRequest对象
  31. vartimer;
  32. functionstartListener(){
  33. varxmlhttp;
  34. try{
  35. //IE5.0
  36. @H_610_3018@xmlhttp=newActiveXObject('Msxm12.XMLHTTP');
  37. }catch(e){
  38. try{
  39. //IE5.5及更高版本
  40. @H_610_3018@xmlhttp=newActiveXObject('Microsoft.XMLHTTP');
  41. }catch(e){
  42. try{
  43. //其他浏览器
  44. @H_610_3018@xmlhttp=newXMLHttpRequest();
  45. }catch(e){}
  46. }
  47. }
  48. var@H_610_3018@progressStatusText=document.getElementById("progressBar");
  49. xmlhttp.open("get","UploadServlet?@H_610_3018@uploadStatus=true",true);
  50. /**此处Header设置非常重要,必须设置Content-type类型,负责会报错误
  51. */
  52. xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
  53. @H_610_3018@xmlhttp.onreadystatechange=function(){
  54. if(@H_610_3018@xmlhttp.readyState==4){
  55. if(@H_610_3018@xmlhttp.status==200){
  56. @H_610_3018@progressStatusText.innerHTML="";
  57. @H_610_3018@progressStatusText.innerHTML=xmlhttp.responseText;
  58. var@H_610_3018@temp=xmlhttp.responseText.indexOf("success");
  59. if(temp>0){
  60. window.clearTimeout(timer);
  61. }else{
  62. @H_610_3018@timer=window.setTimeout(startListener,1000);
  63. }
  64. }
  65. }
  66. }
  67. xmlhttp.send(null);
  68. }
  69. functionstartUpload(){
  70. @H_610_3018@timer=window.setTimeout(startListener,1000);
  71. returntrue;
  72. }
  73. functioncancelUpload(){
  74. varxmlhttp;
  75. try{
  76. //IE5.0
  77. @H_610_3018@xmlhttp=newActiveXObject('Msxm12.XMLHTTP');
  78. }catch(e){
  79. try{
  80. //IE5.5及更高版本
  81. @H_610_3018@xmlhttp=newActiveXObject('Microsoft.XMLHTTP');
  82. }catch(e){
  83. try{
  84. //其他浏览器
  85. @H_610_3018@xmlhttp=newXMLHttpRequest();
  86. }catch(e){}
  87. }
  88. }
  89. var@H_610_3018@progressStatusText=document.getElementById("progressBar");
  90. xmlhttp.open("get","UploadServlet?@H_610_3018@cancelUpload=true",true);
  91. xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
  92. //xmlhttp.setRequestHeader("Content-type","multipart/form-data");
  93. @H_610_3018@xmlhttp.onreadystatechange=function(){
  94. if(@H_610_3018@xmlhttp.readyState==4){
  95. if(@H_610_3018@xmlhttp.status==200){
  96. @H_610_3018@progressStatusText.innerHTML="";
  97. @H_610_3018@progressStatusText.innerHTML=xmlhttp.responseText;
  98. }
  99. }
  100. }
  101. xmlhttp.send(null);
  102. returnfalse;
  103. }
  104. //-->
  105. </script>
  106. </head>
  107. <body>
  108. <div@H_610_3018@id="controlPanel">
  109. <!--这个是隐藏的<iframe>作为表单提交后处理的后台目标
  110. 通过表单form的target属性指定该<iframe>将返回信息显示<iframe>框架中
  111. -->
  112. <iframe@H_610_3018@id='target_upload'@H_610_3018@name='target_upload'@H_610_3018@src=''@H_610_3018@style='display:none'></iframe>
  113. <form@H_610_3018@id="fileUploadForm"@H_610_3018@name="fileUploadForm"@H_610_3018@action="UploadServlet"
  114. @H_610_3018@enctype="multipart/form-data"@H_610_3018@method="post"@H_610_3018@onsubmit="returnstartUpload();"@H_610_3018@target="target_upload">
  115. <input@H_610_3018@type="file"@H_610_3018@name="file"@H_610_3018@id="file"@H_610_3018@size="40"/><br>
  116. <input@H_610_3018@type="submit"@H_610_3018@name="uploadButton"@H_610_3018@id="uploadButton"@H_610_3018@value="开始上传"/>
  117. <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>
  118. </form>
  119. <div@H_610_3018@id="progressBar">
  120. </div>
  121. </div>
  122. </body>
  123. </html>

至此,整个文件上传的实现到此完成,读者可以在此基础上,发挥自己的创新能力,去完善此实例。

Good Luck!

相关文章

JS原生Ajax操作(XMLHttpRequest) GET请求 POST请求 兼容性问题 利用iframe模拟ajax 实现表单提交的返回...
AJAX 每日更新前端基础,如果觉得不错,点个star吧 &#128515; https://github.com/WindrunnerMax/E...
踩坑Axios提交form表单几种格式 前后端分离的开发前后端, 前端使用的vue,后端的安全模块使用的SpringSe...
很早就听闻ajax的名声,但是却一直不知道怎么用,今天自己捣鼓了一下,竟然会用了,哈哈哈哈。 为了防止...
需要在服务器上进行哈 jquery的ajax方法: // jquery请求 $.ajax({ url: &quot;./server/slider.js...
Ajax函数封装ajax.js // Get / Post // 参数 get post // 是否异步 // 如何处理响应数据 // URL // var...