需要做一个文件上传进度的效果,结合网上资料和自己的实践后,这里做一个整理
步骤如下:
1.重写、自定义JakartaMultiPartRequest类
<span style="font-size:12px;">package com.hikvision.fileUploadProcess.interceptor; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest; public class MyJakartaMultiPartRequest extends JakartaMultiPartRequest { @Override public void parse(HttpServletRequest servletRequest, String saveDir) throws IOException { //什么也不做 } }</span><strong style="font-size: 14px; "> </strong>
原因:
struts2默认的拦截器中有一个FileUploadInterceptor,它会拦截所有的MultipartRequest,并且将得到的File及相关信息传递给action,因此在action被调用之前,文件上传已经被处理完了,不能引入监听文件写入时文件进度;
struts2处理文件上传使用的是commons-fileupload,因此我们可以使用ProgressListener。注意我们需要在解析请求的时候加入我们的监听器,我们首先想到的是替换掉FileUploadInterceptor,不幸的是 FileUploadInterceptor并不执行解析的任务,实际在FileUploadInterceptor被调用之前,MultipartRequest已经被解析了,文件上传的工作已经完成。而实际上对于所有的文件上传请求,struts2会为其生成一个MultiPartRequestWrapper进行包装,而它维护着一个
MultiPartRequest接口的实例。MultiPartRequest的实现类只有一个 JakartaMultiPartRequest,JakartaMultiPartRequest有一个方法parseRequest,此方法负责解析 request并生成FileItem,即对文件进行读写操作,因此我们可以重写此方法,添加ProgressListener。不幸的是,JakartaMultiPartRequest的很多方法都是private的,我们不能继承它然后重写parseRequest方法,JakartaMultiPartRequest实现了MultiPartRequest接口,我们可以编写一个类,实现
MultiPartRequest接口,替代JakartaMultiPartRequest类的代码全都拷贝过来,并修改parseRequest方法,完成文件的写入与进度的监听。
2.配置struts2.xml
<!-- 1配置自定义文件类myRequestParser,继承MultiPartRequest重写 --> <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="myRequestParser" class="com.hikvision.fileUploadProcess.interceptor.MyJakartaMultiPartRequest" scope="default" optional="true" /> <!-- 注意struts2.3.15.1以前版本这里为struts.multipart.handler, struts2.3.15.1(包含2.3.15.1)这里为struts.multipart.parser--> <constant name="struts.multipart.parser" value="myRequestParser" /> <!-- 配置项目所上传文件的最大的Size为1000M --> <constant name="struts.multipart.maxSize" value="1048576000"/>
3.定义文件上传进度信息的类
package com.hikvision.fileUploadProcess.entity; /** * 上传文件进度信息 * * @author wanglei * @version 0.1 */ public class FileUploadProgress { // 文件总长度(设置至少为1字节防止前台出现/0的情况) private long length = 1; // 已上传的文件长度 private long currentLength = 0; // 上传是否完成 private boolean isComplete = false; public long getLength() { return length; } public void setLength(long length) { this.length = length; } public long getCurrentLength() { return currentLength; } public void setCurrentLength(long currentLength) { this.currentLength = currentLength; } public boolean isComplete() { return isComplete; } public void setComplete(boolean isComplete) { this.isComplete = isComplete; } public FileUploadProgress() { super(); } }
4.实现ProgressListener接口
package com.hikvision.fileUploadProcess.impl; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.commons.fileupload.ProgressListener; import com.hikvision.fileUploadProcess.entity.FileUploadProgress; /** * 文件上传进度消息 * @author hongchenjin * */ public class FileUploadListener implements ProgressListener { private HttpSession session; public FileUploadListener(HttpServletRequest request) { session = request.getSession(); FileUploadProgress fileUploadProgress = new FileUploadProgress(); fileUploadProgress.setComplete(false); session.setAttribute("fileUploadProgress", fileUploadProgress); } //更新进度情况 @Override public void update(long readedBytes, long totalBytes, int currentItem) { //实现文件上传的核心方法 Object attribute = session.getAttribute("fileUploadProgress"); FileUploadProgress fileUploadProgress = null; if(null == attribute){ fileUploadProgress = new FileUploadProgress(); fileUploadProgress.setComplete(false); session.setAttribute("fileUploadProgress", fileUploadProgress); }else{ fileUploadProgress = (FileUploadProgress)attribute; } fileUploadProgress.setCurrentLength(readedBytes); fileUploadProgress.setLength(totalBytes); if(readedBytes==totalBytes){ fileUploadProgress.setComplete(true); }else{ fileUploadProgress.setComplete(false); } session.setAttribute("progress", fileUploadProgress); } }
5.文件上传进度的action
package com.hikvision.modules.guide.action; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import javax.servlet.http.HttpServletRequest; import org.apache.struts2.ServletActionContext; import com.hikvision.fileUploadProcess.entity.FileUploadProgress; import com.hikvision.frame.entity.OperateResult; import com.hikvision.modules.guide.entity.NameToLocalFile; import com.hikvision.modules.guide.service.GuideService; import com.hikvision.modules.guide.util.GetSharePathXml; import com.hikvision.util.AjaxUtil; import com.opensymphony.xwork2.ActionSupport; public class GuideUploadAction extends ActionSupport { private GuideService guideService; public GuideService getGuideService() { return guideService; } public void setGuideService(GuideService guideService) { this.guideService = guideService; } //文件格式不支持 public void typeNotSupport(){ OperateResult result = new OperateResult(false,""); result.setResult(false); result.setMsg("上传文件最大不能超过100M;支持的格式为exe,png,jpg,gif,bmp,doc,docx,xls,rar,txt,zip,js,css,msi,pptx"); AjaxUtil.ajaxWrite(result); } /** * 上传文件 * * @return */ public void uploadfile() { OperateResult result = new OperateResult(false,""); try { HttpServletRequest request = ServletActionContext.getRequest(); //获取文件备注 String comments = request.getParameter("comments"); //文件上传 UploadFile.upload(request, ServletActionContext.getResponse()); //将文件名和文件的对应关系存进数据库里 NameToLocalFile nameToLocalFile = new NameToLocalFile(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); String time = sdf.format(new Date()); nameToLocalFile.setCreatetime(time); //保存在服务器上的路径 nameToLocalFile.setLocalpath("/" + request.getParameter("variety") + "/" + request.getParameter("name")); nameToLocalFile.setName(request.getParameter("name")); nameToLocalFile.setVariety(request.getParameter("variety")); nameToLocalFile.setComments(comments); guideService.saveOrUpdate(nameToLocalFile); result.setResult(true); result.setMsg("上传成功"); } catch (IOException e) { LOG.error("上传文件发生异常,错误原因 : " + e.getMessage()); result.setMsg("上传文件最大不能超过100M;"); result.setResult(false); } AjaxUtil.ajaxWrite(result); } /** * 修改文件 */ public void updateNameToLocalFile(){ OperateResult result = new OperateResult(false,""); HttpServletRequest request = ServletActionContext.getRequest(); //种类 String variety = request.getParameter("variety"); //名称 String name = request.getParameter("name"); //文件备注 String comments = request.getParameter("comments"); //是否上传了文件 Boolean flag = Boolean.parseBoolean(request.getParameter("flag")); //id int id = Integer.parseInt(request.getParameterValues("ids")[0]); //根据id获取数据库的nameToLocalFileById NameToLocalFile nameToLocalFileById = guideService.getNameToLocalFileById(id); //分为两种情况,第一种为用户重新上传了文件,第二种是用户只修改了名字和种类 if(flag){ /** * 用户重新上传文件的情况 */ //删除原来文件 String localpath = nameToLocalFileById.getLocalpath(); String realPath = GetSharePathXml.getShareFolderPath() + localpath; File file = new File(realPath); //用户上传了文件 try { if(file.exists()){ //删除原来文件(判断是否存在该文件,存在就删除) file.delete(); } //文件上传 UploadFile.upload(request, ServletActionContext.getResponse()); result.setResult(true); result.setMsg("修改成功"); } catch (Exception e) { result.setResult(false); result.setMsg("修改失败"); } } //将文件名和文件的对应关系存进数据库里 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); String time = sdf.format(new Date()); nameToLocalFileById.setCreatetime(time); if(flag){ //保存在服务器上的路径 nameToLocalFileById.setLocalpath("/" + variety + "/" + name); nameToLocalFileById.setName(name); } nameToLocalFileById.setVariety(variety); nameToLocalFileById.setComments(comments); guideService.saveOrUpdate(nameToLocalFileById); result.setResult(true); result.setMsg("修改成功"); AjaxUtil.ajaxWrite(result); } /** * 显示上传文件进度进度 * * @return page view */ public void progress() { // 新建当前上传文件的进度信息对象 FileUploadProgress p = null; Object attribute = ServletActionContext.getRequest().getSession().getAttribute("fileUploadProgress"); if(null == attribute){ p = new FileUploadProgress(); // 缓存progress对象 ServletActionContext.getRequest().getSession().setAttribute("fileUploadProgress", p); }else{ p = (FileUploadProgress)attribute; } ServletActionContext.getResponse().setContentType("text/html;charset=UTF-8"); ServletActionContext.getResponse().setHeader("pragma", "no-cache"); ServletActionContext.getResponse().setHeader("cache-control", "no-cache"); ServletActionContext.getResponse().setHeader("expires", "0"); //以下方法为输出json(封装,可根据实际情况修改输出的方式) AjaxUtil.ajaxWriteObject(p); } /** * 清除session */ public void clearProgressSession(){ ServletActionContext.getRequest().getSession().setAttribute("fileUploadProgress", null); } }
6.UploadFile类(文件上传)
package com.hikvision.modules.guide.action; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.ProgressListener; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.log4j.Logger; import com.hikvision.fileUploadProcess.impl.FileUploadListener; import com.hikvision.modules.guide.util.GetSharePathXml; /** * upload file * * @author scott.Cgi */ public class UploadFile { private static final Logger LOG = Logger.getLogger(UploadFile.class); /** * 上传文件 * * @param request * http request * @param response * htp response * @throws IOException * IOException */ @SuppressWarnings("unchecked") public static void upload(HttpServletRequest request, HttpServletResponse response) throws IOException { if (request.getContentType() == null) { throw new IOException( "the request doesn't contain a multipart/form-data stream"); } // 上传临时路径 String path = GetSharePathXml.getShareFolderPath(); // 设置上传工厂 DiskFileItemFactory factory = new DiskFileItemFactory(); factory.setRepository(new File(path)); // 阀值,超过这个值才会写到临时目录 factory.setSizeThreshold(1024 * 1024 * 10); ServletFileUpload upload = new ServletFileUpload(factory); // 最大上传限制 upload.setSizeMax(1024 * 1024 * 1000); // 设置监听器监听上传进度 upload.setProgressListener(new FileUploadListener(request)); try { List<FileItem> items = upload.parseRequest(request); //获取文件类型 String variety = request.getParameter("variety"); for (FileItem item : items) { // 非表单域 if (!item.isFormField()) { FileOutputStream fos = new FileOutputStream(path + "/" + variety + "/" + item.getName()); // 文件全在内存中 if (item.isInMemory()) { fos.write(item.get()); } else { InputStream is = item.getInputStream(); byte[] buffer = new byte[1024]; int len; while ((len = is.read(buffer)) > 0) { fos.write(buffer, 0, len); } is.close(); } fos.close(); LOG.info("完成上传文件!"); item.delete(); LOG.info("删除临时文件!"); LOG.info("更新progress对象状态为完成状态!"); } } } catch (Exception e) { LOG.error("上传文件出现异常, 错误原因 : " + e.getMessage()); request.getSession().removeAttribute("percent"); } } }
前台部分:点击上传,然后循环调用
//进度条显示 var everylisten = function() { //显示进度条 $("#prosbar").parent("div").css({"display":"block"}); $.ajax({ url : 'http://127.0.0.1/guideUpload!progress.action', method : 'GET', timeout : 120000, contentType : "application/json; charset=utf-8", dataType : "json", success : function(result) { if(null != result) { if(result.complete) { //将进度条长度设为0并隐藏 $("#prosbar").css({"width":"0%"}); clearTimeout(everylisten); //清除session clearProgressSession(); }else{ var width = result.currentLength * 100 / result.length + "%"; $("#prosbar").css({"width": width}); setTimeout(everylisten, 500); } }else{ alert(data.msg); } } }); };
清除进度条session
//清除session function clearProgressSession(){ $.ajax({ url : hik.guide.getContextPath() + '/guideUpload!clearProgressSession.action', method : 'GET', timeout : 120000, contentType : "application/json; charset=utf-8", dataType : "json", success : function(result) { } }); }
点击文件上传时的js代码(PS:文件上传时相关参数写在路径里,不然后台接收不到(如果不写在路径里,测试时在浏览器调试发现参数确实传了,但在request里这个参数的值为空),因为请求地址是要经过tomcat的,为防止中文乱码的情况,在tomcat路径conf文件夹下找到server.xml文件,找到以下项,添加红色部分的代码 <Connector
URIEncoding="UTF-8" connectionTimeout="20000" port="80" protocol="HTTP/1.1" redirectPort="8443"/>
,保存后重启tomcat)
$("#GuideForm").submit(function(){ everylisten(); return false; $.ajaxFileUpload ( { url:'http://127.0.0.1/guideUpload!uploadfile.action?' + $("#GuideForm").serialize(), secureuri:false, fileElementId:'upload', dataType: 'json', data:{}, success: function (data, status) { $(dialogEl).dialog('close'); //上传成功后,提示用户上传成功 if(data.success){ alert("success"); }else{ alert("false"); } }, error: function (data, status, e) { alert("出错了"); } } ) });
页面代码(红色部分为必须写的地方):
<span style="color:#333333;"><form id="GuideForm" name="guideForm" action="" onsubmit="return false;" class="form-horizontal five-columns character5 padding-top8px" </span><span style="color:#cc0000;">enctype="multipart/form-data"</span><span style="color:#333333;"> method="post"> <div> <input type="hidden" name="ids" > <div class="control-group five-columns"> <label class="control-label">文件名称:</label> <div class="controls"> <input class="span4" type="text" name="name" maxlength="20" readonly="readonly"/> </div> </div> <div class="control-group five-columns"> <label class="control-label">文件类型:</label> <div class="controls"> <select name="variety"> <option value="tools">开发工具</option> <option value="ecliplse_plugin">Eclipse常用插件</option> <option value="svn">代码版本控制SVN</option> <option value="database">数据库</option> <option value="other">其他</option> </select> </div> </div> <div class="control-group five-columns" style="display:none;"> <label class="control-label">文件路径:</label> <div class="controls"> <a name="path" style="color:blue"></a> </div> <div class="controls"> <input type="button" class="btn btn-primary uploadReset" value="重新上传" name="uploadReset"> </div> </div> <div class="control-group five-columns"> <div class="controls"> <label class="control-label">上传文件 :</label> <input class="span4" type="file" name="upload" maxlength="20" id="upload" /> </div> </div> <div class="control-group five-columns"> <div class="controls"> <label class="control-label">文件备注:</label> <textarea rows="" cols="" name="comments" maxLength="50"></textarea> </div> </div> <div class="progress progress-striped active" > <div class="bar" style="width: 0%;" id="prosbar"></div> </div> </div> <!-- 上传进度条 --> </form></span>