相信大家还没有做过这个功能的时候,在其他的博客上面也找到了很多类似的资料,但是对于一些新手来说,很难理解其中的意思。下面这段话是我从其他博客摘取下来的,说的是在Struts2中实现这个功能的原理:
- 利用Ajax在客户端一直查询服务器端的上传进度,取得进度的状态文本信息(xml,json格式的文本等),然后利用JS解析,显示在前台。
- 在Struts2.0中,框架事先已经定义一种监听器:ProgressListener(进度监听器),里面有一个update(longreadedBytes,longtotalBytes,intcurrentItem)方法,其中,readedBytes是已经上传到服务器的位数,而totalBytes是上传文件总位数.当文件已二进制的方式上传时,每上传一部分数据,就会调用这个方法一次。故要实现监听进度,必须实现这个接口,并实现update方法,在update方法中保存这个进度到session。当客服端需要进度的信息时,只需要访问某个action,在这个action中读取session中保存的进度状态就可以了。
相信大家看了这段话之后很难理解其中的意思,我当时也是这样的。但是这个文件上传的功能必须实现,那么这个显示进度的功能也必须实现,所以无论如何我们也得实现这个功能,这是一件很痛苦的事情。
但是,经过小编的理解之后,觉得有更好的方式解读这两句话。首先看一个图吧:
看了这个图之后是不是顿时思路清晰多了?当然这里只是知道了大概的思路,但是具体怎么做还没有体现出来。
现在我们开始动手实现具体的细节吧。
首先我这里是使用ssh框架进行开发的,还不会使用ssh框架的同学请自学,主要是Struts2框架。
然后是,前端我用了Bootstrap框架,进度条的显示的样式使用了Bootstrap的默认样式。
然后是准备自定义的MultiPartRequest类:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package util.upload; import com.opensymphony.xwork2.LocaleProvider; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.util.LocalizedTextUtil; import com.opensymphony.xwork2.util.logging.Logger; import com.opensymphony.xwork2.util.logging.LoggerFactory; 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.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.RequestContext; import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException; 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.dispatcher.multipart.JakartaMultiPartRequest; import org.apache.struts2.dispatcher.multipart.MultiPartRequest; public class MyMultiPartRequest implements MultiPartRequest { static final Logger LOG = LoggerFactory.getLogger(JakartaMultiPartRequest.class); protected Map<String,List<FileItem>> files = new HashMap(); protected Map<String,List<String>> params = new HashMap(); protected List<String> errors = new ArrayList(); protected long maxSize; private Locale defaultLocale; public MyMultiPartRequest() { this.defaultLocale = Locale.ENGLISH; } @Inject("struts.multipart.maxSize") public void setMaxSize(String maxSize) { this.maxSize = Long.parseLong(maxSize); } @Inject public void setLocaleProvider(LocaleProvider provider) { this.defaultLocale = provider.getLocale(); } public void parse(HttpServletRequest request,String saveDir) throws IOException { String errorMessage; try { this.setLocale(request); this.processUpload(request,saveDir); } catch (SizeLimitExceededException var5) { if(LOG.isWarnEnabled()) { LOG.warn("Request exceeded size limit!",var5,new String[0]); } errorMessage = this.buildErrorMessage(var5,new Object[]{Long.valueOf(var5.getPermittedSize()),Long.valueOf(var5.getActualSize())}); if(!this.errors.contains(errorMessage)) { this.errors.add(errorMessage); } } catch (Exception var6) { if(LOG.isWarnEnabled()) { LOG.warn("Unable to parse request",var6,new String[0]); } errorMessage = this.buildErrorMessage(var6,new Object[0]); if(!this.errors.contains(errorMessage)) { this.errors.add(errorMessage); } } } protected void setLocale(HttpServletRequest request) { if(this.defaultLocale == null) { this.defaultLocale = request.getLocale(); } } protected String buildErrorMessage(Throwable e,Object[] args) { String errorKey = "struts.messages.upload.error." + e.getClass().getSimpleName(); if(LOG.isDebugEnabled()) { LOG.debug("Preparing error message for key: [#0]",new String[]{errorKey}); } return LocalizedTextUtil.findText(this.getClass(),errorKey,this.defaultLocale,e.getMessage(),args); } protected void processUpload(HttpServletRequest request,String saveDir) throws FileUploadException,UnsupportedEncodingException { Iterator i$ = this.parseRequest(request,saveDir).iterator(); while(i$.hasNext()) { FileItem item = (FileItem)i$.next(); if(LOG.isDebugEnabled()) { LOG.debug("Found item " + item.getFieldName(),new String[0]); } if(item.isFormField()) { this.processNormalFormField(item,request.getCharacterEncoding()); } else { this.processFileField(item); } } } protected void processFileField(FileItem item) { if(LOG.isDebugEnabled()) { LOG.debug("Item is a file upload",new String[0]); } if(item.getName() != null && item.getName().trim().length() >= 1) { Object values; if(this.files.get(item.getFieldName()) != null) { values = (List)this.files.get(item.getFieldName()); } else { values = new ArrayList(); } ((List)values).add(item); this.files.put(item.getFieldName(),(List<FileItem>) values); } else { LOG.debug("No file has been uploaded for the field: " + item.getFieldName(),new String[0]); } } protected void processNormalFormField(FileItem item,String charset) throws UnsupportedEncodingException { if(LOG.isDebugEnabled()) { LOG.debug("Item is a normal form field",new String[0]); } Object values; if(this.params.get(item.getFieldName()) != null) { values = (List)this.params.get(item.getFieldName()); } else { values = new ArrayList(); } if(charset != null) { ((List)values).add(item.getString(charset)); } else { ((List)values).add(item.getString()); } this.params.put(item.getFieldName(),(List<String>) values); item.delete(); } protected List<FileItem> parseRequest(HttpServletRequest servletRequest,String saveDir) throws FileUploadException { UploadStatus status = new UploadStatus(); // 上传状态 UploadListener listner = new UploadListener(status); // 监听器 servletRequest.getSession().setAttribute("uploadStatus",status); // 把状态放到session里去 DiskFileItemFactory fac = createDiskFileItemFactory(saveDir); ServletFileUpload upload = new ServletFileUpload(fac); upload.setSizeMax(maxSize); upload.setProgressListener(listner);// 添加监听器 return upload.parseRequest(createRequestContext(servletRequest)); } protected ServletFileUpload createServletFileUpload(DiskFileItemFactory fac) { ServletFileUpload upload = new ServletFileUpload(fac); upload.setSizeMax(this.maxSize); return upload; } protected DiskFileItemFactory createDiskFileItemFactory(String saveDir) { DiskFileItemFactory fac = new DiskFileItemFactory(); fac.setSizeThreshold(0); if(saveDir != null) { fac.setRepository(new File(saveDir)); } return fac; } public Enumeration<String> getFileParameterNames() { return Collections.enumeration(this.files.keySet()); } public String[] getContentType(String fieldName) { List items = (List)this.files.get(fieldName); if(items == null) { return null; } else { ArrayList contentTypes = new ArrayList(items.size()); Iterator i$ = items.iterator(); while(i$.hasNext()) { FileItem fileItem = (FileItem)i$.next(); contentTypes.add(fileItem.getContentType()); } return (String[])contentTypes.toArray(new String[contentTypes.size()]); } } public File[] getFile(String fieldName) { List items = (List)this.files.get(fieldName); if(items == null) { return null; } else { ArrayList fileList = new ArrayList(items.size()); File storeLocation; for(Iterator i$ = items.iterator(); i$.hasNext(); fileList.add(storeLocation)) { FileItem fileItem = (FileItem)i$.next(); storeLocation = ((DiskFileItem)fileItem).getStoreLocation(); if(fileItem.isInMemory() && storeLocation != null && !storeLocation.exists()) { try { storeLocation.createNewFile(); } catch (IOException var8) { if(LOG.isErrorEnabled()) { LOG.error("Cannot write uploaded empty file to disk: " + storeLocation.getAbsolutePath(),var8,new String[0]); } } } } return (File[])fileList.toArray(new File[fileList.size()]); } } public String[] getFileNames(String fieldName) { List items = (List)this.files.get(fieldName); if(items == null) { return null; } else { ArrayList fileNames = new ArrayList(items.size()); Iterator i$ = items.iterator(); while(i$.hasNext()) { FileItem fileItem = (FileItem)i$.next(); fileNames.add(this.getCanonicalName(fileItem.getName())); } return (String[])fileNames.toArray(new String[fileNames.size()]); } } public String[] getFilesystemName(String fieldName) { List items = (List)this.files.get(fieldName); if(items == null) { return null; } else { ArrayList fileNames = new ArrayList(items.size()); Iterator i$ = items.iterator(); while(i$.hasNext()) { FileItem fileItem = (FileItem)i$.next(); fileNames.add(((DiskFileItem)fileItem).getStoreLocation().getName()); } return (String[])fileNames.toArray(new String[fileNames.size()]); } } public String getParameter(String name) { List v = (List)this.params.get(name); return v != null && v.size() > 0?(String)v.get(0):null; } public Enumeration<String> getParameterNames() { return Collections.enumeration(this.params.keySet()); } public String[] getParameterValues(String name) { List v = (List)this.params.get(name); return v != null && v.size() > 0?(String[])v.toArray(new String[v.size()]):null; } public List<String> getErrors() { return this.errors; } 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; } protected 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 { ServletInputStream in = req.getInputStream(); if(in == null) { throw new IOException("Missing content in the request"); } else { return req.getInputStream(); } } }; } public void cleanUp() { Set names = this.files.keySet(); Iterator i$ = names.iterator(); while(i$.hasNext()) { String name = (String)i$.next(); List items = (List)this.files.get(name); Iterator i$1 = items.iterator(); while(i$1.hasNext()) { FileItem item = (FileItem)i$1.next(); if(LOG.isDebugEnabled()) { String msg = LocalizedTextUtil.findText(this.getClass(),"struts.messages.removing.file",Locale.ENGLISH,"no.message.found",new Object[]{name,item}); LOG.debug(msg,new String[0]); } if(!item.isInMemory()) { item.delete(); } } } } }实现自定义的ProgressListener
package util.upload; import org.apache.commons.fileupload.ProgressListener; public class UploadListener implements ProgressListener { private UploadStatus status; public UploadListener(UploadStatus status) { this.status = status; } public void update(long bytesRead,long contentLength,int items) { // 上传组件会调用该方法 status.setBytesRead(bytesRead); // 已读取的数据长度 status.setContentLength(contentLength); // 文件总长度 status.setItems(items); // 正在保存第几个文件 } }用来存储下载进度状态的Java bean
package util.upload; /** * Created by martsforever on 2016/2/24. */ public class UploadStatus { private long bytesRead;//已经上传的字节数,单位:字节 private long contentLength;//所有文件总长度,单位:字节 private int items;//正在上传的第几个文件 private long startTime = System.currentTimeMillis();//开始上传的时间,用于计算上传速度 public long getBytesRead() { return bytesRead; } public void setBytesRead(long bytesRead) { this.bytesRead = bytesRead; } public long getContentLength() { return contentLength; } public void setContentLength(long contentLength) { this.contentLength = contentLength; } public int getItems() { return items; } public void setItems(int items) { this.items = items; } public long getStartTime() { return startTime; } public void setStartTime(long startTime) { this.startTime = startTime; } }这桑三个文件准备好了之后不要忘了在struts的配置文件中添加一下代码:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <constant name="struts.multipart.parser" value="util.upload.MyMultiPartRequest" /> <constant name="struts.multipart.handler" value="util.upload.MyMultiPartRequest" /> <constant name="struts.multipart.maxSize" value="2000000000"/> </struts>
jsp中的代码:
选择文件的表单:
<div class="col-xs-12 col-sm-12 col-md-12 clean text-center" style="padding-top: 10vh"> <center> <form id="newResourceForm" action="uploadResourceFile" method="post"> <input type="text" class="form-control" id="name" placeholder="资源名称" style="width: 30vw; margin-bottom: 5vh"> <input type="text" class="form-control" id="introduce" placeholder="简介" style="width: 30vw; margin-bottom: 5vh"> <input id="resource" type="file" name="resource" style="display:none"> <div class="input-group" style="width: 30vw; margin-bottom: 5vh"> <input id="photoCover" type="text" class="form-control" placeholder="文件临时路径" aria-describedby="basic-addon2"> <span class="input-group-addon btn btn-default" id="basic-addon2" onclick="$('input[id=resource]').click();">选择文件</span> </div> </form> <button type="submit" class="btn btn-success" style="width: 8vw" id="newResourceConfirmBtn">提交</button> </center> </div>显示文件进度
<%--上传文件进度模态对话框--%> <div class="modal fade" id="uploadFileProgressModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title">正在上传文件</h4> </div> <div class="modal-body"> <div class="progress"> <div id="uploadProgress" class="progress-bar progress-bar-success progress-bar-striped active" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width: 0%"> <span class="sr-only">40% Complete (success)</span> </div> </div> </div> </div> </div> </div>消息提示:
<%--消息提示模态对话框--%> <div class="modal fade" id="msg" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title">消息提示</h4> </div> <div class="modal-body"> <span id="msgContent"></span> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal" id="msgBtn">确定</button> </div> </div> </div> </div>不要忘了导入刚才的js文件:
<script src="js/ajaxfileupload.js"></script>
两个ajax
<script type="text/javascript"> var options = { backdrop: false,keyboard: false }; var uploadInterval; $("#newResourceConfirmBtn").click(function () { // alert($("#name").val().trim()+$("#introduce").val().trim()); $.ajaxFileUpload({ url: 'uploadResourceFile?name='+$("#name").val().trim()+"&"+"introduce="+$("#introduce").val().trim(),type: 'post',secureuri: false,//一般设置为false fileElementId: 'resource',// 上传文件的id、name属性名 dataType: 'json',//返回值类型,一般设置为json、application/json data: {//传递参数到服务器 "name": $("#name").val().trim(),"introduce": $("#introduce").val().trim() },success: function (data,status) { if (data.saveStatus == "success") { $("#msgContent").html("添加成功"); $("#msg").modal(options); $("#msgBtn").unbind('click'); $("#msgBtn").click(function(){ window.location.reload(); }); } else("保存失败!"); },error: function (data,status,e) { alert(e); } }); $("#uploadConfirmBtn").attr({"disabled": "disabled"}); $("#uploadFileProgressModal").modal(options); uploadInterval = setInterval("showuploadProgress()",1000); }); // var precent = 0; function showuploadProgress() { $.ajax({ type: "POST",//提交方式 url: "getFileUploadProgress",//路径 dataType: "json",//返回的json格式的数据 success: function (result) {//返回数据根据结果进行相应的处理 $("#uploadProgress").width(result.percent + '%'); if(result.percent == 100){ clearInterval(uploadInterval); } },error: function (XMLHttpRequest,textStatus,errorThrown) { alert(errorThrown); } }); // precent+=10; // $("#uploadProgress").width(precent+'%'); // if(precent == 100){ // $("#uploadConfirmBtn").removeAttr("disabled"); // clearInterval(uploadInterval); // } } $('input[id=resource]').change(function () { $('#photoCover').val($(this).val()); }); </script>
@Action(value = "uploadResourceFile") public void uploadResourceFile()throws Exception { System.out.println("uploadResourceFile"); String name = request.getParameter("name"); String introduce = request.getParameter("introduce"); System.out.println("name="+name); System.out.println("introduce="+introduce); System.out.println("file is null"+(resource == null)); System.out.println("fileName"+resourceFileName); System.out.println("fileContentType"+resourceContentType); JSONObject jsonObject = new JSONObject(); response.setContentType("text/html;charset=utf-8"); response.setCharacterEncoding("utf-8"); if (resource != null) { Resource newNesource = new Resource(); newNesource.setName(name); newNesource.setIntroduce(introduce); newNesource.setTime(dateFormat.format(new Date())); newNesource.setDownloadTimes(0); newNesource.setSize(resource.length()); // resource.setDownloadUrl("resource/"+resourceFileName+resource.getTime()); resourceContentType = resourceFileName.substring(resourceFileName.lastIndexOf(".") + 1);//获得正真的文件类型 System.out.println("fileContentType:" + resourceContentType); resourceFileName = newNesource.getName() + "." + resourceContentType;//存储的文件名称为用户账号名 String realpath = ServletActionContext.getServletContext().getRealPath("/resource"); /*System.out.println("realpath:" + realpath);*/ File saveFile = new File(new File(realpath),resourceFileName); if (!saveFile.getParentFile().exists()) { System.out.println("目录不存在,重新创建目录!"); saveFile.getParentFile().mkdirs(); } FileUtils.copyFile(resource,saveFile); String savePath = saveFile.getAbsolutePath(); newNesource.setDownloadUrl("resource/" + resourceFileName); resourceService.add(newNesource); System.out.println(resource.toString()); jsonObject.put("saveStatus","success"); } else { System.out.println("file is null"); jsonObject.put("saveStatus","fail"); } response.getWriter().write(jsonObject.toString()); }
/** * 获取文件上传的进度 */ @Action(value = "getFileUploadProgress") public void getFileUploadProgress() throws Exception{ UploadStatus status = (UploadStatus) session.get("uploadStatus"); if (status == null) { System.out.println("uploadStatus is null"); return; } long startTime = status.getStartTime(); //上传开始时间 long currentTime = System.currentTimeMillis(); //现在时间 long time = (currentTime - startTime) / 1000 + 1; //已传输的时间 单位:s //传输速度单位:byte/s double velocity = ((double) status.getBytesRead()) / (double) time; //估计总时间 double totalTime = status.getContentLength(); //估计剩余时间 double timeLeft = totalTime - time; //已经完成的百分比 int percent = (int) (100 * (double) status.getBytesRead() / (double) status.getContentLength()); //已经完成数单位:m double length = ((double) status.getBytesRead()) / 1024 / 1024; //总长度 单位:m double totalLength = ((double) status.getContentLength()) / 1024 / 1024; System.out.println("bytesRead:"+status.getBytesRead()); System.out.println("ContentLength:"+status.getContentLength()); // System.out.println("percent:"+percent); // System.out.println("length:"+length); // System.out.println("totalLength:"+totalLength); // System.out.println("velocity:"+velocity); // System.out.println("time:"+time); // System.out.println("totalTime:"+totalTime); // System.out.println("timeLeft:"+timeLeft); // System.out.println("fileNumber:"+status.getItems()); JSONObject jsonObject = new JSONObject(); jsonObject.put("percent",String.valueOf(percent)); jsonObject.put("length",String.valueOf(length)); jsonObject.put("totalLength",String.valueOf(totalLength)); jsonObject.put("velocity",String.valueOf(velocity)); jsonObject.put("time",String.valueOf(time)); jsonObject.put("totalTime",String.valueOf(totalTime)); jsonObject.put("timeLeft",String.valueOf(timeLeft)); jsonObject.put("fileNumber",String.valueOf(status.getItems())); response.setContentType("text/html;charset=utf-8"); response.setCharacterEncoding("utf-8"); response.getWriter().write(jsonObject.toString()); }不要忘了该Action中添加三个成员变量:
private File resource; private String resourceFileName; private String resourceContentType;还要提供get和set方法,这个成员变量的名称必须和ajax中的fileElementId一致,而且html中input的id和name都是这个。(这里看不清楚的可以提问,我也觉得这里表达不清楚)。
这里面使用了JsonObject,其中可以通过maven添加依赖: