原理:
利用Ajax在客户端一直查询服务器端的上传进度,取得进度的状态文本信息(xml,json格式的文本等),然后利用JS解析,显示在前台。
在Struts2. 0中,框架事先已经定义一种监听器:ProgressListener(进度监听器),里面有一个update(long readedBytes, long totalBytes, int currentItem)方法,其中,readedBytes是已经上传到服务器的位数,而totalBytes是上传文件总位数.当文件已二进制的方式上传时,每上传一部分数据,就会调用这个方法一次。故要实现监听进度,必须实现这个接口,并实现update方法,在update方法中保存这个进度到session。当客服端需要进度的信息时,只需要访问某个action,在这个action中读取session中保存的进度状态就可以了.
上传文件可大致分为两个阶段:1. 上传到服务器上,在临时目录中 2.从临时目录中把文件移到指定目录(由自己写的action处理),而struts2.的监听器只监听第一阶段。
实现:
(源代码下载: http://download.csdn.net/source/3568014)
第一步:
实现ProgressListener接口,实现update( )方法,详情见action包中的FileUploadListener.java 文件,里面有一个自定义的类:State ,它描述的是进度的状态,详情请看State注释。Update方法要做的就是不断地更新session中的state对象 代码如下:
package action; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.commons.fileupload.ProgressListener; public class FileUploadListener implements ProgressListener{ private HttpSession session; public FileUploadListener(HttpServletRequest request) { session = request.getSession(); State state = new State(); session.setAttribute("state", state); } @Override public void update(long readedBytes, long totalBytes, int currentItem) { // TODO Auto-generated method stub System.out.println("update:"+readedBytes+";"+totalBytes+";"+currentItem); State state = (State) session.getAttribute("state"); state.setReadedBytes(readedBytes); state.setTotalBytes(totalBytes); state.setCurrentItem(currentItem); } }
State类:
package action; public class State { private long readedBytes = 0L;/*已经上传的位数*/ private long totalBytes = 0L;/*文件所占位数*/ private int currentItem = 0; private int rate=0; /*上传百分比*/ public long getReadedBytes() { return readedBytes; } public void setReadedBytes(long readedBytes) { this.readedBytes = readedBytes; } public long getTotalBytes() { return totalBytes; } public void setTotalBytes(long totalBytes) { this.totalBytes = totalBytes; } public int getCurrentItem() { return currentItem; } public void setCurrentItem(int currentItem) { this.currentItem = currentItem; } public int getRate() { return rate; } public void setRate(int rate) { this.rate = rate; } }
第二步:
将监听器注入到struts2.0的MultiPartRequest封装类中,客户端发送request到服务器,struts2.0会将request封装成MultiPartRequest。因此必须将监听器注入到MultiPartRequest中。只需要在MultiPartRequest中加入以下两句:
FileUploadListener progressListener = new FileUploadListener(servletRequest);
upload.setProgressListener(progressListener);//添加自己的监听器
所以重新写一个新类MyMultiPartRequest代替MultiPartRequest ,代码与org.apache.struts2.dispatcher.multipart.MultiPartRequest 一样,在方法
private List<FileItem> parseRequest(HttpServletRequest servletRequest, String saveDir) 中加入监听器.
如下:
package action; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.util.logging.Logger; import com.opensymphony.xwork2.util.logging.LoggerFactory; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.RequestContext; import org.apache.commons.fileupload.disk.DiskFileItem; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.struts2.StrutsConstants; import javax.servlet.http.HttpServletRequest; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.struts2.dispatcher.multipart.MultiPartRequest; public class MyMultiPartRequest implements MultiPartRequest{ static final Logger LOG = LoggerFactory.getLogger(MultiPartRequest.class); // maps parameter name -> List of FileItem objects protected Map<String,List<FileItem>> files = new HashMap<String,List<FileItem>>(); // maps parameter name -> List of param values protected Map<String,List<String>> params = new HashMap<String,List<String>>(); // any errors while processing this request protected List<String> errors = new ArrayList<String>(); protected long maxSize; @Inject(StrutsConstants.STRUTS_MULTIPART_MAXSIZE) public void setMaxSize(String maxSize) { this.maxSize = Long.parseLong(maxSize); } /** * Creates a new request wrapper to handle multi-part data using methods adapted from Jason Pell's * multipart classes (see class description). * * @param saveDir the directory to save off the file * @param request the request containing the multipart * @throws java.io.IOException is thrown if encoding fails. */ public void parse(HttpServletRequest request, String saveDir) throws IOException { try { processUpload(request, saveDir); } catch (FileUploadException e) { LOG.warn("Unable to parse request", e); errors.add(e.getMessage()); } } private void processUpload(HttpServletRequest request, String saveDir) throws FileUploadException, UnsupportedEncodingException { for (FileItem item : parseRequest(request, saveDir)) { if (LOG.isDebugEnabled()) { LOG.debug("Found item " + item.getFieldName()); } if (item.isFormField()) { processNormalFormField(item, request.getCharacterEncoding()); } else { processFileField(item); } } } private void processFileField(FileItem item) { LOG.debug("Item is a file upload"); // Skip file uploads that don't have a file name - meaning that no file was selected. if (item.getName() == null || item.getName().trim().length() < 1) { LOG.debug("No file has been uploaded for the field: " + item.getFieldName()); return; } List<FileItem> values; if (files.get(item.getFieldName()) != null) { values = files.get(item.getFieldName()); } else { values = new ArrayList<FileItem>(); } values.add(item); files.put(item.getFieldName(), values); } private void processNormalFormField(FileItem item, String charset) throws UnsupportedEncodingException { LOG.debug("Item is a normal form field"); List<String> values; if (params.get(item.getFieldName()) != null) { values = params.get(item.getFieldName()); } else { values = new ArrayList<String>(); } // note: see http://jira.opensymphony.com/browse/WW-633 // basically, in some cases the charset may be null, so // we're just going to try to "other" method (no idea if this // will work) if (charset != null) { values.add(item.getString(charset)); } else { values.add(item.getString()); } params.put(item.getFieldName(), values); } private List<FileItem> parseRequest(HttpServletRequest servletRequest, String saveDir) throws FileUploadException { DiskFileItemFactory fac = createDiskFileItemFactory(saveDir); ServletFileUpload upload = new ServletFileUpload(fac); upload.setSizeMax(maxSize); /*自己新建监听器*/ FileUploadListener progressListener = new FileUploadListener(servletRequest); upload.setProgressListener(progressListener);//添加自己的监听器 return upload.parseRequest(createRequestContext(servletRequest)); } private DiskFileItemFactory createDiskFileItemFactory(String saveDir) { DiskFileItemFactory fac = new DiskFileItemFactory(); // Make sure that the data is written to file fac.setSizeThreshold(0); if (saveDir != null) { fac.setRepository(new File(saveDir)); } return fac; } /* (non-Javadoc) * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileParameterNames() */ public Enumeration<String> getFileParameterNames() { return Collections.enumeration(files.keySet()); } /* (non-Javadoc) * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getContentType(java.lang.String) */ public String[] getContentType(String fieldName) { List<FileItem> items = files.get(fieldName); if (items == null) { return null; } List<String> contentTypes = new ArrayList<String>(items.size()); for (FileItem fileItem : items) { contentTypes.add(fileItem.getContentType()); } return contentTypes.toArray(new String[contentTypes.size()]); } /* (non-Javadoc) * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFile(java.lang.String) */ public File[] getFile(String fieldName) { List<FileItem> items = files.get(fieldName); if (items == null) { return null; } List<File> fileList = new ArrayList<File>(items.size()); for (FileItem fileItem : items) { File storeLocation = ((DiskFileItem) fileItem).getStoreLocation(); if(fileItem.isInMemory() && storeLocation!=null && !storeLocation.exists()) { try { storeLocation.createNewFile(); } catch (IOException e) { if(LOG.isErrorEnabled()){ LOG.error("Cannot write uploaded empty file to disk: " + storeLocation.getAbsolutePath(),e); } } } fileList.add(storeLocation); } return fileList.toArray(new File[fileList.size()]); } /* (non-Javadoc) * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileNames(java.lang.String) */ public String[] getFileNames(String fieldName) { List<FileItem> items = files.get(fieldName); if (items == null) { return null; } List<String> fileNames = new ArrayList<String>(items.size()); for (FileItem fileItem : items) { fileNames.add(getCanonicalName(fileItem.getName())); } return fileNames.toArray(new String[fileNames.size()]); } /* (non-Javadoc) * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFilesystemName(java.lang.String) */ public String[] getFilesystemName(String fieldName) { List<FileItem> items = files.get(fieldName); if (items == null) { return null; } List<String> fileNames = new ArrayList<String>(items.size()); for (FileItem fileItem : items) { fileNames.add(((DiskFileItem) fileItem).getStoreLocation().getName()); } return fileNames.toArray(new String[fileNames.size()]); } /* (non-Javadoc) * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameter(java.lang.String) */ public String getParameter(String name) { List<String> v = params.get(name); if (v != null && v.size() > 0) { return v.get(0); } return null; } /* (non-Javadoc) * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterNames() */ public Enumeration<String> getParameterNames() { return Collections.enumeration(params.keySet()); } /* (non-Javadoc) * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterValues(java.lang.String) */ public String[] getParameterValues(String name) { List<String> v = params.get(name); if (v != null && v.size() > 0) { return v.toArray(new String[v.size()]); } return null; } /* (non-Javadoc) * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getErrors() */ public List getErrors() { return errors; } /** * Returns the canonical name of the given file. * * @param filename the given file * @return the canonical name of the given file */ private String getCanonicalName(String filename) { int forwardSlash = filename.lastIndexOf("/"); int backwardSlash = filename.lastIndexOf("\\"); if (forwardSlash != -1 && forwardSlash > backwardSlash) { filename = filename.substring(forwardSlash + 1, filename.length()); } else if (backwardSlash != -1 && backwardSlash >= forwardSlash) { filename = filename.substring(backwardSlash + 1, filename.length()); } return filename; } /** * Creates a RequestContext needed by Jakarta Commons Upload. * * @param req the request. * @return a new request context. */ private RequestContext createRequestContext(final HttpServletRequest req) { return new RequestContext() { public String getCharacterEncoding() { return req.getCharacterEncoding(); } public String getContentType() { return req.getContentType(); } public int getContentLength() { return req.getContentLength(); } public InputStream getInputStream() throws IOException { InputStream in = req.getInputStream(); if (in == null) { throw new IOException("Missing content in the request"); } return req.getInputStream(); } }; } }
在struts.xml中重新指定MultiPartRequest:
<bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="requestParser"
class="action.MyMultiPartRequest" scope="default" optional="true" />
<constant name="struts.multipart.handler" value="requestParser" />
到这里,基本完成大部分工作.
接下来是业务,写两个Action ,一个是查询进度的FileProgressAction ,一个是处理上传文件FileProgressUploadAction . FileProgressAction从Session中读入state对象,返回客户端。
在客户端方面,点击”提交”的时候,需要完成两步工作,1.不断到服务器查询进度2.上传文件,因为本人JS不是很好,就潦草写了一些,主要用的是Jquery框架。然后查询进度时,state对象以json数据方式传递到客户端的。
struts.xml文件:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd"> <struts> <!-- 限定文件大小 --> <constant name="struts.multipart.maxSize" value="500000000000000" /> <package name="upload" namespace="/upload" extends="json-default"> <!-- 查询上传进度 --> <action name="progress" class="action.FileProgressAction"> <result name="success" type="json"></result> </action> <!-- 上传文件 --> <action name="upload" class="action.FileProgressUploadAction"> <result name="success">/index.jsp</result> <result name="input">/error.jsp</result> </action> </package> <!-- 重新指定request封装类 --> <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="requestParser" class="action.MyMultiPartRequest" scope="default" optional="true" /> <constant name="struts.multipart.handler" value="requestParser" /> </struts>
前台页面:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <script type="text/javascript" src="./js/jquery-1.4.min.js"></script> <script> var id=0; function addressAction(){ $.post( './upload/progress.action', function(data){ if(data.currentItem==0){ $("#m").text('0%'); }else if(data.state.rate!=100){ $("#m").text(data.state.readedBytes+'/'+data.state.totalBytes+':'+data.state.rate+'%'); }else{ $("#m").text(data.state.readedBytes+'/'+data.state.totalBytes+':'+'100% 上传完成!'); window.clearInterval(id); } $("#img").html(""); var num=data.state.rate/10; for(var i=1;i<=num;i++){ $("#img").append("<img src='./images/grid.gif' />"); } for(var j=1;j<=10-num;j++){ $("#img").append("<img src='./images/gray.gif' />"); } }, 'json' ); } function submitForm(){ if($("#f1").val()==""){ alert('上传文件为空!!!'); return ; } id=window.setInterval(addressAction,1000); $("form:first").submit();/*提交第一个表单*/ } </script> </head> <body> <form action="./upload/upload.action" method="post" enctype="multipart/form-data"> File:<input type="file" name="file" id="f1" /> <br/> <span id="m"></span><br/> <span id="img"></span> <br/> <input type="button" onclick="submitForm()" value="上传" /> </form> <br/> </body> </html>
注意事项:
struts-json包要引进来
使用Jquery框架