Struts 2 带进度条的文件上传

最近在做一个Struts2上传功能的模块,需求很明确

  1. 能控制上传文件类型
  2. 能根据上传文件类型控制上文件大小
  3. 所有要界面友好(容错,给予足够的提示)

一、开工前准备

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>

 好了,一切就绪,跑起来吧:

 首先是上传表单页面

 上传中

 

猜你喜欢

转载自linbaolee.iteye.com/blog/2093814