最近在做一个Struts2上传功能的模块,需求很明确
- 能控制上传文件类型
- 能根据上传文件类型控制上文件大小
- 所有要界面友好(容错,给予足够的提示)
一、开工前准备
1、了解struts2 上传原理
打开struts-default.xml我们发现,struts2在做文件上传时首先定义了struts.multipart.parser这一个专门用于解析上传文件的解析器,默认由JakartaMultiPartRequest实现,并且使用了fileUpload这个拦截器在请求尚未进入Action之前将文件从客户端浏览器读入服务器缓冲区,等待处理,也就是说在Action还未对文件作出处理之前Struts已经将文件上传完成,后续工作(主要是文件转移和存档)则交给开发者去完成(强大),FileUpload这个拦截器对应的类的名字就叫作FileUploadInterceptor.java,
<interceptor-stack name="defaultStack"> <interceptor-ref name="exception"/> <interceptor-ref name="alias"/> <interceptor-ref name="servletConfig"/> <interceptor-ref name="i18n"/> <interceptor-ref name="prepare"/> <interceptor-ref name="chain"/> <interceptor-ref name="scopedModelDriven"/> <interceptor-ref name="modelDriven"/> <interceptor-ref name="fileUpload"/> <interceptor-ref name="checkbox"/> ...<!-- 此处省略其他拦截器配置 --> </interceptor-stack>
在这个类的intercept()方法中,它首先是拿到了此次请求的对象主体HttpServletRequest
ActionContext ac = invocation.getInvocationContext(); HttpServletRequest request = (HttpServletRequest) ac.get(ServletActionContext.HTTP_REQUEST);
然后再把它强制转换成MultiPartRequestWrapper,也就是在request的外面再包裹一层,通过HttpServletRequestWrapper包装文件上传请求,模块化文件上传处理,提高代码复用率。MultiPartRequestWrapper持有被装饰者(HttpServletRequest对象)引用,初始化用于存放MultipartRequest的参数的Map,同时也持有MultiPartRequest的一个引用(其中的parse()方法)方便于解析上传和文件,而MultiPartRequest是一个接口,由JakartaMultiPartRequest来具体实现。
多媒体请求到达->进行包裹,转换成commons-fileupload能够接受的MultipartRequest对象->交由ServletFileUpload处理(建立连接管道等)
二、整体思路
监听上传状态并保存到Session中,在上传过程中客户端定时向服务器询问这个状态
好,问题来了,既然Struts在Action前已经将文件上传这一步的工作完成了,我们怎么去监听文件上传的进度?
--改默认配置!
改之前先创建一个替换对像,上文说过,在Struts中上传解析工作是由JakartaMultiPartRequest中的parse()具体来实现的(解析来自ServletFileUpload的流),可它并没有给提供定时更新上传状态的功能,所以我们要给它加一个监听器,监听其状态,监听器要实现apache的ProgressListener接口,这样可以让每一次状态的改变都能被这个监听器监听到。
开始着手做:
监听上传状态
复制一份JakartaMultiPartRequest.java取名:DerrickMultiPartRequest.java放到自己的一个包中,重写其parseRequest()方法如下:
private List<FileItem> parseRequest(HttpServletRequest servletRequest, String saveDir) throws FileUploadException { UploadStatus ups = new UploadStatus(); //UploadStatus自己新建的类 UploadingListener upadlistener = new UploadingListener(ups);//UploadingListener实现ProgressListener接口 DiskFileItemFactory fac = createDiskFileItemFactory(saveDir); ServletFileUpload upload = new ServletFileUpload(fac); upload.setSizeMax(maxSize); // 设置进度监听器 upload.setProgressListener(upadlistener); System.out.println("====================parseRequest setting listener completed ================="); return upload.parseRequest(createRequestContext(servletRequest)); }
//UploadStatus.java public class UploadStatus { private long readbytes; private long contentLengh; private int item; //getters and setters are begin here ... }
将状态保存到Session里
//UploadingListener.java public class UploadingListener implements ProgressListener{ UploadStatus ups ; public UploadingListener(UploadStatus ups){ this.ups = ups; } /* (non-Javadoc) * @see org.apache.commons.fileupload.ProgressListener#update(long, long, int) */ @Override public void update(long readbytes, long contentLengh, int itemIndex) { ups.setContentLengh(contentLengh); ups.setReadbytes(readbytes); ups.setItem(itemIndex); Map<String,Object> session = ServletActionContext.getContext().getSession(); session.put("processStatus", ups); }
替换准备工作完成,下面着手编写Action
FileUploadAction 完成两个工作,文件转移和响应状态询问
//FileUploadAction.java public class FileUploadAction extends BaseAction{ private static final long serialVersionUID = 1L; private List<File> upload; private List<String> uploadFileName; private List<String> uploadContentType; private IFileService fileService; /* * 文件上传分发 */ public String upload() throws AuthorizationException, IOException{ String flag = "decline"; for (int i=0;i<upload.size();i++){ if(this.getUploadContentType().get(i).startsWith("image")){ flag = imgupload(); }else if(this.getUploadContentType().get(i).startsWith("application")){ flag = fileupload(); }else{ flag = "fileupload"; } } return flag; } /* (non-Javadoc) * 除图片外其他类型文件上传 */ public String fileupload() throws AuthorizationException ,IOException{ String root = ServletActionContext.getServletContext().getRealPath("/uploads"); InputStream is = null; OutputStream os = null; List<String> list = new ArrayList<String>(); for(int i = 0 ; i < upload.size(); i++){ String str = uploadFileName.get(i).substring(uploadFileName.get(i).lastIndexOf("\\")+1); File f = Common.CreateFile(root, str); is = new FileInputStream(upload.get(i)); os = new FileOutputStream(f); int len = 0; byte[] buffer = new byte[1024]; while(len != -1){ len = is.read(buffer, 0, buffer.length); os.write(buffer); } is.close(); os.close(); list.add(root.substring(root.lastIndexOf("\\")+1)+"\\"+str); } getSession().put("uploadList", list); return SUCCESS; } /* (non-Javadoc) * 上传图片方法 */ public String imgupload() throws AuthorizationException ,IOException{ String root = ServletActionContext.getServletContext().getRealPath("/uploads/img"); InputStream is = null; OutputStream os = null; List<String> list = new ArrayList<String>(); for(int i = 0 ; i < upload.size(); i++){ String str = uploadFileName.get(i).substring(uploadFileName.get(i).lastIndexOf("\\")+1); File f = Common.CreateFile(root, str); is = new FileInputStream(upload.get(i)); os = new FileOutputStream(f); int len = 0; byte[] buffer = new byte[1024]; while(len != -1){ len = is.read(buffer, 0, buffer.length); os.write(buffer); } is.close(); os.close(); list.add(root.substring(root.lastIndexOf("\\")+1)+"\\"+str); } getSession().put("uploadList", list); return SUCCESS; } public void uploadStatus() throws Exception{ BigDecimal read ; BigDecimal total; BigDecimal bdpercent; double percent = 0.00; HttpServletResponse response = ServletActionContext.getResponse(); response.setContentType("text/html;charset=utf-8"); PrintWriter out = response.getWriter(); UploadStatus ups = (UploadStatus) getSession().get("processStatus"); if(null!=ups){ if(0 != ups.getReadbytes()){ read = new BigDecimal(ups.getReadbytes()); total = new BigDecimal(ups.getContentLengh()); bdpercent = read.divide(total, 2, BigDecimal.ROUND_UP); percent = bdpercent.doubleValue(); } }else{ System.out.println("ups is null"); } out.print((int)(percent*100)); out.flush(); out.close(); //return null; }
至此后台就改造完成了,配置一下:
//struts.xml定义要替换的MultiPartRequest的实现并让struts应用更改的实现而不使用其自带的Jakatra <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="derrick" class="com.llb.base.impl.DerrickMultiPartRequest" scope="default" /> <constant name="struts.multipart.parser" value="derrick" />
//定义自己的拦截器栈,注意一定要追加defaultStack这个栈,不然struts的其他拦截器是会在自己的这个栈之后执行,后果不堪设想 <interceptors> <interceptor-stack name="imageUploadStack"> <interceptor-ref name="fileUpload"> <param name="allowedTypes"> image/bmp,image/png,image/gif,image/jpeg,image/pjpeg,image/x-png </param> <param name="maximumSize">5242880</param><!-- 50M --> </interceptor-ref> <interceptor-ref name="defaultStack" /> </interceptor-stack> <interceptor-stack name="attachedUploadStack"> <interceptor-ref name="fileUpload"> <param name="allowedTypes"> application/octet-stream,application/zip,application/x-zip-compressed,application/x-rar-compressed </param> <param name="maximumSize">209715200</param><!-- 200M --> </interceptor-ref> <interceptor-ref name="defaultStack" /> </interceptor-stack> </interceptors>
//配置Action <action name="upload" class="fileUploadAction" method="upload" > <result>${appContext}/upload/form.jsp</result> <result name="input">${appContext}/upload/form.jsp</result> <result name="decline" type="redirect">${appContext}/login.jsp</result> </action> <action name="fileupload" class="fileUploadAction" method="fileupload" > <interceptor-ref name="attachedUploadStack" /> <result>${appContext}/upload/form.jsp</result> <result name="input">${appContext}/upload/form.jsp</result> <result name="error">${appContext}/upload/error.jsp</result> <result name="decline">${appContext}/login.jsp</result> <!-- <exception-mapping result="error" exception="java.io.FileNotFoundException" /> --> </action> <!-- imgupload action 略 --> <!--页面定时刷新的后台接收Action--> <action name="fileuploadstatus" class="fileUploadAction" method="uploadStatus" />
客户端定时向服务器询问
程序页面的主体部分:
JS脚本
<script type="text/javascript"> $(document).ready(function(){ $("#btn_submit").click(function(){ //updateStatus(); setInterval(updateStatus, 1000); }); function updateStatus(){ $('.div_process').show('slow'); $.ajax({ type:'get', url: "fileuploadstatus.action", success: function(percent){ $(".bar").css('width',percent+'%'); $(".txt_percent").text(percent+'%'); } }); } }); $(document).ajaxComplete(function (){ $('.bar').add("<span>Ajax Complete trigger on</span>"); }) </script>
HTML
<div class="container"> <div class="content"> <div class="main"> <div class="progress progress-striped active hide div_process"> <div class="bar" style="width:2px;"> <span class="txt_percent">0%</span> </div> </div> <s:fielderror name="tooLarge"></s:fielderror> <!--<s:actionerror />--> <div class="file-box"> <s:form enctype="multipart/form-data" class="form-inline" action="upload.action" method="post" > <input id="textfield" type="text" name="fileName" style="width:180px; margin-bottom:0px;" /> <input class="btn" type="button" value="浏览..." /> <input id="fileField" class="file" type="file" style="width:260px; cursor:pointer;" onchange="document.getElementById('textfield').value=this.value" name="upload" /> <input id ="btn_submit" class="btn" type="submit" value="上传" name="submit" /> </s:form> </div> </div> </div> <!-- end .container --> </div>
好了,一切就绪,跑起来吧:
首先是上传表单页面
上传中