struts2上傳文件,顯示進度條 (2)

先看效果:

 

 

 

    在struts2中上傳是很簡單的,struts2會先把文件寫到臨時文件中,以後在提供這個文件的File對象到action中。具體原理看這裏:

http://blog.csdn.net/tom_221x/archive/2009/01/12/3761390.aspx。

 

    利用servlet和common-upload.jar很容易實現顯示文件上傳的進度,在common-upload組件中實現一個ProgressListener接口,組件會把上傳的實時進度傳給你。但是想在struts2中,實時的顯示進度是有些困難的。因爲struts2把request對象給封裝了,在Action中拿到request對象,如果是上傳文件,那麼struts2已經把文件寫到文件系統裏去了。

 

 

    struts2上傳文件的時候封裝request對象其實是org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper,也就是說在action中拿到的實際類型是MultiPartRequestWrapper。片段如下:

   

  1. public class MultiPartRequestWrapper extends StrutsRequestWrapper {  
  2.     protected static final Logger LOG = LoggerFactory.getLogger(MultiPartRequestWrapper.class);  
  3.     Collection<String> errors;  
  4.     MultiPartRequest multi;  
  5.     /** 
  6.      * Process file downloads and log any errors. 
  7.      * 
  8.      * @param request Our HttpServletRequest object 
  9.      * @param saveDir Target directory for any files that we save 
  10.      * @param multiPartRequest Our MultiPartRequest object 
  11.      */  
  12.     public MultiPartRequestWrapper(MultiPartRequest multiPartRequest, HttpServletRequest request, String saveDir) {  
  13.         super(request);  
  14.           
  15.         multi = multiPartRequest;  
  16.         try {  
  17.             multi.parse(request, saveDir);  
  18.             for (Object o : multi.getErrors()) {  
  19.                 String error = (String) o;  
  20.                 addError(error);  
  21.             }  
  22.         } catch (IOException e) {  
  23.             addError("Cannot parse request: "+e.toString());  
  24.         }   
  25.     }  

 

可以看到在構造的時候,調用multi.parse(request, saveDir)把上傳的數據給封裝了。這個MultiPartRequest的解析功能是在struts-default.xml中配置的,如下:

 

  1. <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="struts" class="org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest" scope="default"/>  
  2. !-- 文件解析器類 -->  
  3. <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="jakarta" class="org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest" scope="default" />  
  4.   
  5. !-- 這就是struts2的文件解析器設置 -->  
  6. <constant name="struts.multipart.handler" value="jakarta" />  

 

 

現在的設想是,strut2不要幫我解析上傳的文件,留到action中,我自己設置。所以我們要覆蓋這是配置,如下:

 

  1. <!-- 重寫文件上傳解析方法  -->  
  2. <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="myRequestParser"  
  3. class="com.*.*.utils.MyRequestParseWrapper" scope="default" optional="true" />  
  4.   
  5. ;constant name="struts.multipart.handler" value="myRequestParser" />  

 

這個MyRequestParseWrapper如下:

 

  1. /** 
  2.  * 重寫struts2的request封裝類 
  3.  *  
  4.  * @author scott.Cgi 
  5.  */  
  6. public class MyRequestParseWrapper extends JakartaMultiPartRequest {  
  7.     /* 
  8.      * (non-Javadoc) 
  9.      * @see 
  10.      * org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest#parse 
  11.      * (javax.servlet.http.HttpServletRequest, java.lang.String) 
  12.      */  
  13.     @Override  
  14.     public void parse(HttpServletRequest servletRequest, String saveDir)  
  15.             throws IOException {  
  16.             //什麼也不做  
  17.     }  
  18. }  

 

這樣一來,在action中拿到的request就是帶有上傳文件的了。

 

 

 

接下來,所以說實現原理,依然使用common-uplaod.jar組件:

 

1. 頁面有2個iframe,一個上傳數據,一個顯示進度。

2. 當然有2個action,一個上傳數據,一個回寫進度。

3. 上傳的時候首先請求的是更新進度的iframe, 這個iframe執行客戶端js發起上傳文件請求,第二個iframe開始上傳數據。與此同時,第一個iframe開始回寫進度。進度對象保存在 session中,通過request的hashcode爲key。進度從第一個進度iframe,傳遞到第二個上傳iframe中,實現進度信息的通信。

 

 

說明一下,2個iframe是因爲,request未結束,不可以向客戶端寫數據,所以文件超大,就會阻塞回寫信息。

具體的上傳我封裝了一下,具體代碼如下:

 

  1. import java.io.File;  
  2. import java.io.FileOutputStream;  
  3. import java.io.IOException;  
  4. import java.io.InputStream;  
  5. import java.io.PrintWriter;  
  6. import java.util.List;  
  7. import javax.servlet.http.HttpServletRequest;  
  8. import javax.servlet.http.HttpServletResponse;  
  9. import org.apache.commons.fileupload.FileItem;  
  10. import org.apache.commons.fileupload.ProgressListener;  
  11. import org.apache.commons.fileupload.disk.DiskFileItemFactory;  
  12. import org.apache.commons.fileupload.servlet.ServletFileUpload;  
  13. import org.apache.log4j.Logger;  
  14. /** 
  15.  * upload file 
  16.  *  
  17.  * @author scott.Cgi 
  18.  */  
  19. public class UploadFile {  
  20.     private static final Logger LOG = Logger.getLogger(UploadFile.class);  
  21.     /** 
  22.      * 上傳文件 
  23.      *  
  24.      * @param request 
  25.      *            http request 
  26.      * @param response 
  27.      *            htp response 
  28.      * @throws IOException 
  29.      *             IOException 
  30.      */  
  31.     @SuppressWarnings("unchecked")  
  32.     public static void upload(HttpServletRequest request,  
  33.             HttpServletResponse response) throws IOException {  
  34.         LOG.info("客戶端提交類型: " + request.getContentType());  
  35.         if (request.getContentType() == null) {  
  36.             throw new IOException(  
  37.                     "the request doesn't contain a multipart/form-data stream");  
  38.         }  
  39.           
  40.         String key = request.getParameter("key");  
  41.         Progress p = (Progress)request.getSession().getAttribute(key);  
  42.           
  43.         // 設置上傳文件總大小  
  44.         p.setLength(request.getContentLength());  
  45.         LOG.info("上傳文件大小爲 : " + p.getLength());  
  46.         // 上傳臨時路徑  
  47.         String path = request.getSession().getServletContext().getRealPath("/");  
  48.         LOG.info("上傳臨時路徑 : " + path);  
  49.         // 設置上傳工廠  
  50.         DiskFileItemFactory factory = new DiskFileItemFactory();  
  51.         factory.setRepository(new File(path));  
  52.         // 閥值,超過這個值纔會寫到臨時目錄  
  53.         factory.setSizeThreshold(1024 * 1024 * 10);  
  54.         ServletFileUpload upload = new ServletFileUpload(factory);  
  55.         // 最大上傳限制  
  56.         upload.setSizeMax(1024 * 1024 * 200);  
  57.         // 設置監聽器監聽上傳進度  
  58.         upload.setProgressListener(p);  
  59.         try {  
  60.             LOG.info("解析上傳文件....");  
  61.             List<FileItem> items = upload.parseRequest(request);  
  62.               
  63.             LOG.info("上傳數據...");  
  64.             for (FileItem item : items) {  
  65.                   
  66.                 // 非表單域  
  67.                 if (!item.isFormField()) {  
  68.                     LOG.info("上傳路徑  : " + path + item.getName());  
  69.                     FileOutputStream fos = new FileOutputStream(path + item.getName());  
  70.                     // 文件全在內存中  
  71.                     if (item.isInMemory()) {  
  72.                         fos.write(item.get());  
  73.                         p.setComplete(true);  
  74.                     } else {  
  75.                         InputStream is = item.getInputStream();  
  76.                         byte[] buffer = new byte[1024];  
  77.                         int len;  
  78.                         while ((len = is.read(buffer)) > 0) {  
  79.                             fos.write(buffer, 0, len);  
  80.                         }  
  81.                         is.close();  
  82.                     }  
  83.                     fos.close();  
  84.                     LOG.info("完成上傳文件!");  
  85.                       
  86.                     item.delete();  
  87.                     LOG.info("刪除臨時文件!");  
  88.                       
  89.                     p.setComplete(true);  
  90.                     LOG.info("更新progress對象狀態爲完成狀態!");  
  91.                 }  
  92.             }  
  93.         } catch (Exception e) {  
  94.             LOG.error("上傳文件出現異常, 錯誤原因 : " + e.getMessage());  
  95.             // 發生錯誤,進度信息對象設置爲完成狀態  
  96.             p.setComplete(true);  
  97.             request.getSession().removeAttribute(key);  
  98.         }  
  99.     }  
  100.       
  101.     /** 
  102.      * 執行客戶端腳本 
  103.      *  
  104.      * @param response 
  105.      *            http response 
  106.      * @param script 
  107.      *            javscript string 
  108.      * @throws IOException  
  109.      *            IOException 
  110.      */   
  111.     public static void execClientScript(HttpServletResponse resposne,  
  112.             String script) throws IOException {  
  113.           
  114.         PrintWriter out = resposne.getWriter();  
  115.           
  116.         out.println("<mce:script type='text/javascript'><!--  
  117. " + script + "  
  118. // --></mce:script>");  
  119.         // fix ie problem  
  120.         out.println("---------------------------------------------------");  
  121.         out.println("---------------------------------------------------");  
  122.         out.println("---------------------------------------------------");  
  123.         out.println("---------------------------------------------------");  
  124.         out.println("---------------------------------------------------");  
  125.         out.println("---------------------------------------------------");  
  126.         out.println("---------------------------------------------------");  
  127.         out.flush();  
  128.     }  
  129.     /** 
  130.      * 上傳文件進度信息 
  131.      *  
  132.      * @author wanglei 
  133.      * @version 0.1 
  134.      */  
  135.     public static class Progress implements ProgressListener {  
  136.         // 文件總長度  
  137.         private long length = 0;  
  138.         // 已上傳的文件長度  
  139.         private long currentLength = 0;  
  140.         // 上傳是否完成  
  141.         private boolean isComplete = false;  
  142.         /* 
  143.          * (non-Javadoc) 
  144.          * @see org.apache.commons.fileupload.ProgressListener#update(long, 
  145.          * long, int) 
  146.          */  
  147.         @Override  
  148.         public void update(long bytesRead, long contentLength, int items) {  
  149.             this.currentLength = bytesRead;  
  150.         }  
  151.         /** 
  152.          * the getter method of length 
  153.          *  
  154.          * @return the length 
  155.          */  
  156.         public long getLength() {  
  157.             return length;  
  158.         }  
  159.         /** 
  160.          * the getter method of currentLength 
  161.          *  
  162.          * @return the currentLength 
  163.          */  
  164.         public long getCurrentLength() {  
  165.             return currentLength;  
  166.         }  
  167.         /** 
  168.          * the getter method of isComplete 
  169.          *  
  170.          * @return the isComplete 
  171.          */  
  172.         public boolean isComplete() {  
  173.             return isComplete;  
  174.         }  
  175.         /** 
  176.          * the setter method of the length 
  177.          *  
  178.          * @param length 
  179.          *            the length to set 
  180.          */  
  181.         public void setLength(long length) {  
  182.             this.length = length;  
  183.         }  
  184.         /** 
  185.          * the setter method of the currentLength 
  186.          *  
  187.          * @param currentLength 
  188.          *            the currentLength to set 
  189.          */  
  190.         public void setCurrentLength(long currentLength) {  
  191.             this.currentLength = currentLength;  
  192.         }  
  193.         /** 
  194.          * the setter method of the isComplete 
  195.          *  
  196.          * @param isComplete 
  197.          *            the isComplete to set 
  198.          */  
  199.         public void setComplete(boolean isComplete) {  
  200.             this.isComplete = isComplete;  
  201.         }  
  202.     }  
  203. }  

 

action代碼:

 

  1. import java.io.IOException;  
  2. import com.ufinity.mars.utils.UploadFile;  
  3. import com.ufinity.mars.utils.UploadFile.Progress;  
  4. import com.ufinity.savor.service.FileService;  
  5. /** 
  6.  * file action 
  7.  *  
  8.  * @author scott.Cgi 
  9.  */  
  10. public class FileAction extends AbstractAction {  
  11.     /** {field's description} */  
  12.     private static final long serialVersionUID = 6649027352616232244L;  
  13.     private FileService fileService;  
  14.     /** 
  15.      * 上傳文件頁面 
  16.      *  
  17.      * @return page view 
  18.      */  
  19.     public String preupload() {  
  20.         return SUCCESS;  
  21.     }  
  22.     /** 
  23.      * 上傳文件 
  24.      *  
  25.      * @return page view 
  26.      */  
  27.     public String uploadfile() {  
  28.         try {  
  29.             UploadFile.upload(this.request, this.response);  
  30.         } catch (IOException e) {  
  31.             LOG.error("上傳文件發生異常,錯誤原因 : " + e.getMessage());  
  32.         }  
  33.         return null;  
  34.     }  
  35.     /** 
  36.      * 顯示上傳文件進度進度 
  37.      *  
  38.      * @return page view 
  39.      */  
  40.     public String progress() {  
  41.         String callback1 = this.request.getParameter("callback1");  
  42.         String callback2 = this.request.getParameter("callback2");  
  43.         // 緩存progress對象的key值  
  44.         String key = Integer.toString(request.hashCode());  
  45.         // 新建當前上傳文件的進度信息對象  
  46.         Progress p = new Progress();  
  47.         // 緩存progress對象  
  48.         this.request.getSession().setAttribute(key, p);  
  49.           
  50.           
  51.         response.setContentType("text/html;charset=UTF-8");  
  52.         response.setHeader("pragma""no-cache");  
  53.         response.setHeader("cache-control""no-cache");  
  54.         response.setHeader("expires""0");  
  55.         try {  
  56.             UploadFile.execClientScript(response, callback1 + "(" + key + ")");  
  57.             long temp = 0l;  
  58.             while (!p.isComplete()) {  
  59.                 if (temp != p.getCurrentLength()) {  
  60.                     temp = p.getCurrentLength();  
  61.                     // 向客戶端顯示進度  
  62.                     UploadFile.execClientScript(response, callback2 + "("  
  63.                             + p.getCurrentLength() + "," + p.getLength() + ")");  
  64.                       
  65.                 } else {  
  66.                     //LOG.info("progress的狀態 :" + p.isComplete());  
  67.                     //LOG.info("progress上傳的數據量 :+ " + p.getCurrentLength());  
  68.                     //上傳進度沒有變化時候,不向客戶端寫數據,寫數據過於頻繁會讓chrome沒響應  
  69.                     Thread.sleep(300);  
  70.                 }  
  71.                   
  72.             }  
  73.               
  74.         } catch (Exception e) {  
  75.             LOG.error("調用客戶端腳本錯誤,原因 :" + e.getMessage());  
  76.             p.setComplete(true);  
  77.               
  78.         }  
  79.           
  80.         this.request.getSession().removeAttribute(key);  
  81.         LOG.info("刪除progress對象的session key");  
  82.           
  83.         return null;  
  84.     }  
  85.     /** 
  86.      * the getter method of fileService 
  87.      *  
  88.      * @return the fileService 
  89.      */  
  90.     public FileService getFileService() {  
  91.         return fileService;  
  92.     }  
  93.     /** 
  94.      * the setter method of the fileService 
  95.      *  
  96.      * @param fileService 
  97.      *            the fileService to set 
  98.      */  
  99.     public void setFileService(FileService fileService) {  
  100.         this.fileService = fileService;  
  101.     }  
  102. }  

 

頁面代碼:

 

  1.     <mce:style type="text/css"><!--  
  2.         iframe{  
  3.             border:none;  
  4.             width:0;  
  5.             height:0;  
  6.         }  
  7.           
  8.         #p_out{  
  9.           width:200px;  
  10.           height:12px;  
  11.           margin:10px 0 0 0;  
  12.           padding:1px;  
  13.           font-size:10px;  
  14.           border:solid #6b8e23 1px;  
  15.         }  
  16.           
  17.         #p_in{  
  18.           width:0%;  
  19.           height:100%;  
  20.           background-color:#6b8e23;  
  21.           margin:0;  
  22.           padding:0;  
  23.         }  
  24.           
  25.         #dis{  
  26.           margin:0;  
  27.           padding:0;  
  28.           text-align:center;  
  29.           font-size:12px;  
  30.           height:12px;  
  31.           width:200px;  
  32.         }  
  33.           
  34. --></mce:style><style type="text/css" mce_bogus="1">     iframe{  
  35.             border:none;  
  36.             width:0;  
  37.             height:0;  
  38.         }  
  39.           
  40.         #p_out{  
  41.           width:200px;  
  42.           height:12px;  
  43.           margin:10px 0 0 0;  
  44.           padding:1px;  
  45.           font-size:10px;  
  46.           border:solid #6b8e23 1px;  
  47.         }  
  48.           
  49.         #p_in{  
  50.           width:0%;  
  51.           height:100%;  
  52.           background-color:#6b8e23;  
  53.           margin:0;  
  54.           padding:0;  
  55.         }  
  56.           
  57.         #dis{  
  58.           margin:0;  
  59.           padding:0;  
  60.           text-align:center;  
  61.           font-size:12px;  
  62.           height:12px;  
  63.           width:200px;  
  64.         }  
  65.         </style>  
  66.     </head>  
  67.     <body>  
  68.         <div class="main">  
  69.           
  70.         <div class="top">  
  71.            <jsp:include page="/top.jsp" />  
  72.         </div>  
  73.           
  74.         <div style="width: 250px; margin: 0 auto;">  
  75.           
  76.         
  77.            <div class="errorbox">  
  78.                 <s:actionerror/>  
  79.            </div>  
  80.               
  81.            <form id="uploadfile_form" name="uploadfile_form" enctype="multipart/form-data"    
  82.                  method="post" target="uploadfile_iframe">    
  83.              
  84.                 <input type="file" name="file" />    
  85.                 <br><br>    
  86.                 <button onclick="progress()">提交</button>    
  87.              
  88.                 <div id="p_out"><div id="p_in"></div></div>    
  89.                 <div id="dis"></div>    
  90.            </form>    
  91.              
  92.            <iframe frameborder="0" id="uploadfile_iframe" name="uploadfile_iframe" src="javascript:void(0)" mce_src="javascript:void(0)"></iframe>    
  93.            <iframe frameborder="0" id="progress_iframe" name="progress_iframe" src="javascript:void(0)" mce_src="javascript:void(0)"></iframe>    
  94.         </div>  
  95.           
  96.         </div>  
  97.           
  98.     </body>  
  99.       
  100.     <mce:script type="text/javascript"><!--  
  101.     //上傳文件  
  102.     function uploadFile(key){  
  103.         document.forms[0].action = 'uploadfile.action?callback=parent.upload&key='+key;  
  104.         document.forms[0].submit();  
  105.         document.getElementById('dis').innerHTML = "開始傳送數據...";  
  106.           
  107.     }  
  108.     //獲取文件上傳進度  
  109.     function progress(){  
  110.         document.getElementById('progress_iframe').src = 'progress.action?callback1=parent.uploadFile&callback2=parent.upload';  
  111.         document.getElementById('dis').innerHTML = '初始化數據...';  
  112.         document.getElementById('p_in').style.width = "0%";  
  113.     }  
  114.     //更新進度  
  115.     function upload(len, total){  
  116.         document.getElementById('p_in').style.width = (Math.round(len/total*100))+'%';  
  117.         document.getElementById('dis').innerHTML = len + '/' + total + ' Byte';  
  118.         if(len === total) {  
  119.             document.getElementById('dis').innerHTML = "文件上傳完成!";  
  120.         }  
  121.     }      
  122.       
  123.       
  124. // --></mce:script>  
  125. </html>  

 

 

注意: common-upload.jar依賴common-io.jar

 

 

 

     最後,以前寫過一個servlet的上傳文件顯示進度(很久以前的事了,唉。。。),在這裏:

http://blog.csdn.net/tom_221x/archive/2009/01/14/3777064.aspx

 

 

     有興趣的實現一個試試吧,還以加入多個文件的隊列上傳,或是多線程上傳,還可以加入斷點續傳。哈哈,實現了記得要開源噢!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章