夢幻e家人

          java咖啡
          隨筆 - 15, 文章 - 0, 評論 - 11, 引用 - 0
          數據加載中……

          掌控上傳進度的AJAX Upload

          掌控上傳進度的AJAX Upload

          cleverpig 發表于 2007-01-08 11:12:14
          作者:cleverpig     來源:Matrix
          評論數:83 點擊數:5,066     投票總得分:12 投票總人次:4
          關鍵字:AJAX,upload,monitor

          摘要:

          2006年底Google了一下AJAX Upload實現,結果沒有發現很完整的Java實現。碩果僅存的就是TELIO公司的Pierre-Alexandre發表的《AJAX Upload progress monitor for Commons-FileUpload Example》一文。雖然文中完成Upload工作的是Common-FileUpload組件,但在其代碼中沒有1.2版本所提供的Listener功能,這就對檢測文件上傳情況造成了困難...

          工具箱

          本站收藏
          掌控上傳進度的AJAX Upload

          作者:
          cleverpig

          image
          AJAX——最酷的“沖浪板”



          動機:

                  2006年底Google了一下AJAX Upload實現,結果沒有發現很完整的Java實現。碩果僅存的就是TELIO公司的Pierre-Alexandre發表的《AJAX Upload progress monitor for Commons-FileUpload Example》文中提供的ajax-upload-1.0.war

                  雖然上文中完成Upload工作的是Apache的Common-FileUpload組件,但在其代碼中所使用的FileUpload1.1版本并沒有1.2版本所提供的上傳處理Listener功能,這就對檢測文件上傳情況造成了困難。我想正是這個原因致使Pierre-Alexandre使用了DWR+MonitoredDiskFileItem、MonitoredDiskFileItemFactory類(分別繼承DiskFileItemDiskFileItemFactory)的方式:前者負責在web客戶端進行Remote Call;后者在進行文件數據讀取時統計數據總量、讀取數據量、處理文件總數,并保存于Session中,以供web客戶端通過DWR遠程調用UploadMonitor類的getUploadInfo方法進行輪詢(Poll)。

                  從本人觀點出發,Pierre-Alexandre實現的不足之處:
                  1.沒有用戶取消上傳功能;
                  2.完全的DWR實現,沒有使用Prototype,對于不會使用DWR的開發者來講有一定的知識局限性,而且由于DWR的個性而造成不便將此實現集成到項目中。



          Prototype+Servlet的實現:

          image
          Prototype+Servlet的Example


                  所以出于研究Prototype之目的,本人經過仔細思考,嘗試實現了一個Prototype+Servlet的簡單Example。其工作流程很簡單:
          1.在Form提交上傳文件Field的同時,使用AJAX周期性地從Servlet輪詢上傳狀態信息;
          2.然后,根據此信息更新進度條和相關文字,及時反映文件傳輸狀態;
          3.如果用戶取消上傳操作,則進行相應的現場清理工作:刪除已經上傳的文件,在Form提交頁面中顯示相關信息;
          4.如果上傳完畢,在Form提交頁面中顯示已經上傳的文件內容(或鏈接),也可以與一些AJAX SlideShow應用結合在一起。

          服務器端代碼:

                  Bean序列化/反序列化工作:XmlUnSerializer這個類雖然不能夠通吃任何模樣的Bean,但應付一般的Bean、具有Collection類型屬性的Bean和Bean List來講還是夠用的。
                  {XmlUnSerializer類的核心方法serializeBean和serializeBeanList}:

                  /**
                   * 將bean系列化為UTF-8編碼的xml
                   * @param beanObj
                   * @return
                   * @throws IOException
                   */
                  public static String serializeBean(Object beanObj) throws IOException{
                  …
                  }
                  /**
                   * 將bean列表序列化為UTF-8編碼的xml
                   * @param beanObj
                   * @return
                   * @throws IOException
                   */
                  public static String serializeBeanList(Object beanListObj) throws IOException{
                  …
                  }


                  文件上傳狀態Bean:使用FileUploadStatus這個類記錄文件上傳狀態,并將其作為服務器端與web客戶端之間通信的媒介物:通過對這個類對象進行XML序列化作為服務器回應發送給web客戶端,web客戶端使用JavaScript對其進行反序列化處理獲得JavaScript版本的文件上傳狀態對象。
                  {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="";


                  文件上傳狀態監視工作:使用Common-FileUpload 1.2版本(20070103)。此版本與1.1版的區別在于提供了能夠監視文件上傳情況的ProcessListener接口,使開發者通過FileUploadBase類對象的setProcessListener方法植入自己的Listener,而且實現這個Listener很簡單
                  {FileUploadListener主要方法update}:

                  /**
                   * 更新狀態
                   * @param pBytesRead 讀取字節總數
                   * @param pContentLength 數據總長度
                   * @param pItems 當前正在被讀取的field號
                   */
                  public void update(long pBytesRead, long pContentLength, int pItems){
                          FileUploadStatus fuploadStatus=BackGroundService.takeOutFileUploadStatusBean(this.session);
                          logger.debug("當前正在處理第" + pItems+"個文件");
                          fuploadStatus.setUploadTotalSize(pContentLength);
                          //讀取完成
                      if (pContentLength == -1) {
                         logger.debug("讀取完成:讀取了 " + pBytesRead + " bytes.");
                         fuploadStatus.setStatus("完成對" + pItems+"個文件的讀取:讀取了 " + pBytesRead + " bytes.");
                         fuploadStatus.setReadTotalSize(pBytesRead);
                         fuploadStatus.setSuccessUploadFileCount(pItems);
                         fuploadStatus.setProcessEndTime(System.currentTimeMillis());
                         fuploadStatus.setProcessRunningTime(fuploadStatus.getProcessEndTime());
                      //讀取中
                      } else {
                         logger.debug("讀取進行中:已經讀取了 " + pBytesRead + " / " + pContentLength+ " bytes.");
                         fuploadStatus.setStatus("當前正在處理第" + pItems+"個文件:已經讀取了 " + pBytesRead + " / " + pContentLength+ " bytes.");
                         fuploadStatus.setReadTotalSize(pBytesRead);
                         fuploadStatus.setCurrentUploadFileNum(pItems);
                         fuploadStatus.setProcessRunningTime(System.currentTimeMillis());
                      }
                      BackGroundService.storeFileUploadStatusBean(this.session,fuploadStatus);
                  }

                  很清楚,我也把FileUploadStatus這個Bean存取于Session中。

                  Servlet實現:BackGroundService這個Servlet類負責接收Form Post數據、回應狀態輪詢請求、處理取消文件上傳的請求。盡管可以把這些功能相互分離開來(比如構造一個FileUploadManager類),但出于簡單明了、便于閱讀之目的,還是將它們放到Servlet中,只是由不同的方法進行分割。
                  {BackGroundService中的processFileUpload方法用于處理文件上傳請求}:

                  /**
                   * 處理文件上傳
                   * @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);
                          //設置臨時文件存儲位置
                          factory.setRepository(new File(request.getRealPath("/upload/temp")));
                          ServletFileUpload upload = new ServletFileUpload(factory);
                          //設置單個文件的最大上傳size
                          upload.setFileSizeMax(10240000);
                          //設置整個request的最大size
                          upload.setSizeMax(10240000);
                          upload.setProgressListener(new FileUploadListener(request.getSession()));
                          //保存初始化后的FileUploadStatus Bean
                          storeFileUploadStatusBean(request.getSession(),initFileUploadStatusBean(request));
                          
                          String forwardURL="";
                          try {
                                  List items = upload.parseRequest(request);
                                  //獲得返回url
                                  for(int i=0;i<items.size();i++){
                                          FileItem item=(FileItem)items.get(i);
                                          if (item.isFormField()){
                                                  logger.debug("form Field["+item.getFieldName()+"]="+item.getString());
                                                  forwardURL=item.getString();
                                                  break;
                                          }
                                  }
                                  //處理文件上傳
                                  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());
                                                  logger.debug("處理文件["+fileName+"]:保存路徑為"
                                                                  +request.getRealPath(UPLOAD_DIR)+File.separator+fileName);
                                                  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) {
                                  logger.error("上傳文件時發生錯誤:"+e.getMessage());
                                  e.printStackTrace();
                                  uploadExceptionHandle(request,"上傳文件時發生錯誤:"+e.getMessage());
                          } catch (Exception e) {
                                  // TODO Auto-generated catch block
                                  logger.error("保存上傳文件時發生錯誤:"+e.getMessage());
                                  e.printStackTrace();
                                  uploadExceptionHandle(request,"保存上傳文件時發生錯誤:"+e.getMessage());
                          }
                          if (forwardURL.length()==0){
                                  forwardURL=DEFAULT_UPLOAD_FAILURE_URL;
                          }
                          request.getRequestDispatcher(forwardURL).forward(request,response);
                  }


                  {BackGroundService中的responseFileUploadStatusPoll方法用于處理對文件上傳狀態的輪詢請求}:

                  /**
                   * 回應上傳狀態查詢
                   * @param request
                   * @param response
                   * @throws IOException
                   */
                  private void responseFileUploadStatusPoll(HttpServletRequest request,HttpServletResponse response) throws IOException{
                          response.setContentType("text/xml");
                          response.setCharacterEncoding("UTF-8");
                          response.setHeader("Cache-Control", "no-cache");
                          logger.debug("發送上傳狀態回應");
                          response.getWriter().write(XmlUnSerializer.serializeBean(
                                          request.getSession().getAttribute(UPLOAD_STATUS)));
                  }


                  {BackGroundService中的processCancelFileUpload方法用于處理取消文件上傳的請求}:

                  /**
                   * 處理取消文件上傳
                   * @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);
                  }


          Web客戶端代碼:

          image
          Prototype給開發者更多的自由選擇


          web客戶端使用了基于Prototype的AjaxWrapper類和XMLDomForAjax類,前者實現了對Ajax.Request功能的封裝,而后者實現了對來自服務器的XML Response的反序列化(反序列化為JavaScript對象)。

                  為了避免在AjaxWrapper的回調方法中發生this被重寫的問題,我使用了ClassUtils類給任何類的每個方法注冊一個對類對象自身引用,詳見《解開JavaScript生命的達芬奇密碼》《Prototype.AjaxRequest的調用堆棧重寫問題》
                  {ClassUtils類代碼}:

          //類工具
          var ClassUtils=Class.create();
          ClassUtils.prototype={
                  _ClassUtilsName:'ClassUtils',
                  initialize:function(){
                  },
                  /**
                   * 給類的每個方法注冊一個對類對象的自我引用
                   * @param reference 對類對象的引用
                   */
                  registerFuncSelfLink:function(reference){
                          for (var n in reference) {
                          var item = reference[n];                        
                          if (item instanceof Function)
                                          item.$ = reference;
                      }
                  }
          }


                  {將XML反序列化為JavaScript對象的XMLDomForAjax類代碼}:

          var XMLDomForAjax=Class.create();
          XMLDomForAjax.prototype={
                  isDebug:false,
                  //dom節點類型常量
                  ELEMENT_NODE:1,
                  ATTRIBUTE_NODE:2,
              TEXT_NODE:3,
              CDATA_SECTION_NODE:4,
              ENTITY_REFERENCE_NODE:5,
              ENTITY_NODE:6,
              PROCESSING_INSTRUCTION_NODE:7,
              COMMENT_NODE:8,
              DOCUMENT_NODE:9,
              DOCUMENT_TYPE_NODE:10,
              DOCUMENT_FRAGMENT_NODE:11,
              NOTATION_NODE:12,
              
                  initialize:function(isDebug){
                          new ClassUtils().registerFuncSelfLink(this);
                          this.isDebug=isDebug;
                  },
                  /**
                   * 建立跨平臺的dom解析器
                   * @param xml xml字符串
                   * @return dom解析器
                   */
                  createDomParser:function(xml){
                          // code for IE
                          if (window.ActiveXObject){
                            var doc=new ActiveXObject("Microsoft.XMLDOM");
                            doc.async="false";
                            doc.loadXML(xml);
                          }
                          // code for Mozilla, Firefox, Opera, etc.
                          else{
                            var parser=new DOMParser();
                            var doc=parser.parseFromString(xml,"text/xml");
                          }
                          return doc;
                  },
                  /**
                   * 反向序列化xml到javascript Bean
                   * @param xml xml字符串
                   * @return javascript Bean
                   */
                  deserializedBeanFromXML:function (xml){
                          var funcHolder=arguments.callee.$;
                          var doc=funcHolder.createDomParser(xml);
                          // documentElement總表示文檔的root
                          var objDomTree=doc.documentElement;
                          var obj=new Object();
                      for (var i=0; i<objDomTree.childNodes.length; i++) {
                              //獲得節點
                              var node=objDomTree.childNodes[i];
                              //取出其中的field元素進行處理
                          if ((node.nodeType==funcHolder.ELEMENT_NODE) && (node.tagName == 'field')) {
                                  var nodeText=funcHolder.getNodeText(node);
                                  if (funcHolder.isDebug){
                                      alert(node.getAttribute('name')+' type:'+node.getAttribute('type')+' text:'+nodeText);
                                  }
                              var objFieldValue=null;
                              //如果為列表
                              if (node.getAttribute('type')=='java.util.List'){
                                      if (objFieldValue && typeof(objFieldValue)=='Array'){
                                              if (nodeText.length>0){
                                                                  objFieldValue[objFieldValue.length]=nodeText;
                                                          }
                                                  }
                                                  else{
                                                          objFieldValue=new Array();
                                                  }
                                          }
                                          else if (node.getAttribute('type')=='long'
                                                  || node.getAttribute('type')=='java.lang.Long'
                                                  || node.getAttribute('type')=='int'
                                                  || node.getAttribute('type')=='java.lang.Integer'){
                                                  
                                                  objFieldValue=parseInt(nodeText);
                                          }
                                          else if (node.getAttribute('type')=='double'
                                                  || node.getAttribute('type')=='float'
                                                  || node.getAttribute('type')=='java.lang.Double'
                                                  || node.getAttribute('type')=='java.lang.Float'){
                                                  
                                                  objFieldValue=parseFloat(nodeText);
                                          }
                                          else if (node.getAttribute('type')=='java.lang.String'){
                                                  objFieldValue=nodeText;
                                          }
                                          else{
                                                  objFieldValue=nodeText;
                                          }
                                          //賦值給對象
                                          obj[node.getAttribute('name')]=objFieldValue;
                                          if (funcHolder.isDebug){
                                                  alert(eval('obj.'+node.getAttribute('name')));
                                          }
                          }
                          else if (node.nodeType == funcHolder.TEXT_NODE){
                                  if (funcHolder.isDebug){
                                          //alert('TEXT_NODE');
                                  }
                                  
                          }
                          else if (node.nodeType == funcHolder.CDATA_SECTION_NODE){
                                  if (funcHolder.isDebug){
                                          //alert('CDATA_SECTION_NODE');
                                  }
                          }
                      }
                      return obj;
                  },
                  /**
                   * 獲得dom節點的text
                   */
                  getNodeText:function (node) {
                          var funcHolder=arguments.callee.$;
                      // is this a text or CDATA node?
                      if (node.nodeType == funcHolder.TEXT_NODE || node.nodeType == funcHolder.CDATA_SECTION_NODE) {
                          return node.data;
                      }
                      var i;
                      var returnValue = [];
                      for (i = 0; i < node.childNodes.length; i++) {
                              //采用遞歸算法
                          returnValue.push(funcHolder.getNodeText(node.childNodes[i]));
                      }
                      return returnValue.join('');
                  }
          }


                  {AjaxWrapper類的主要方法putRequest和callBackHandler}:

                   /**
                   * 以get的方式向server發送request
                   * @param url
                   * @param params
                   * @param callBackFunction 發送成功后回調的函數或者函數名
                   */
                  putRequest:function(url,params,callBackFunction){
                          var funcHolder=arguments.callee.$;
                      var xmlHttp = new Ajax.Request(url,
                                  {
                                          method: 'get',
                                      parameters: params,
                                          requestHeaders:['my-header-encoding','utf-8'],
                                      onFailure: function(){
                                                  alert('對不起,網絡通訊失敗,請重新刷新!');
                                          },
                                          onSuccess: function(transport){
                                          },
                                          onComplete: function(transport){
                                                  funcHolder.callBackHandler.apply(funcHolder,[transport,callBackFunction]);
                                          }
                                  });
                  },
                  /**
                   * 遠程調用的回調處理
                   * @param transport xmlhttp的transport
                   * @param callBackFunction 回調時call的方法,可以是函數也可以是函數名
                   */
                  callBackHandler:function(transport,callBackFunction){
                          var funcHolder=arguments.callee.$;
                          if(transport.status!=200){
                                  alert("獲得回應失敗,請求狀態:"+transport.status);
                          }
                          else{
                                  funcHolder.xml_source=transport.responseText;
                                  if (funcHolder.debug_flag)
                                          alert('call callback function');
                                  if (typeof(callBackFunction)=='function'){
                                          if (funcHolder.debug_flag){
                                                  alert('invoke callbackFunc');
                                          }
                                          callBackFunction(transport.responseText);
                                  }
                                  else{
                                          if (funcHolder.debug_flag){
                                                  alert('evalFunc callbackFunc');
                                          }
                                          new execute().evalFunc(callBackFunction,transport.responseText);
                                  }
                                  if (funcHolder.debug_flag)
                                          alert('end callback function');
                          }
                  }


                  {頁面中主要的JavaScript方法:refreshUploadStatus和startProcess/cancelProcess}:

          //刷新上傳狀態
          function refreshUploadStatus(){
                  var ajaxW = new AjaxWrapper(false);
                  ajaxW.putRequest(
                          './uploadStatus.action',
                          'uploadStatus=',
                          function(responseText){
                                  var deserialor=new XMLDomForAjax(false);
                                  var uploadInfo=deserialor.deserializedBeanFromXML(responseText);
                                  var progressPercent = Math.ceil(
                                          (uploadInfo.readTotalSize) / uploadInfo.uploadTotalSize * 100);

                          $('progressBarText').innerHTML = ' 上傳處理進度: '+progressPercent+'% ['+
                                  (uploadInfo.readTotalSize)+'/'+uploadInfo.uploadTotalSize + ' bytes]'+
                                  ' 正在處理第'+uploadInfo.currentUploadFileNum+'個文件'+
                                  ' 耗時: '+(uploadInfo.processRunningTime-uploadInfo.processStartTime)+' ms';
                          $('progressStatusText').innerHTML=' 反饋狀態: '+uploadInfo.status;
                          $('totalProgressBarBoxContent').style.width = parseInt(progressPercent * 3.5) + 'px';
                          }
                  );
          }
          //上傳處理
          function startProgress(){
                  Element.show('progressBar');
              $('progressBarText').innerHTML = ' 上傳處理進度: 0%';
              $('progressStatusText').innerHTML=' 反饋狀態:';
              $('uploadButton').disabled = true;
              var periodicalExe=new PeriodicalExecuter(refreshUploadStatus,2);
              return true;
          }
          //取消上傳處理
          function cancelProgress(){
                  $('cancelUploadButton').disabled = true;
                  var ajaxW = new AjaxWrapper(false);
                  ajaxW.putRequest(
                          './uploadStatus.action',
                          'cancelUpload=true',
                          //因為form的提交,這可能不會執行
                          function(responseText){
                                  var deserialor=new XMLDomForAjax(false);
                                  var uploadInfo=deserialor.deserializedBeanFromXML(responseText);
                                  $('progressStatusText').innerHTML=' 反饋狀態: '+uploadInfo.status;
                                  if (msgInfo.cancel=='true'){
                                          alert('刪除成功!');
                                          window.location.reload();
                                  };
                          }
                  );
          }


          運行界面:

          image
          起始頁面



          image
          上傳進行中…



          image
          上傳完成后的文件列表



          image
          用戶取消上傳后顯示的頁面




          image
          上傳過程中出錯(上傳文件過大)頁面


          源代碼下載:






          相關鏈接:
                  AJAX Upload progress monitor for Commons-FileUpload Example
                  Apache Common FileUpload組件
                  Prototype官方網站
                  IBM的AJAX SlideShow應用
                  解開JavaScript生命的達芬奇密碼
                  Prototype.AjaxRequest的調用堆棧重寫問題

          感謝閱讀此文

                  請支持cleverpig發起的image


          本頁頁面地址:

          投票評分(記入本貼作者的專家分)

               非常好 還行 一般 扔雞蛋          投票總得分: / 投票總人次:

          用戶評論列表

          #1 評論作者: GOVO 發表時間: 2007-01-08 01:23 下午

          這個方法早想到了,就等common.FileUpload的1.2出來。

          #2 評論作者: GOVO 發表時間: 2007-01-08 01:24 下午

          讓老外先發表,我暈哦~

          #3 評論作者: cleverpig 發表時間: 2007-01-08 02:08 下午

          GOVO,這是cleverpig的原創,可不是老外的作品哦。

          #4 評論作者: jctr 發表時間: 2007-01-09 02:01 下午

          import com.bjinfotech.util.objecttk.*;
          沒有,程序無法運行

          #5 評論作者: mreay 發表時間: 2007-01-09 02:13 下午

          貌似不錯,抽空試試!!!
          有人用AS實現過類似的功能嗎?

          #6 評論作者: cleverpig 發表時間: 2007-01-09 05:49 下午

          to jctr:感謝你的糾錯!我已經修改了上面的源代碼下載:直接提供war包,源代碼在WEB-INF/classes目錄中。

          #7 評論作者: qhz 發表時間: 2007-01-10 09:02 上午

          正好用上,趕緊試試!

          #8 評論作者: archer1207 發表時間: 2007-01-10 09:29 上午

          其實ss有想類似的實現:http://wiki.springside.org.cn/display/springside/AjaxUpload
          不過作者的也不錯,+U.

          #9 評論作者: cleverpig 發表時間: 2007-01-10 10:34 上午

          springside提供的AjaxUpload采用DWR+MVC框架的,而我提供的是比較original的版本,出于簡化的目的單純地使用prototype+jsp,這樣不和任何框架耦合,適用于任何支持Sevlet的java web框架。

          #10 評論作者: haidaotao 發表時間: 2007-01-11 10:33 上午

          在tomcat里上傳中文文件名我最頭疼了,傳是傳的上去,但是點擊后是404錯誤。不知道AJAX Upload有么有辦法解決

          #11 評論作者: kq1983 發表時間: 2007-01-11 03:28 下午

               tomcat重起后,第一次上傳沒有進度條(連接失敗,500,后臺在跑),第二次上傳以后才開始出現進度條,cleverpig這問題怎么解決.

          #12 評論作者: kq1983 發表時間: 2007-01-11 03:50 下午

                瀏覽器關閉后,第一次就是不會出現進度條(連接失敗,500,后臺在跑),第二次開始才出來進入條.

          #13 評論作者: easyrun 發表時間: 2007-01-11 03:51 下午

          還有一個問題,文件上傳時文件的索引不正確,總是從開始處理第2個文件開始,就算是上傳一個文件也是顯示正在處理第2個文件

          #14 評論作者: easyrun 發表時間: 2007-01-11 03:58 下午

          連接失敗,500,這個問題我也遇到過,莫名其妙的有好了。
          問題出現原因是在返回狀態給客戶端時,到session里去取UPLOAD_STATUS沒有取到,然后再調用serializeBean方法時出現nullpointexception。
          因為在執行
          Field[] fields=beanObj.getClass().getDeclaredFields();
          時beanObj為空。
          建議可以在這里做個保護措施。如果為空怎么怎么樣。

          不過session里的UPLOAD_STATUS為什么為空這個原因還沒有找到

          #15 評論作者: easyrun 發表時間: 2007-01-11 04:18 下午

          取消上傳的功能對小文件來說根本沒有意義,因為上傳小文件時基本上是一閃就過,沒有機會來取消,呵呵。
          是否可以把取消下載這個動作放到所有文件上傳后再讓用戶來選擇取消,如果可以的話能做到在上傳多文件時可以取消其中的任意一個上傳文件那是最好了。
          觀察了一下取消下載動作本身就是在所有文件上傳完了之后才生效的,沒有細究上面的想法在實現上是否可行,只是覺得那樣可能會更方便一點。

          #16 評論作者: softicer 發表時間: 2007-01-12 01:08 上午

          謝謝 cleverpig  的原創。難得找到中文的參考資料了,呵呵。

          #17 評論作者: cleverpig 發表時間: 2007-01-12 04:37 下午

          根據大家的反饋,已經完成了AJAX FileUpload的u1版,進行了以下fix:
          1。支持中文文件下載
          2。增加了單個文件的刪除功能
          3。增加了AJAX動畫顯示
          大家可以從本文的源代碼處或者這里下載

          #18 評論作者: cleverpig 發表時間: 2007-01-12 04:39 下午

          to haidaotao同學:

          >>>在tomcat里上傳中文文件名我最頭疼了,傳是傳的上去,但是點擊后是404錯誤。不知道AJAX Upload有么有辦法解決

          u1版已經使用servlet處理文件下載請求(使用PlainURLEncoder類對文件名進行了編解碼),而不是從前的直接鏈接。

          #19 評論作者: cleverpig 發表時間: 2007-01-12 04:42 下午

          to  kq1983同學:
          關于進度條的顯示問題,完全依靠了AJAX的調用周期長短(目前代碼中為2秒)。所以也許在2秒內第一個文件已經被處理完,而AJAX反饋從server回來便會顯示正在處理第2個文件。

          #20 評論作者: cleverpig 發表時間: 2007-01-12 04:49 下午

          to easyrun同學:

          關于處理進度的問題請參考上一條。

          >>是否可以把取消下載這個動作放到所有文件上傳后再讓用戶來選擇取消,如果可以的話能做到在上傳多文件時可以取消其中的任意一個上傳文件那是最好了。

          u1版已經增加上傳后的刪除功能。而在上傳多文件時的取消處理時,由于使用fileUpload的解析文件功能無法中斷,所以我是在解析文件后的保存文件的循環中完成的——刪除所有已經上傳的文件、清空session中的文件上傳列表。我想這就是出現你所見到的“文件上傳后才刪除”的原因。

          #21 評論作者: zengxianhong 發表時間: 2007-01-12 06:15 下午

          瀏覽器關閉后,第一次就是不會出現進度條(連接失敗,500,后臺在跑),第二次開始才出來進入條是因為processFileUpload方法中的request.getSession()取得session和responseFileUploadStatusPoll方法中的request.getSession()取得session不同。

          #22 評論作者: cleverpig 發表時間: 2007-01-12 09:58 下午

          to zengxianhong 同學:

          在瀏覽器關閉后,原來的回話將結束(在server中也將如此)。這樣在下次打開fileupload.html進行上傳時,便會建立新的session,而在processFileUpload方法和responseFileUploadStatusPoll方法中的session都來自于同一個client的session。而且如果起初發生連接失敗(500),那證明后天可能發生錯誤(建議使用FireFox的firebug查看一下XMLHTTPResponse的內容)。

          #23 評論作者: baiyun 發表時間: 2007-01-12 11:46 下午

          這樣用Session存取UPLOAD_STATUS的話,因為這個key確定,所以同一時間是能有一個頁面進行上傳,要是我想多個頁面同時執行上傳動作,請問應該怎么樣修改?就是js端怎么能動態的得到這個Session的key?

          #24 評論作者: discover7 發表時間: 2007-01-13 01:41 下午

          這個東東能不能運行啊。
          先試試看

          #25 評論作者: cleverpig 發表時間: 2007-01-14 10:46 上午

          to baiyun同學:這需要增加一個server端的seesionManager功能,對session進行管理,比如firefox的瀏覽器tab會導致多個頁面共享同一個session,而不是IE那樣的1 session/page。

          #26 評論作者: zhaow8810 發表時間: 2007-01-15 08:32 下午

          不用session用cookie存儲上傳狀態能實現嗎

          #27 評論作者: cleverpig 發表時間: 2007-01-15 09:08 下午

          to zhaow8810同學:
          >>不用session用cookie存儲上傳狀態能實現嗎?
          cookie并不具有數據存儲的獨立性、隔離性,而session是基于瀏覽器單次會話的存儲體。所以為了將上傳數據在server與client之間共享,session是很好的媒介。
          但是有些瀏覽器對session的share行為,使得單純使用session并不能達到精準的數據隔離,因此在下一個版本中將增加SessionManger。

          #28 評論作者: eidff 發表時間: 2007-01-15 09:35 下午

          為什么我的運行時,雖然報錯也可以上傳文件成功?

          #29 評論作者: zhaow8810 發表時間: 2007-01-16 12:19 上午

          那我的系統就沒用session保存用戶狀態啊 在集群體環境下為了多服務器更好的性能而用cookie保存用戶狀態,這樣減少了session序列化給服務器的壓力。而在這種環境下能否集成您提供的ajax上傳文件功能那?

          #30 評論作者: zengxianhong 發表時間: 2007-01-16 08:02 上午

          to cleverpig
          第一次部署運行時,文件上傳成功,但是從服務器端得到的消息是500的錯誤。我debug了一下,發現第一次運行時,processFileUpload方法和responseFileUploadStatusPoll方法中取得的session不是同一個session。第二次之后就沒問題了。

          #31 評論作者: cleverpig 發表時間: 2007-01-16 09:07 上午

          to zhaow8810同學:
          >>那我的系統就沒用session保存用戶狀態啊 在集群體環境下為了多服務器更好的性能而用cookie保存用戶狀態,這樣減少了session序列化給服務器的壓力。

          用戶狀態保存在client也可以,但是建議你使用session cookie。使用session cookie保存用戶信息的話,可以替代server端對session的存取管理,用AJAX將處理狀態傳送到client,進而保存到session cookie。
          推薦你一個簡單的session cookie實現

          #32 評論作者: cleverpig 發表時間: 2007-01-16 09:10 上午

          to zengxianhong同學:
          >>第一次部署運行時,文件上傳成功,但是從服務器端得到的消息是500的錯誤。我debug了一下,發現第一次運行時...

          這個問題我測試時沒有發現,可能和你我的運行環境差異有關:web容器、servlet api version、瀏覽器版本等。

          #33 評論作者: zengxianhong 發表時間: 2007-01-16 09:26 上午

          to cleverpig
          我的web容器:apache-tomcat-5.5.17
          servlet api version:2.4
          瀏覽器版本: firefox_2.0.0.1
                           IE_6.0.2900
          兩個瀏覽器我都測了,都有那種現象。

          #34 評論作者: liusf 發表時間: 2007-01-16 02:35 下午

          討論相當激烈啊....感謝分享...確實不錯..

          #35 評論作者: sljsuper 發表時間: 2007-01-16 03:08 下午

          我也出現 zengxianhong的問題!!刪除的時候也是!

          #36 評論作者: agu 發表時間: 2007-01-16 03:48 下午

          我也遇到同樣的問題 刪文件時報錯

          java.lang.NoClassDefFoundError: org/apache/commons/collections/FastHashMap
                  at org.apache.commons.beanutils.ConvertUtilsBean.<init>(ConvertUtilsBean
          .java:125)

          #37 評論作者: zengxianhong 發表時間: 2007-01-16 05:28 下午

          to agu
          上面的錯誤是缺少commons-collections.jar,和session沒有關系。

          #38 評論作者: 191301587 發表時間: 2007-01-18 01:18 下午

          無法下載...............

          #39 評論作者: 191301587 發表時間: 2007-01-18 01:19 下午

          樓主或哪位能否給我發一個學習學習,謝謝

          ivanw@126.com

          #40 評論作者: haiter 發表時間: 2007-01-19 11:15 上午

          當上傳文件為100M時,在上傳的過程中,點擊取消。實際上服務器上并沒有取消。如何才能真正取消?

          #41 評論作者: cleverpig 發表時間: 2007-01-19 03:52 下午

          to zengxianhong同學:
          >>第一次部署運行時,文件上傳成功,但是從服務器端得到的消息是500的錯誤。我debug了一下,發現第一次運行時...

          問題已經找到,在某些情況下的確出現ajax xmlHttpRequest和form post的session不一致的現象。

          #42 評論作者: cleverpig 發表時間: 2007-01-19 03:53 下午

          針對上面的問題,我發布u2版本,進行了部分改進和提升。

          #43 評論作者: cleverpig 發表時間: 2007-01-19 03:54 下午

          U2's new Feature:
          1.使用獨立的UploadSessionManager管理uploadSession,使后者在AJAX和form之間共享。分離原處一處的“處理文件上傳”、“狀態查詢”任務到UploadProcessService和FileUploadCommandService這兩個servlet中,實現上傳和查詢的操作分離,提高反應速度。
          2.使用AJAX join命令,首先發送AJAX請求,然后進行form submit,這樣可以達到session的一致;
          3.增加了MyServletFileUpload類,繼承與ServletFileUpload,并在其parserRequest方法中增加了cancel upload處理;
          4.增加在servlet進行處理前的上傳文件超限的檢測能力;
          5.upload和result頁面完全使用AJAX+DHTML,擺脫了對JSP的依賴。

          #44 評論作者: cleverpig 發表時間: 2007-01-19 04:35 下午

          to haiter同學:
          >>當上傳文件為100M時,在上傳的過程中,點擊取消。實際上服務器上并沒有取消。如何才能真正取消?

          我沒有想出實現“真正的取消”的辦法,因為form submit是事務性的,沒法中間取消掉。所以即使后臺已經執行玩取消操作了,但form仍然要提交完才行。

          #45 評論作者: zengxianhong 發表時間: 2007-01-19 04:51 下午

          to cleverpig
          知道是什么原因引起的ajax xmlHttpRequest和form post的session不一致的現象嗎?

          #46 評論作者: haiter 發表時間: 2007-01-19 05:09 下午

          當上傳文件為100M時,在上傳的過程中,點擊取消。服務器上還在不斷地打印如下信息:說明沒有真正取消。如何才能真正取消?
          ----
          [hirycajulyrosuvydyso]被添加/更新
          17:06:51,515 DEBUG FileUploadStatusBeanManager:20 - 使用已建立的UploadSession[hirycajulyrosuvydyso]


          #47 評論作者: cleverpig 發表時間: 2007-01-20 09:08 下午

          to haiter同學:

          >>服務器上還在不斷地打印如下信息:說明沒有真正取消。如何才能真正取消?

          這些信息表示uploadsession中的fileUploadStatus被更新,因為已經執行了取消,所以要更新fileUploadStatus中的信息。雖然終止了對form提交的request的解析處理工作,但web 容器對form提交數據的處理仍然要完成。目前沒找到合適的方法實現真正的取消。

          #48 評論作者: cleverpig 發表時間: 2007-01-20 09:13 下午

          to zengxianhong:
          >>知道是什么原因引起的ajax xmlHttpRequest和form post的session不一致的現象嗎?

          原因還沒有找到,但問題現象很容易再現,只需servlet和AJAX、form就可以。
          1。首先使用form提交上傳文件(文件足夠大需要執行一段時間),然后發送AJAX請求到servlet,servlet將分配給兩種request不同的session。
          2。首先發送AJAX請求,然后再提交form數據,servlet將分配給第一次的AJAX請求一個新session,而后的form請求將重用這個session。

          #49 評論作者: chenlb 發表時間: 2007-01-21 04:14 下午

          u2版,要在lib下加commons-collections.jar

          上傳的進度不能反應出來,輪查時間間隔太短了,有時一部到100%
          有時只有2次變化,

          我本地上傳3M左右的文件,只有2-3次的變化, 建議進度條進度變化做細的點.

          感謝你的分享

          #50 評論作者: cleverpig 發表時間: 2007-01-22 09:28 上午

          to chenlb同學:

          >>上傳的進度不能反應出來,輪查時間間隔太短了,有時一部到100%
          有時只有2次變化

          輪詢時間可以自定義,只要修改fileupload.html頁面中函數startProgress中:


          var periodicalExe=new PeriodicalExecuter(refreshUploadStatus,5);

          其中第二個參數是輪詢周期,目前默認是5秒。

           

          #51 評論作者: cleverpig 發表時間: 2007-01-22 09:48 上午

          to 缺少commons-collections.jar的同學們:

          這個問題將在下一個update版本中修復。
          可以到這里下載本人使用的3.1版:

          ,或者下載更新的版本,把它放入到WEB-INF/lib目錄下即可。

           

          #52 評論作者: cleverpig 發表時間: 2007-01-30 06:10 下午

          更新到U3版本:
          U3 part1
          U3 part2

          #53 評論作者: cleverpig 發表時間: 2007-01-30 06:10 下午

          U3's new Feature:
          1.采用單獨的form進行file提交,并將其target設置為隱藏的IFrame,從而實現IFrameIO;
          2.集成了先進的ClamAV查毒引擎技術,在文件上傳完成前進行病毒掃描,及時刪除染毒文件;
          3.重構了UploadSessionManager、FileUploadStatusBeanManager、UploadSessionImpl、FileUploadStatus等類,去掉了FileUploadStatus類中的URLList屬性,增加了FileUploadStatusList類對其進行FileUploadStatus列表進行收納、管理。
          4.將原來頁面功能合為一頁,實現了One Page One Application。

          #54 評論作者: xmlspy 發表時間: 2007-01-30 06:32 下午

          病毒掃描總是報 文件被感染病毒,
          文件被刪除,導致在頁面選擇刪除的時候失敗

          #55 評論作者: cleverpig 發表時間: 2007-01-30 10:30 下午

          to xmlsply同學:

          這并不是bug。使用ClamAV進行文件查毒,需要下載clamwin.
          在安裝后確認程序中的clamav.properties配置正確,便可使用查毒功能。如果沒有安裝的話,程序默認把上傳文件作為染毒文件對待。

          #56 評論作者: cleverpig 發表時間: 2007-01-30 10:32 下午

          如果使用unix系統的話,請下載clamav,并安裝和配置clamav.properties。
          U3版中的clamav.properties支持clamwin在window平臺下的默認安裝。

          #57 評論作者: cleverpig 發表時間: 2007-01-31 04:15 下午

          為了增加查毒功能的可選性,更新了部分代碼。
          大家可以通過設置clamav.properties來可選地開關查毒功能:


          #查毒功能使能標志
          enable=true
          #執行程序路徑
          executeFile=C:/Program Files/ClamWin/bin/clamscan.exe
          #病毒代碼庫路徑
          libPath=C:/Documents and Settings/All Users/.clamwin/db



          新的U3_1版代碼如下:
          U3_1 part1
          U3_1 part2

          #58 評論作者: blueice 發表時間: 2007-01-31 04:46 下午

          我裝了U3版,挺好,謝謝分享

          #59 評論作者: Tin 發表時間: 2007-02-01 09:46 上午

          謝謝Cleverpig,很干凈的實現,鉆研的精神令人敬佩呀!

          #60 評論作者: ndlgyb 發表時間: 2007-02-01 10:46 上午

          為什么有時會出現 “對不起,網絡通訊失敗,請重新刷新!”的警告呢
          用兩臺機器測試在上傳接近100M的文件能成功,但刪除時會出現找不到路徑之類的問題

          #61 評論作者: cleverpig 發表時間: 2007-02-01 11:54 上午

          to ndlgyb同學:

          >>為什么有時會出現 “對不起,網絡通訊失敗,請重新刷新!”的警告呢
          后臺可能發生錯誤,請仔細查看一下日志。

          >>用兩臺機器測試在上傳接近100M的文件能成功,但刪除時會出現找不到路徑之類的問題
          兩種可能:
          1。后臺可能發生錯誤,請仔細查看一下日志。
          2。文件正在被別的程序訪問或者已經被刪除,可通過瀏覽upload目錄進行驗證。

          #62 評論作者: adam 發表時間: 2007-02-06 04:52 下午

          無法下載呀,下載為html樣式,我修改.html為.jar后,為如下文件。2007_01_31_160627_uSLgPxqmcU_bak.rar,2007_01_31_160637_UqKVpbIsNN.rar解包出現錯誤。

          請問是什么原因?謝謝

          #63 評論作者: adam 發表時間: 2007-02-06 04:52 下午

          無法下載呀,下載為html樣式,我修改.html為.jar后,為如下文件。2007_01_31_160627_uSLgPxqmcU_bak.rar,2007_01_31_160637_UqKVpbIsNN.rar解包出現錯誤。

          請問是什么原因?謝謝

          #64 評論作者: adam 發表時間: 2007-02-06 04:52 下午

          無法下載呀,下載為html樣式,我修改.html為.jar后,為如下文件。2007_01_31_160627_uSLgPxqmcU_bak.rar,2007_01_31_160637_UqKVpbIsNN.rar解包出現錯誤。

          請問是什么原因?謝謝

          #65 評論作者: cleverpig 發表時間: 2007-02-07 07:28 下午

          我測試了可以下載的,記住要首先解壓縮2007_01_31_160627_uSLgPxqmcU.rar文件,然后選擇第二個文件2007_01_31_160637_UqKVpbIsNN.rar。

          #66 評論作者: fffff 發表時間: 2007-02-25 04:24 上午

          ddddddddddddddddddddddddd

          #67 評論作者: xiequnwe 發表時間: 2007-02-28 03:41 下午

          不知道下面這個問題算不算一個bug:
          當瀏覽一個文件,不上傳,然后點取消,這個時候居然發現后臺一直在操作。文件沒上傳上去,但是文件列表里居然有剛才瀏覽的文件名。

          #68 評論作者: xiequnwe 發表時間: 2007-02-28 03:47 下午

          重新測試的時候居然發現沒有了。怪。

          #69 評論作者: xiequnwe 發表時間: 2007-02-28 04:01 下午

          上傳過程中,如果取消了操作,最好是給于提示,比如正在刪除上傳的文件,刪除完了后提示刪除成功。

          #70 評論作者: xiequnwe 發表時間: 2007-02-28 04:03 下午

          對于上傳文件列表中的文件,直接打開,總是報錯,不能查看。

          #71 評論作者: xiequnwe 發表時間: 2007-02-28 04:03 下午

          對于上傳文件列表中的文件,直接打開,總是報錯,不能查看。

          #72 評論作者: xiequnwen 發表時間: 2007-02-28 05:39 下午

          這里有個進度條顯示的,沒用ajax,希望樓主參考,做個完美版本出來。
          http://blog.csdn.net/lxzjsj/archive/2006/12/17/1446082.aspx

          #73 評論作者: liukui 發表時間: 2007-03-08 05:15 下午

          不錯,寫得不錯jlkjkll

          #74 評論作者: 夜羽·隼 發表時間: 2007-03-10 02:38 下午

          請問:

          我的文件放到eclipse里面的時候,他報錯:import junit.framework.TestCase;找不到

          還有,我在局域網里其他機子上可以上傳下載,但是無法直接打開上傳的文件

          這是為什么呢?

          #75 評論作者: kris_zhang 發表時間: 2007-03-14 11:47 上午

          如果想在上傳后轉到一個成功頁面,那就在upload.js中refreshUploadStatus函數中if (uploadInfo.status=='upload_done'){
          寫上要跳轉的方法.

          #76 評論作者: kris_zhang 發表時間: 2007-03-14 11:48 上午

          如果有人有這個的flash版,請給我一份謝謝。
          zhang.kris@gmail.com

          #77 評論作者: LuckyKing 發表時間: 2007-04-04 07:37 下午

          u3在tomcat6.0上測試通過沒有問題,但在resin3.0上就一直在初始化的時候循環,不知道是為什么,用的是jdk5.0, u1,u2都無法在tomcat和resin上跑....,不知道樓主有沒有測試過這些情況

          #78 評論作者: lizhaosuper 發表時間: 2007-04-09 09:42 下午

          確實做的還不錯支持一下

          #79 評論作者: songhuan 發表時間: 2007-04-11 10:24 上午

          為什么我來是出來這些
          09:29:13,718 DEBUG Digester:1262 -   New match='web-app/mime-mapping'
          09:29:13,718 DEBUG Digester:1273 -   Fire begin() for CallMethodRule[methodName=addMimeMapping, paramCount=2, paramTypes={java.lang.String, java.lang.String}]
          09:29:13,718 DEBUG Digester:2701 - Pushing params
          09:29:13,718 DEBUG sax:932 - characters(

          #80 評論作者: xinghai 發表時間: 2007-05-08 11:02 上午

          InteractionMessage 這是哪個包的類啊

          #81 評論作者: liuwei3230 發表時間: 2007-05-15 09:52 上午

          好文章,現在正在學習這方面的.

          #82 評論作者: wq163 發表時間: 2007-06-18 01:19 下午

          關于對文件上傳狀態Bean的序列化/反序列化工作,用JSON來做是不是更簡單呢?這樣就不用在java和js中寫那么多解析的代碼了

          #83 評論作者: xiao7cn 發表時間: 2007-07-15 09:48 下午

          cleverpig , 我如果把fileUpload_single.html文件放進里層目錄里,上傳時提示:“對不起,網絡通訊失敗,請重新刷新”。請問如何解決?謝謝!

          #84 評論作者: xiao7cn 發表時間: 2007-07-15 09:50 下午

          cleverpig , 我如果把fileUpload_single.html文件放進里層目錄里,上傳時提示:“對不起,網絡通訊失敗,請重新刷新”。請問如何解決?謝謝!

          posted on 2007-08-07 17:02 軒轅 閱讀(2492) 評論(1)  編輯  收藏 所屬分類: java

          評論

          # re: 掌控上傳進度的AJAX Upload  回復  更多評論   

          childNodes
          2011-01-12 22:44 | childNodes
          主站蜘蛛池模板: 新昌县| 临江市| 东台市| 水城县| 明水县| 灵寿县| 浦江县| 东平县| 临桂县| 石台县| 宁德市| 都昌县| 柳林县| 南宫市| 瑞丽市| 郧西县| 古交市| 日土县| 清远市| 邯郸县| 仁布县| 霍州市| 惠来县| 清河县| 垫江县| 塔城市| 安义县| 布拖县| 营口市| 宜城市| 蓝山县| 江孜县| 布尔津县| 扎鲁特旗| 济南市| 图们市| 临海市| 永修县| 宣化县| 定边县| 茶陵县|