本文將使用 apache fileupload ,spring MVC jquery 實現一個帶進度條的多文件上傳, 由於fileupload 的局限,暫不能實現每個上傳文件都顯示進度條,只能實現一個總的進度條
優點:不用引入第三方的組件(如js框架,flash等)
缺點:如果同時上傳多個文件,由於apache fileupload API的限制,只能顯示一個總的進度條
1.引入環境所需要的jar包
引入上傳工具類jar包
<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency>
引入mvc處理json數據jar包(@requestbody&@responsebody)
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.5.1</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.5.1</version> </dependency>
2.jsp頁面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery.js"></script> <link rel="stylesheet" href="//code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css"> <script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script> <link rel="stylesheet" href="http://jqueryui.com/resources/demos/style.css"> <script type="text/javascript"> $(document).ready(function(){ $('#subbut').bind('click', function(){ $('#fForm').submit(); var eventFun = function(){ $.ajax({ type: 'GET', url: '${pageContext.request.contextPath}/test/process.action', data: {}, dataType: 'json', success : function(data){ $("#fff").html("上傳進度:"+data.show); $( "#proBar" ).progressbar({ value: data.rate }); if(data.rate == 100){ window.clearInterval(intId); alert("上傳完成"); } }});}; var intId = window.setInterval(eventFun,100); }); }); </script> <title>Insert title here</title> </head> <body> <form id='fForm' action="${pageContext.request.contextPath}/test/testbar.action" encType="multipart/form-data" method="post" target="uploadf"> <div> <label>上傳文件:</label> <div> <input type="file" name="file" style="width:550"> </div> <label id="fff"></label> <div> <div style="width:50%"> <div id = 'proBar'></div> </div> </div> </div> <div > <div > <button type="button" id="subbut" >submit</button> </div> </div> </form> <iframe name="uploadf" style="display:none"></iframe> </body> </html>
這里注意需要引入js和css,如果需要更換進度條的樣式,可以引入其他的css(如bootstarp等)
3.后台的實現
import java.io.File; import java.util.Iterator; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileItemFactory; 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 org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.ModelAndView; @Controller @RequestMapping("/test") public class FileUploadController { private final Logger log = Logger.getLogger(FileUploadController.class); /** * upload 上傳文件 */ @RequestMapping(value = "/testbar.action", method = RequestMethod.POST) public ModelAndView upload(HttpServletRequest request,HttpServletResponse response) throws Exception { final HttpSession hs = request.getSession(); ModelAndView mv = new ModelAndView(); boolean isMultipart = ServletFileUpload.isMultipartContent(request); if(!isMultipart){ return mv; } // Create a factory for disk-based file items FileItemFactory factory = new DiskFileItemFactory(); // Create a new file upload handler ServletFileUpload upload = new ServletFileUpload(factory); //解決上傳文件名的中文亂碼 upload.setHeaderEncoding("UTF-8"); upload.setProgressListener(new ProgressListener(){ public void update(long pBytesRead, long pContentLength, int pItems) { ProcessInfo pri = new ProcessInfo(); pri.itemNum = pItems; pri.readSize = pBytesRead; pri.totalSize = pContentLength; pri.show = pBytesRead+"/"+pContentLength+" byte"; pri.rate = Math.round(new Float(pBytesRead) / new Float(pContentLength)*100); hs.setAttribute("proInfo", pri); } }); // Parse the request List items = upload.parseRequest(request); // Process the uploaded items Iterator iter = items.iterator(); while (iter.hasNext()) { FileItem item = (FileItem) iter.next(); if (item.isFormField()) { String name = item.getFieldName(); String value = item.getString("utf-8"); System.out.println("this is common feild!"+name+"="+value); } else { System.out.println("this is file feild!"); String fieldName = item.getFieldName(); String fileName = item.getName().substring(item.getName().lastIndexOf("\\")+1);; String contentType = item.getContentType(); boolean isInMemory = item.isInMemory(); long sizeInBytes = item.getSize(); File uploadedFile = new File("c://"+fileName); item.write(uploadedFile); } } return mv; } /** * process 獲取進度 */ @RequestMapping(value = "/process.action", method = RequestMethod.GET) public @ResponseBody Object process(HttpServletRequest request,HttpServletResponse response) throws Exception { return ( ProcessInfo)request.getSession().getAttribute("proInfo"); } //精度條pojo class ProcessInfo{ public long totalSize = 1; public long readSize = 0; public String show = ""; public int itemNum = 0; public int rate = 0; } }
4.SpringMVC中servletFileUpload.parseRequest(request)解析為空獲取不到數據問題
問題產生的原因
先看一下springmvc中上傳的配置
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="UTF-8" /> <property name="maxUploadSize" value="2000000000" /> </bean>
再看看controller中使用
方式一
public void upload2(HttpServletRequest request) { // 轉型為MultipartHttpRequest try { MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; List<MultipartFile> fileList = multipartRequest.getFiles("file"); for (MultipartFile mf : fileList) { if(!mf.isEmpty()){ } } } catch (Exception e) { e.printStackTrace(); } }
方式二
public String upload(HttpServletRequest request, @RequestParam(value = "file") MultipartFile[] files) { try { for (MultipartFile mf : files) { if(!mf.isEmpty()){ } } } catch (Exception e) { e.printStackTrace(); } return "upload"; }
這里springMVC 都為我們封裝好成自己的文件對象了,轉換的過程就在我們所配置的CommonsMultipartResolver這個轉換器里面.他的轉換器里面就是調用common-fileupload的方式解析,然后再使用parseFileItems()方法封裝成自己的文件對象.大家應該發現了上面的這句代碼,已經使用過fileUpload解析過request了,你在Controller里面接收到的request都已經是解析過的,你再次使用upload進行解析獲取到的肯定是空,這個就是問題的所在
解決方式一(以上案例就是用此解決方法)
1.刪除mvc里面上傳的配置
<!-- <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> --> <!-- 指定所上傳文件的總大小不能超過200KB。注意maxUploadSize屬性的限制不是針對單個文件,而是所有文件的容量之和 --> <!-- <property name="maxUploadSize" value="200000"/> --> <!-- </bean> -->
2.在控制器里面自己完成request的解析(當然上面spring MVC提供的兩種方法是不能用的,所有上傳的地方都需要自己做處理)
public void upload3(HttpServletRequest request) { DiskFileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload upload = new ServletFileUpload(factory); try { List<FileItem> list = upload.parseRequest(request); for(FileItem item : list){ if(item.isFormField()){ }else{ //item.write(new File("")); } } } catch (FileUploadException e) { e.printStackTrace(); } }
解決方式二(重寫listener)
1.創建狀態pojo
public class Progress { public long totalSize = 1; public long readSize = 0; public String show = ""; public int itemNum = 0; public int rate = 0; public void setTotalSize(long totalSize) { this.totalSize = totalSize; } public void setReadSize(long readSize) { this.readSize = readSize; } public String getShow() { return readSize+"/"+totalSize+" byte"; } public int getItemNum() { return itemNum; } public void setItemNum(int itemNum) { this.itemNum = itemNum; } public int getRate() { return Math.round(new Float(readSize) / new Float(totalSize)*100); } }
2.寫自己的listener
import javax.servlet.http.HttpSession; import org.apache.commons.fileupload.ProgressListener; public class FileUploadListener implements ProgressListener{ private HttpSession session; public void setSession(HttpSession session){ this.session=session; Progress status = new Progress(); session.setAttribute("status", status); } /* * pBytesRead 到目前為止讀取文件的比特數 pContentLength 文件總大小 pItems 目前正在讀取第幾個文件 */ public void update(long pBytesRead, long pContentLength, int pItems) { Progress status = (Progress) session.getAttribute("status"); status.setReadSize(pBytesRead); status.setTotalSize(pContentLength); status.setItemNum(pItems); } }
3.寫自己的resolver
public class CommonsMultipartResolverExt extends CommonsMultipartResolver{ @Override protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException { FileUploadListener listener = new FileUploadListener(); listener.setSession(request.getSession()); String encoding = determineEncoding(request); FileUpload fileUpload = prepareFileUpload(encoding); fileUpload.setProgressListener(listener); try { List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request); return parseFileItems(fileItems, encoding); } catch (FileUploadBase.SizeLimitExceededException ex) { throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex); } catch (FileUploadException ex) { throw new MultipartException("Could not parse multipart servlet request", ex); } } }
4.配置上傳resolver
<bean id="multipartResolver" class="com.fyh.www.common.CommonsMultipartResolverExt"></bean>
5.測試
@Controller @RequestMapping("/test2") public class FileUploadController2 { private final Logger log = Logger.getLogger(FileUploadController2.class); /** * upload 上傳文件 */ @RequestMapping(value = "/testbar.action", method = RequestMethod.POST) public ModelAndView upload(HttpServletRequest request,HttpServletResponse response) throws Exception { ModelAndView mv = new ModelAndView(); MultipartHttpServletRequest multipartRequest=(MultipartHttpServletRequest) request; MultipartFile file = multipartRequest.getFile("file"); InputStream inputStream = file.getInputStream(); FileUtils.copyInputStreamToFile(inputStream, new File("e://"+file.getOriginalFilename())); return mv; } /** * process 獲取進度 */ @RequestMapping(value = "/process.action", method = RequestMethod.GET) public @ResponseBody Object process(HttpServletRequest request,HttpServletResponse response) throws Exception { return ( Progress)request.getSession().getAttribute("status"); } }
此方案在獲取監控進度的同時並不因影響mvc原有上傳方法的使用
解決方式三(需要用到進度條的上傳時,寫獨立的servlet,servlet+apache uploadfile 與springmvc互不干預)不建議用此方法