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

 

 

     有兴趣的实现一个试试吧,还以加入多个文件的队列上传,或是多线程上传,还可以加入断点续传。哈哈,实现了记得要开源噢!


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