文件上传和下载(可批量上传)——Spring(二)
针对SpringMVC的文件上传和下载。下载用之前“文件上传和下载——基础(一)”的依然可以,但是上传功能要修改,这是因为springMVC 都为我们封装好成自己的文件对象了,转换的过程就在我们所配置的CommonsMultipartResolver里面
原因分析
首先我们来看下Spring mvc 中文件上传的配置
1 <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> 2 <!-- one of the properties available; the maximum file size in bytes --> 3 <!-- 5M --> 4 <property name="defaultEncoding" value="utf-8"/> 5 <property name="maxUploadSize" value="25474565"/> 6 </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()方法封装成自己的文件对象 .
1 List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
大家应该发现了上面的这句代码,已经使用过fileUpload解析过request了,你在Controller里面接收到的request都已经是解析过的,你再次使用upload进行解析获取到的肯定是空,这个就是问题的所在(大家可以在servlet里面实验,看看第二次解析后能不能获取到数据,当然是不能的)
解决方案
1)删除Spring MVC文件上传配置
1 <!-- 2 <bean id="multipartResolver" 3 class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> 4 <property name="defaultEncoding" value="UTF-8" /> 5 6 <property name="maxUploadSize" value="2000000000" /> 7 </bean> 8 -->
在控制器里面自己完成request的解析(当然上面spring MVC提供的两种方法是不能用的,所有上传的地方都需要自己做处理)
1 public void upload3(HttpServletRequest request) { 2 DiskFileItemFactory factory = new DiskFileItemFactory(); 3 ServletFileUpload upload = new ServletFileUpload(factory); 4 try { 5 List<FileItem> list = upload.parseRequest(request); 6 for(FileItem item : list){ 7 if(item.isFormField()){ 8 9 }else{ 10 //item.write(new File("")); 11 } 12 } 13 } catch (FileUploadException e) { 14 e.printStackTrace(); 15 } 16 17 }
2)如果是需要使用的ProgressListener监听器我们可以重写 CommonsMultipartResolver的parseRequest方法
1 package com.lwp.spring.ext; 2 3 import java.util.List; 4 import javax.servlet.http.HttpServletRequest; 5 import org.apache.commons.fileupload.FileItem; 6 import org.apache.commons.fileupload.FileUpload; 7 import org.apache.commons.fileupload.FileUploadBase; 8 import org.apache.commons.fileupload.FileUploadException; 9 import org.apache.commons.fileupload.servlet.ServletFileUpload; 10 import org.springframework.web.multipart.MaxUploadSizeExceededException; 11 import org.springframework.web.multipart.MultipartException; 12 import org.springframework.web.multipart.commons.CommonsMultipartResolver; 13 import com.lwp.listener.FileUploadListener; 14 public class CommonsMultipartResolverExt extends CommonsMultipartResolver { 15 @Override 16 protected MultipartParsingResult parseRequest(HttpServletRequest request) 17 throws MultipartException { 18 FileUploadListener listener = new FileUploadListener(); 19 String encoding = determineEncoding(request); 20 FileUpload fileUpload = prepareFileUpload(encoding); 21 fileUpload.setProgressListener(listener); 22 try { 23 List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request); 24 return parseFileItems(fileItems, encoding); 25 } 26 catch (FileUploadBase.SizeLimitExceededException ex) { 27 throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex); 28 } 29 catch (FileUploadException ex) { 30 throw new MultipartException("Could not parse multipart servlet request", ex); 31 } 32 } 33 }
监听器方法
1 import org.apache.commons.fileupload.ProgressListener; 2 3 public class FileUploadListener implements ProgressListener { 4 5 @Override 6 public void update(long arg0, long arg1, int arg2) { 7 //arg0 已经上传多少字节 8 //arg1 一共多少字节 9 //arg2 正在上传第几个文件 10 System.out.println(arg0 +"\t" + arg1 +"\t" + arg2); 11 } 12 13 }
配置文件改为我们自己的(这种方式的缺陷是,所有文件上传都需要使用到Listener)
1 <bean id="multipartResolver" 2 class="com.lwp.spring.ext.CommonsMultipartResolverExt"> 3 <property name="defaultEncoding" value="UTF-8" /> 4 <property name="maxUploadSize" value="2000000000" /> 5 </bean>
注: 综上所述,如果只是普通的文件上传spring MVC 完全可以完成,如果需要使用进度条的listener前段可以使用假的进度条或者是上面的两种方式.
以上出自:http://blog.csdn.net/lwphk/article/details/43015829#
看完前辈写过的东西后,我也自己看了一下源代码,有所感想于是进行尝试,结果可行
1 package com.tommy.business; 2 3 import org.apache.commons.fileupload.FileItem; 4 import org.apache.commons.fileupload.FileUploadException; 5 import org.apache.commons.fileupload.disk.DiskFileItemFactory; 6 import org.apache.commons.fileupload.servlet.ServletFileUpload; 7 import org.springframework.stereotype.Controller; 8 import org.springframework.web.bind.annotation.RequestMapping; 9 import org.springframework.web.multipart.MultipartFile; 10 import org.springframework.web.multipart.MultipartHttpServletRequest; 11 import org.springframework.web.multipart.commons.CommonsMultipartFile; 12 import org.springframework.web.multipart.commons.CommonsMultipartResolver; 13 14 import javax.servlet.ServletException; 15 import javax.servlet.http.HttpServletRequest; 16 import javax.servlet.http.HttpServletResponse; 17 import java.io.*; 18 import java.net.URLEncoder; 19 import java.util.HashMap; 20 import java.util.List; 21 import java.util.Map; 22 23 /** 24 * Created by OnlyOne on 2016/3/4. 25 */ 26 @Controller 27 @RequestMapping("/background/uploadAndDownload/") 28 public class UploadAndDownloadController { 29 /** 30 * 31 * @param req 32 * @param resp 33 * @throws ServletException 34 * @throws IOException 35 * @throws FileUploadException 36 */ 37 @RequestMapping("uploadFile") 38 public String uploadFile(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, FileUploadException { 39 /*//防止中文乱码,与页面字符集一致 40 req.setCharacterEncoding("UTF-8");*/ 41 //得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全 42 String savePath = req.getServletContext().getRealPath("/WEB-INF/upload"); 43 // this.getServletContext().getRealPath("/WEB-INF/upload"); 44 //创建保存目录的文件 45 File saveFile = new File(savePath); 46 //判断保存目录文件是否存在,不存在则创建一个文件夹 47 if(!saveFile.exists()){ 48 System.out.println("文件目录创建中。。。"); 49 saveFile.mkdir(); 50 } 51 //消息提示 52 String message = ""; 53 //将req转换成Spring的request 54 MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) req; 55 //获取上传文件 56 List<MultipartFile> list = multipartHttpServletRequest.getFiles("file"); 57 //获取普通输入项的数据 58 String map = multipartHttpServletRequest.getParameter("username"); 59 System.out.println(map); 60 for(MultipartFile multipartFile: list){ 61 FileItem item = ((CommonsMultipartFile)multipartFile).getFileItem(); 62 //如果FileItem中封装的是普通输入项的数据 63 if(item.isFormField()){ 64 String name = item.getFieldName(); 65 //解决普通输入项的数据的中文乱码问题 66 String value = item.getString("UTF-8"); 67 // value = new String(value.getBytes("iso8859-1"),"UTF-8"); 68 System.out.println(name + "=" + value); 69 }else{//如果fileitem中封装的是上传文件 70 //得到上传的文件名称 71 String fileName = item.getName(); 72 System.out.println("文件名是:"+ fileName); 73 if(fileName == null || fileName.trim().equals("")){ 74 continue; 75 } 76 /*注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的, 77 如: c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt*/ 78 //处理获取到的上传文件的文件名的路径部分,只保留文件名部分.如果传上来的文件名没有带路径,则lastIndexOf返回-1 79 fileName = fileName.substring(fileName.lastIndexOf("\\")+1); 80 //获取item中的上传输入流 81 BufferedInputStream bis = new BufferedInputStream(item.getInputStream()); 82 //创建一个文件输出流 83 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(savePath + "\\" + fileName)); 84 //创建一个缓冲区 85 byte[] buffer = new byte[1024*8]; 86 //循环将缓冲输入流读入到缓冲区当中 87 while(true){ 88 //循环将缓冲输入流读入到缓冲区当中 89 int length = bis.read(buffer); 90 //判断是否读取到文件末尾 91 if(length == -1){ 92 break; 93 } 94 //使用BufferedOutputStream缓冲输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中 95 bos.write(buffer,0,length); 96 } 97 //关闭输入流 98 bis.close(); 99 //关闭输出流 100 bos.close(); 101 message = "文件上传成功!"; 102 } 103 } 104 return "redirect:listFile.html"; 105 } 106 107 /** 108 * @ClassName: ListFileServlet 109 * @Description: 列出Web系统中所有下载文件 110 * @param req 111 * @param resp 112 */ 113 @RequestMapping("listFile") 114 public String listFile(HttpServletRequest req, HttpServletResponse resp) { 115 String flag = req.getParameter("flag"); 116 //获取上传文件的目录 117 String uploadFilePath = req.getServletContext().getRealPath("/WEB-INF/upload"); 118 //存储要下载的文件名 119 Map<String, String> fileNameMap = new HashMap<String, String>(); 120 //地鬼遍历filePath目录下的所有文件和目录,将文件的文件名存储到map集合中 121 getListfile(new File(uploadFilePath), fileNameMap); 122 if(flag != null){ 123 req.setAttribute("flag",flag); 124 } 125 req.setAttribute("fileNameMap", fileNameMap); 126 return "/background/uploadAndDownload/resourcesList"; 127 } 128 129 /** 130 * 131 * @param file 即代表一个文件,也代表一个文件目录 132 * @param map 存储文件名的Map集合 133 * @Method: listfile 134 * @Description: 递归遍历指定目录下的所有文件 135 */ 136 private void getListfile(File file, Map<String, String> map) { 137 //如果file代表的不是一个文件,而是一个目录 138 if (!file.isFile()) { 139 //列出该目录下的所有文件和目录 140 File files[] = file.listFiles(); 141 //遍历files[]数组 142 for (File f : files) { 143 //递归 144 getListfile(f, map); 145 } 146 } else { 147 /** 148 * 处理文件名,上传后的文件是以uuid_文件名的形式去重新命名的,去除文件名的uuid_部分 149 file.getName().indexOf("_")检索字符串中第一次出现"_"字符的位置,如果文件名类似于:9349249849-88343-8344_阿_凡_达.avi 150 那么file.getName().substring(file.getName().indexOf("_")+1)处理之后就可以得到阿_凡_达.avi部分 151 */ 152 //String realName = file.getName().substring(file.getName().indexOf("_") + 1); 153 //file.getName()得到的是文件的原始名称,这个名称是唯一的,因此可以作为key,realName是处理过后的名称,有可能会重复 154 map.put(file.getName(), file.getName()); 155 } 156 } 157 158 /** 159 * 下载文件 160 * @param req 161 * @param resp 162 * @throws ServletException 163 * @throws IOException 164 */ 165 @RequestMapping("downLoadFile") 166 public void downLoadFile(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 167 //得到要下载的文件名 168 req.setCharacterEncoding("UTF-8"); 169 String fileName = req.getParameter("fileName"); 170 // fileName = new String(fileName.getBytes("iso8859-1"),"UTF-8"); 171 //上传的文件都是保存在/WEN-INF/upload目录党徽宗 172 String fileSavePath = req.getServletContext().getRealPath("/WEB-INF/upload"); 173 //得到要下载的文件 174 File file = new File(fileSavePath + "//" + fileName); 175 //如果文件不存在 176 if(!file.exists()){ 177 req.setAttribute("message", "资源已被删除!"); 178 req.getRequestDispatcher("/message.jsp").forward(req, resp); 179 } 180 //处理文件名 181 String realName = fileName.substring(fileName.indexOf("_")+1); 182 //设置响应头,控制浏览器下载该文件 183 resp.setHeader("content-disposition", "attachment;filename="+ URLEncoder.encode(realName, "UTF-8")); 184 //读取要下载的文件,保存到文件输入流 185 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileSavePath + "\\" + fileName)); 186 //创建输出流 187 BufferedOutputStream bos = new BufferedOutputStream(resp.getOutputStream()); 188 //创建一个缓冲区 189 byte[] buffer = new byte[1024*8]; 190 //循环将缓冲输入流读入到缓冲区当中 191 while(true){ 192 //循环将缓冲输入流读入到缓冲区当中 193 int length = bis.read(buffer); 194 //判断是否读取到文件末尾 195 if(length == -1){ 196 break; 197 } 198 //使用BufferedOutputStream缓冲输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中 199 bos.write(buffer,0,length); 200 } 201 //关闭文件输入流 202 bis.close(); 203 //刷新此输入流并强制写出所有缓冲的输出字节数 204 bos.flush(); 205 //关闭文件输出流 206 bos.close(); 207 } 208 209 }
其实只需要将SpringMVC封装的文件对象转换获得原始的数据对象就好了
采用SpringMVC封装的文件对象进行解析
到了这里,发现了将MultipartFile再转换成FileItem不理想,因为MultipartFile没有普通输入项的数据(如:"上传用户:<input type="text"name="username"/><br/>")
还有对文件名的处理也是多余的。
如果要获取普通输入项的数据,也可以。如下
从这里可以看出,Spring对浏览器提交的文件名已经做了处理,不再需要自己处理上传的文件名。
修改之后的完善版本
1 package com.tommy.business; 2 3 import org.apache.commons.fileupload.FileItem; 4 import org.apache.commons.fileupload.FileUploadException; 5 import org.apache.commons.fileupload.disk.DiskFileItemFactory; 6 import org.apache.commons.fileupload.servlet.ServletFileUpload; 7 import org.springframework.stereotype.Controller; 8 import org.springframework.web.bind.annotation.RequestMapping; 9 import org.springframework.web.multipart.MultipartFile; 10 import org.springframework.web.multipart.MultipartHttpServletRequest; 11 import org.springframework.web.multipart.commons.CommonsMultipartFile; 12 import org.springframework.web.multipart.commons.CommonsMultipartResolver; 13 14 import javax.servlet.ServletException; 15 import javax.servlet.http.HttpServletRequest; 16 import javax.servlet.http.HttpServletResponse; 17 import java.io.*; 18 import java.net.URLEncoder; 19 import java.util.HashMap; 20 import java.util.List; 21 import java.util.Map; 22 23 /** 24 * Created by OnlyOne on 2016/3/4. 25 */ 26 @Controller 27 @RequestMapping("/background/uploadAndDownload/") 28 public class UploadAndDownloadController { 29 /** 30 * 31 * @param req 32 * @param resp 33 * @throws ServletException 34 * @throws IOException 35 * @throws FileUploadException 36 */ 37 @RequestMapping("uploadFile") 38 public String uploadFile(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, FileUploadException { 39 /*//防止中文乱码,与页面字符集一致 40 req.setCharacterEncoding("UTF-8");*/ 41 //得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全 42 String savePath = req.getServletContext().getRealPath("/WEB-INF/upload"); 43 // this.getServletContext().getRealPath("/WEB-INF/upload"); 44 //创建保存目录的文件 45 File saveFile = new File(savePath); 46 //判断保存目录文件是否存在,不存在则创建一个文件夹 47 if(!saveFile.exists()){ 48 System.out.println("文件目录创建中。。。"); 49 saveFile.mkdir(); 50 } 51 //消息提示 52 //将req转换成Spring的request 53 MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) req; 54 //获取上传文件 55 List<MultipartFile> list = multipartHttpServletRequest.getFiles("file"); 56 //获取普通输入项的数据 57 String map = multipartHttpServletRequest.getParameter("username"); 58 System.out.println(map); 59 for(MultipartFile multipartFile: list){ 60 if(!multipartFile.isEmpty()){ 61 //得到上传的文件名称 62 String fileName = multipartFile.getOriginalFilename(); 63 System.out.println("文件名是:"+ fileName); 64 if(fileName == null || fileName.trim().equals("")){ 65 continue; 66 } 67 //获取item中的上传输入流 68 BufferedInputStream bis = new BufferedInputStream(multipartFile.getInputStream()); 69 //创建一个文件输出流 70 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(savePath + "\\" + fileName)); 71 //创建一个缓冲区 72 byte[] buffer = new byte[1024*8]; 73 //循环将缓冲输入流读入到缓冲区当中 74 while(true){ 75 //循环将缓冲输入流读入到缓冲区当中 76 int length = bis.read(buffer); 77 //判断是否读取到文件末尾 78 if(length == -1){ 79 break; 80 } 81 //使用BufferedOutputStream缓冲输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中 82 bos.write(buffer,0,length); 83 } 84 //关闭输入流 85 bis.close(); 86 //关闭输出流 87 bos.close(); 88 } 89 } 90 return "redirect:listFile.html"; 91 } 92 93 /** 94 * @ClassName: ListFileServlet 95 * @Description: 列出Web系统中所有下载文件 96 * @param req 97 * @param resp 98 */ 99 @RequestMapping("listFile") 100 public String listFile(HttpServletRequest req, HttpServletResponse resp) { 101 String flag = req.getParameter("flag"); 102 //获取上传文件的目录 103 String uploadFilePath = req.getServletContext().getRealPath("/WEB-INF/upload"); 104 //存储要下载的文件名 105 Map<String, String> fileNameMap = new HashMap<String, String>(); 106 //地鬼遍历filePath目录下的所有文件和目录,将文件的文件名存储到map集合中 107 getListfile(new File(uploadFilePath), fileNameMap); 108 if(flag != null){ 109 req.setAttribute("flag",flag); 110 } 111 req.setAttribute("fileNameMap", fileNameMap); 112 return "/background/uploadAndDownload/resourcesList"; 113 } 114 115 /** 116 * 117 * @param file 即代表一个文件,也代表一个文件目录 118 * @param map 存储文件名的Map集合 119 * @Method: listfile 120 * @Description: 递归遍历指定目录下的所有文件 121 */ 122 private void getListfile(File file, Map<String, String> map) { 123 //如果file代表的不是一个文件,而是一个目录 124 if (!file.isFile()) { 125 //列出该目录下的所有文件和目录 126 File files[] = file.listFiles(); 127 //遍历files[]数组 128 for (File f : files) { 129 //递归 130 getListfile(f, map); 131 } 132 } else { 133 /** 134 * 处理文件名,上传后的文件是以uuid_文件名的形式去重新命名的,去除文件名的uuid_部分 135 file.getName().indexOf("_")检索字符串中第一次出现"_"字符的位置,如果文件名类似于:9349249849-88343-8344_阿_凡_达.avi 136 那么file.getName().substring(file.getName().indexOf("_")+1)处理之后就可以得到阿_凡_达.avi部分 137 */ 138 //String realName = file.getName().substring(file.getName().indexOf("_") + 1); 139 //file.getName()得到的是文件的原始名称,这个名称是唯一的,因此可以作为key,realName是处理过后的名称,有可能会重复 140 map.put(file.getName(), file.getName()); 141 } 142 } 143 144 /** 145 * 下载文件 146 * @param req 147 * @param resp 148 * @throws ServletException 149 * @throws IOException 150 */ 151 @RequestMapping("downLoadFile") 152 public void downLoadFile(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 153 //得到要下载的文件名 154 req.setCharacterEncoding("UTF-8"); 155 String fileName = req.getParameter("fileName"); 156 // fileName = new String(fileName.getBytes("iso8859-1"),"UTF-8"); 157 //上传的文件都是保存在/WEN-INF/upload目录党徽宗 158 String fileSavePath = req.getServletContext().getRealPath("/WEB-INF/upload"); 159 //得到要下载的文件 160 File file = new File(fileSavePath + "//" + fileName); 161 //如果文件不存在 162 if(!file.exists()){ 163 req.setAttribute("message", "资源已被删除!"); 164 req.getRequestDispatcher("/message.jsp").forward(req, resp); 165 } 166 //处理文件名 167 String realName = fileName.substring(fileName.indexOf("_")+1); 168 //设置响应头,控制浏览器下载该文件 169 resp.setHeader("content-disposition", "attachment;filename="+ URLEncoder.encode(realName, "UTF-8")); 170 //读取要下载的文件,保存到文件输入流 171 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileSavePath + "\\" + fileName)); 172 //创建输出流 173 BufferedOutputStream bos = new BufferedOutputStream(resp.getOutputStream()); 174 //创建一个缓冲区 175 byte[] buffer = new byte[1024*8]; 176 //循环将缓冲输入流读入到缓冲区当中 177 while(true){ 178 //循环将缓冲输入流读入到缓冲区当中 179 int length = bis.read(buffer); 180 //判断是否读取到文件末尾 181 if(length == -1){ 182 break; 183 } 184 //使用BufferedOutputStream缓冲输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中 185 bos.write(buffer,0,length); 186 } 187 //关闭文件输入流 188 bis.close(); 189 //刷新此输入流并强制写出所有缓冲的输出字节数 190 bos.flush(); 191 //关闭文件输出流 192 bos.close(); 193 } 194 195 }