Java Web文件上传
参考资料:http://www.cnblogs.com/xdp-gacl/p/4200090.html
一、问题描述
Java Web文件上传需要借助一些第三方库,常用的是借助Apache的包,有两个:
commons-fileupload
commons-io
二、前端代码示例
<form method="post" id="uploadApkForm" action="uploadapk" enctype="multipart/form-data"> <p> 文件:<input name="apkFile" type="file" /> <!--有multiple属性时支持选中多个文件同时上传--> </p> <p> 版本:<input name="version" type="text" placeholder="请输入版本信息" /> </p> </form>
注:
enctype="multipart/form-data" 是必须的,表示这是个含文件的form表单;
若type="file" 的 input标签含有 multiple 属性,则能够在弹出框中同时选中多个文件上传
三、后端代码示例
try { // 判断enctype属性是否为multipart/form-data boolean isMultipart = ServletFileUpload.isMultipartContent(request); if (!isMultipart) {// 不是文件上传,用传统方法获取数据 // String userId = request.getParameter("userId");等 return; } // 为multipart/form-data int maxMemorySize = 1024 * 1000 * 50;// 50MB int maxRequestSize = 1024 * 1000 * 100;// 100MB String projAbsolutePath = request.getRealPath(""); String uploadRelativeDir = "upload/apk/"; String uploadTmpRelativeDir = uploadRelativeDir + "/tmp/"; File uploadDirObj = new File(projAbsolutePath + uploadRelativeDir); if (!uploadDirObj.exists()) { uploadDirObj.mkdirs(); } File uploadTmpDirObj = new File(projAbsolutePath + uploadTmpRelativeDir); if (!uploadTmpDirObj.exists()) { uploadTmpDirObj.mkdirs(); } // Create a factory for disk-based file items DiskFileItemFactory factory = new DiskFileItemFactory(); // 当上传文件太大时,因为虚拟机能使用的内存是有限的,所以此时要通过临时文件来实现上传文件的保存 // 此方法是设置是否使用临时文件的临界值(单位:字节) factory.setSizeThreshold(maxMemorySize); // 与上一个结合使用,设置临时文件的路径(绝对路径) factory.setRepository(uploadTmpDirObj); // Create a new file upload handler ServletFileUpload upload = new ServletFileUpload(factory); // 解决上传文件名的中文乱码,tomcat8不需要了 // upload.setHeaderEncoding("UTF-8"); // 设置上传内容的大小限制(单位:字节) upload.setSizeMax(maxRequestSize); // Parse the request List<FileItem> items = upload.parseRequest(request); Iterator<?> iter = items.iterator(); String fieldName = null; FileItem item = null; String fileName = null; String version = null; while (iter.hasNext()) { item = (FileItem) iter.next(); fieldName = item.getFieldName(); if (item.isFormField()) {// 普通表单字段,版本信息 version = item.getString(); System.out.println(fieldName + " " + version); } else {// 文件字段 fileName = item.getName(); if (!fileName.endsWith("apk")) {// 不是apk文件 System.out.println("不是apk文件"); return; } fileName = fileName.substring(fileName.lastIndexOf("\\") + 1); String contentType = item.getContentType(); boolean isInMemory = item.isInMemory(); long sizeInBytes = item.getSize(); System.out.println(fileName + " " + contentType + " " + isInMemory + " " + sizeInBytes); item.write(new File(uploadDirObj, fileName)); } } apkInfoMapper.insertVersionInfo(version, new SimpleDateFormat("yy-MM-dd HH:mm:ss").format(System.currentTimeMillis()), (uploadRelativeDir + fileName)); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }
注:
为保证服务器安全,如果文件不提供下载,上传文件应该放在外界无法直接访问的目录下,如WEB-INF目录下;
不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,而有些只是单纯的文件名,要处理获取到的上传文件的文件名的路径部分,只保留文件名部分;
为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名
四、扩展:利用SpringMVC实现单文件/多文件上传下载
1 package com.mucfc; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.FileOutputStream; 6 import java.io.InputStream; 7 import java.io.OutputStream; 8 import java.net.URLEncoder; 9 import java.util.ArrayList; 10 import java.util.HashMap; 11 import java.util.Iterator; 12 import java.util.List; 13 import java.util.Map; 14 import java.util.UUID; 15 16 import javax.servlet.ServletContext; 17 import javax.servlet.http.HttpServletRequest; 18 import javax.servlet.http.HttpServletResponse; 19 20 import org.springframework.stereotype.Controller; 21 import org.springframework.ui.ModelMap; 22 import org.springframework.web.bind.annotation.RequestMapping; 23 import org.springframework.web.bind.annotation.RequestParam; 24 import org.springframework.web.multipart.MultipartFile; 25 import org.springframework.web.multipart.MultipartHttpServletRequest; 26 import org.springframework.web.multipart.commons.CommonsMultipartFile; 27 import org.springframework.web.multipart.commons.CommonsMultipartResolver; 28 29 @Controller 30 @RequestMapping("/file") 31 public class FileController { 32 33 @RequestMapping("/toFile") 34 public String toFileUpload() { 35 return "fileUpload"; 36 } 37 38 @RequestMapping("/toFile2") 39 public String toFileUpload2() { 40 return "fileUpload2"; 41 } 42 43 /** 44 * 方法一上传文件 45 */ 46 @RequestMapping("/onefile") 47 public String oneFileUpload(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request, 48 ModelMap model) { 49 50 // 获得原始文件名 51 String fileName = file.getOriginalFilename(); 52 System.out.println("原始文件名:" + fileName); 53 54 // 新文件名 55 String newFileName = UUID.randomUUID() + fileName; 56 57 // 获得项目的路径 58 ServletContext sc = request.getSession().getServletContext(); 59 // 上传位置 60 String path = request.getRealPath("/img") + "/"; // 设定文件保存的目录 61 62 File f = new File(path); 63 if (!f.exists()) 64 f.mkdirs(); 65 if (!file.isEmpty()) { 66 try { 67 FileOutputStream fos = new FileOutputStream(path + newFileName); 68 InputStream in = file.getInputStream(); 69 int b = 0; 70 while ((b = in.read()) != -1) { 71 fos.write(b); 72 } 73 fos.close(); 74 in.close(); 75 } catch (Exception e) { 76 e.printStackTrace(); 77 } 78 } 79 80 System.out.println("上传图片到:" + path + newFileName); 81 // 保存文件地址,用于JSP页面回显 82 model.addAttribute("fileUrl", "img/" + newFileName); 83 return "fileUpload"; 84 } 85 86 /** 87 * 方法二上传文件,一次一张 88 */ 89 @RequestMapping("/onefile2") 90 public String oneFileUpload2(HttpServletRequest request, HttpServletResponse response) throws Exception { 91 CommonsMultipartResolver cmr = new CommonsMultipartResolver(request.getServletContext()); 92 if (cmr.isMultipart(request)) { 93 MultipartHttpServletRequest mRequest = (MultipartHttpServletRequest) (request); 94 Iterator<String> files = mRequest.getFileNames(); 95 while (files.hasNext()) { 96 MultipartFile mFile = mRequest.getFile(files.next()); 97 if (mFile != null) { 98 String fileName = UUID.randomUUID() + mFile.getOriginalFilename(); 99 String path = request.getRealPath("") + "/img/" + fileName; 100 System.out.println(path); 101 File localFile = new File(path); 102 if (!localFile.exists()) { 103 localFile.createNewFile(); 104 } 105 mFile.transferTo(localFile); 106 request.setAttribute("fileUrl", "img/" + fileName); 107 } 108 } 109 } 110 return "fileUpload"; 111 } 112 113 /** 114 * 一次上传多张图片 115 */ 116 @RequestMapping("/threeFile") 117 public String threeFileUpload(@RequestParam("file") CommonsMultipartFile files[], HttpServletRequest request, 118 ModelMap model) { 119 120 List<String> list = new ArrayList<String>(); 121 // 获得项目的路径 122 ServletContext sc = request.getSession().getServletContext(); 123 // 上传位置 124 String path = sc.getRealPath("/img") + "/"; // 设定文件保存的目录 125 File f = new File(path); 126 if (!f.exists()) 127 f.mkdirs(); 128 129 for (int i = 0; i < files.length; i++) { 130 // 获得原始文件名 131 String fileName = files[i].getOriginalFilename(); 132 System.out.println("原始文件名:" + fileName); 133 // 新文件名 134 String newFileName = UUID.randomUUID() + fileName; 135 if (!files[i].isEmpty()) { 136 try { 137 FileOutputStream fos = new FileOutputStream(path + newFileName); 138 InputStream in = files[i].getInputStream(); 139 int b = 0; 140 while ((b = in.read()) != -1) { 141 fos.write(b); 142 } 143 fos.close(); 144 in.close(); 145 } catch (Exception e) { 146 e.printStackTrace(); 147 } 148 } 149 System.out.println("上传图片到:" + path + newFileName); 150 list.add("img/" + newFileName); 151 152 } 153 // 保存文件地址,用于JSP页面回显 154 model.addAttribute("fileList", list); 155 return "fileUpload2"; 156 157 } 158 159 /** 160 * 列出所有的图片 161 */ 162 @RequestMapping("/listFile") 163 public String listFile(HttpServletRequest request, HttpServletResponse response) { 164 // 获取上传文件的目录 165 ServletContext sc = request.getSession().getServletContext(); 166 // 上传位置 167 String uploadFilePath = sc.getRealPath("/img") + "/"; // 设定文件保存的目录 168 // 存储要下载的文件名 169 Map<String, String> fileNameMap = new HashMap<String, String>(); 170 // 递归遍历filepath目录下的所有文件和目录,将文件的文件名存储到map集合中 171 listfile(new File(uploadFilePath), fileNameMap);// File既可以代表一个文件也可以代表一个目录 172 // 将Map集合发送到listfile.jsp页面进行显示 173 request.setAttribute("fileNameMap", fileNameMap); 174 return "listFile"; 175 } 176 177 public void listfile(File file, Map<String, String> map) { 178 // 如果file代表的不是一个文件,而是一个目录 179 if (!file.isFile()) { 180 // 列出该目录下的所有文件和目录 181 File files[] = file.listFiles(); 182 // 遍历files[]数组 183 for (File f : files) { 184 // 递归 185 listfile(f, map); 186 } 187 } else { 188 /** 189 * 处理文件名,上传后的文件是以uuid_文件名的形式去重新命名的,去除文件名的uuid_部分 190 * file.getName().indexOf 191 * ("_")检索字符串中第一次出现"_"字符的位置,如果文件名类似于:9349249849-88343-8344_阿_凡_达.avi 192 * 那么file.getName().substring(file.getName().indexOf("_")+1) 193 * 处理之后就可以得到 194 */ 195 String realName = file.getName().substring(file.getName().indexOf("_") + 1); 196 // file.getName()得到的是文件的原始名称,这个名称是唯一的,因此可以作为key,realName是处理过后的名称,有可能会重复 197 map.put(file.getName(), realName); 198 } 199 } 200 201 @RequestMapping("/downFile") 202 public void downFile(HttpServletRequest request, HttpServletResponse response) { 203 System.out.println("1"); 204 // 得到要下载的文件名 205 String fileName = request.getParameter("filename"); 206 System.out.println("2"); 207 try { 208 fileName = new String(fileName.getBytes("iso8859-1"), "UTF-8"); 209 System.out.println("3"); 210 // 获取上传文件的目录 211 ServletContext sc = request.getSession().getServletContext(); 212 System.out.println("4"); 213 // 上传位置 214 String fileSaveRootPath = sc.getRealPath("/img"); 215 216 System.out.println(fileSaveRootPath + "\\" + fileName); 217 // 得到要下载的文件 218 File file = new File(fileSaveRootPath + "\\" + fileName); 219 220 // 如果文件不存在 221 if (!file.exists()) { 222 request.setAttribute("message", "您要下载的资源已被删除!!"); 223 System.out.println("您要下载的资源已被删除!!"); 224 return; 225 } 226 // 处理文件名 227 String realname = fileName.substring(fileName.indexOf("_") + 1); 228 // 设置响应头,控制浏览器下载该文件 229 response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(realname, "UTF-8")); 230 // 读取要下载的文件,保存到文件输入流 231 FileInputStream in = new FileInputStream(fileSaveRootPath + "\\" + fileName); 232 // 创建输出流 233 OutputStream out = response.getOutputStream(); 234 // 创建缓冲区 235 byte buffer[] = new byte[1024]; 236 int len = 0; 237 // 循环将输入流中的内容读取到缓冲区当中 238 while ((len = in.read(buffer)) > 0) { 239 // 输出缓冲区的内容到浏览器,实现文件下载 240 out.write(buffer, 0, len); 241 } 242 // 关闭文件输入流 243 in.close(); 244 // 关闭输出流 245 out.close(); 246 } catch (Exception e) { 247 248 } 249 } 250 }
原理:Servlet或Spring(实际上也是依赖Servlet)将收到的文件持久化到默认的临时磁盘目录(用户也可以指定),代码中获取到的File对象即是存在该临时目录的文件对象
另外,关于HTTP POST提交数据的三种常用方式(application/x-www-form-urlencoded、multipart/form-data、application/json)的区别可参阅:https://imququ.com/post/four-ways-to-post-data-in-http.html
五、Spring文件上传
两种方法:
法1:通过@RequestParameter
法2:通过@RequestPart
两种都是用来接收multipart/form-data请求传来的数据,区别:前者可以同时接收文件(multipart)域和普通键值对参数,而后者可以同时接收文件域和更复杂的对象(如json、xml等)。
可参阅:https://stackoverflow.com/questions/16230291/requestpart-with-mixed-multipart-request-spring-mvc-3-2