1、文件上传的原理分析
1.1文件上传的必要前提:
- a、提供form表单,method必须是post
- b、form表单的enctype必须是multipart/form-data
- c、提供input type="file"类的上传输入域
1.2enctype属性
作用:告知服务器请求正文的MIME类型。(请求消息头:Content-Type作用是一致的)
可选值:application/x-www-form-urlencoded(默认):
正文:name=admin&password=123
服务器获取数据:String name = request.getParameter("name");
multipart/form-data:
正文
服务器获取数据:request.getParameter(String)方法获取指定的表单字段字符内容,但文件上传表单已经不在是字符内容,而是字节内容,所以失效。
文件上传:解析请求正文的每部分的内容。
2、借助第三方的上传组件实现文件上传
2.1 fileupload概述
fileupload是由apache的commons组件提供的上传组件。它最主要的工作就是帮我们解析request.getInputStream()。
导入commons-fileupload相关jar包
- commons-fileupload.jar,核心包;
- commons-io.jar,依赖包。
2.2 fileupload的核心类有:
- DiskFileItemFactory 和磁盘打交道(工厂模式)
- ServletFileUpload 核心对象 操作fileitem
- FileItem 表单对象
a、解析原理
2.3 fileupload简单应用
使用fileupload组件的步骤如下:
创建工厂类DiskFileItemFactory对象: DiskFileItemFactory factory = new DiskFileItemFactory()
使用工厂创建解析器对象: ServletFileUpload fileUpload = new ServletFileUpload(factory)
使用解析器来解析request对象: List<FileItem> list = fileUpload.parseRequest(request)
FileItem对象对应一个表单项(表单字段)。可以是文件字段或普通字段
- boolean isFormField():判断当前表单字段是否为普通文本字段,如果返回false,说明是文件字段;
- String getFieldName():获取字段名称,例如:<input type=”text” name=”username”/>,返回的是username;
- String getString():获取字段的内容,如果是文件字段,那么获取的是文件内容,当然上传的文件必须是文本文件;
- String getName():获取文件字段的文件名称;(a.txt)
- String getContentType():获取上传的文件的MIME类型,例如:text/plain。
- int getSize():获取上传文件的大小;
- InputStream getInputStream():获取上传文件对应的输入流;
- void write(File):把上传的文件保存到指定文件中。
- delete();
3、文件上传时要考虑的几个问题(经验分享)
a、保证服务器的安全
把保存上传文件的目录放在用户直接访问不到的地方。
b、避免文件被覆盖
让文件名唯一即可
c、避免同一个文件夹中的文件过多
- 方案一:按照日期进行打散存储目录
- 方案二:用文件名的hashCode计算打散的存储目录:二级目录
d、限制文件的大小:web方式不适合上传大的文件
- 单个文件大小:ServletFileUpload.setFileSizeMax(字节)
- 总文件大小:(多文件上传)ServletFileUpload.setSizeMax(字节)
e、上传字段用户没有上传的问题
通过判断文件名是否为空即可
f、临时文件的问题
DiskFileItemFactory: 作用:产生FileItem对象
内部有一个缓存,缓存大小默认是10Kb。如果上传的文件超过10Kb,用磁盘作为缓存。
存放缓存文件的目录在哪里?默认是系统的临时目录。
如果自己用IO流实现的文件上传,要在流关闭后,清理临时文件。FileItem.delete();
1 package com.upload; 2 3 import java.io.File; 4 import java.io.FileOutputStream; 5 import java.io.IOException; 6 import java.io.InputStream; 7 import java.text.SimpleDateFormat; 8 import java.util.Date; 9 import java.util.List; 10 import java.util.UUID; 11 import java.util.logging.SimpleFormatter; 12 13 import javax.servlet.ServletException; 14 import javax.servlet.http.HttpServlet; 15 import javax.servlet.http.HttpServletRequest; 16 import javax.servlet.http.HttpServletResponse; 17 18 import org.apache.commons.fileupload.FileItem; 19 import org.apache.commons.fileupload.FileUploadBase; 20 import org.apache.commons.fileupload.FileUploadException; 21 import org.apache.commons.fileupload.disk.DiskFileItemFactory; 22 import org.apache.commons.fileupload.servlet.ServletFileUpload; 23 import org.apache.commons.io.FilenameUtils; 24 25 import com.util.UUIDUtil; 26 27 public class UploadServlet2 extends HttpServlet { 28 29 public void doGet(HttpServletRequest request, HttpServletResponse response) 30 throws ServletException, IOException { 31 //执行文件上传的操作 32 //判断是否写了那个multipart/form-data 即是否支持文件上传 33 boolean isMultipartContent = ServletFileUpload.isMultipartContent(request); 34 if(!isMultipartContent){ 35 throw new RuntimeException("your form is not multipart/form-data "); 36 } 37 //创建一个DiskFileItemfactory工厂类 38 DiskFileItemFactory factory = new DiskFileItemFactory(); 39 //保存临时文件的目录 40 factory.setRepository(new File("f:\")); 41 //创建一个servletFileUpload核心对象(表单解析器) 42 ServletFileUpload sfu = new ServletFileUpload(factory); 43 44 //解析request对象,并得到一个表单项的集合 45 try { 46 //sfu.setFileSizeMax(1024*1024*3);//3M 47 48 sfu.setFileSizeMax(1024*1024*36); 49 50 List<FileItem> fileItems = sfu.parseRequest(request); 51 //遍历表单项 52 for (FileItem fileItem : fileItems) { 53 if(fileItem.isFormField()){ 54 //普通表单项 55 processFormField(fileItem); 56 }else{ 57 //上传表单项 58 processUploadField(fileItem); 59 } 60 } 61 62 }catch (FileUploadBase.FileSizeLimitExceededException e) { 63 //throw new RuntimeException("文件过大,不能超过3M"); 64 System.out.println("文件过大,不能超过3M"); 65 } 66 catch (FileUploadException e) { 67 e.printStackTrace(); 68 } 69 } 70 //上传表单项 71 private void processUploadField(FileItem fileItem) { 72 73 try { 74 //得到文件输入流 75 InputStream is = fileItem.getInputStream(); 76 //创建一个文件存盘的目录 77 String directoryRealPath = this.getServletContext().getRealPath("/WEB-INF/upload"); 78 //既代表文件有代表目录 79 File storeDirectory = new File(directoryRealPath); 80 if(!storeDirectory.exists()){ 81 storeDirectory.mkdirs(); 82 } 83 //得到文件名 84 String filename = fileItem.getName(); 85 //filename = filename.substring(filename.lastIndexOf(File.separator)+1); 86 if(filename!=null){ 87 filename = FilenameUtils.getName(filename); 88 if(filename=="") 89 System.out.println("空文件"); 90 } 91 //解决重名问题 UUID亦可以 92 filename = UUIDUtil.getUUID()+"_"+filename; 93 //目录打散 94 //String childDirectory = makeChildDirectory(storeDirectory); 95 96 String childDirectory = makeChildDirectory(storeDirectory,filename); 97 98 //在storeDirectory目录下构建一个完整目录下的文件 99 File file = new File(storeDirectory,childDirectory+File.separator+filename); 100 //通过文件输出流将上传的文件保存到磁盘 101 FileOutputStream fos = new FileOutputStream(file); 102 int len = 0; 103 byte[] b = new byte[1024]; 104 while((len = is.read(b))!= -1){ 105 fos.write(b,0,len); 106 } 107 fos.close(); 108 is.close(); 109 fileItem.delete(); 110 } catch (IOException e) { 111 e.printStackTrace(); 112 } 113 114 } 115 //按目录打散 116 private String makeChildDirectory(File storeDirectory, String filename) { 117 int hascode = filename.hashCode();//返回字符串转换的32位hascode码 int值 118 119 String code = Integer.toHexString(hascode);//把hascode转换成十六进制的字符 assdas3234af 120 121 String childDirectory = code.charAt(0)+File.separator+code.charAt(1); 122 File file = new File(storeDirectory,childDirectory); 123 if(!file.exists()){ 124 file.mkdirs(); 125 } 126 return childDirectory; 127 } 128 /*//按日期打散 129 private String makeChildDirectory(File storeDirectory) { 130 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 131 String dateDirectory = sdf.format(new Date()); 132 //只管创建目录 133 File file = new File(storeDirectory,dateDirectory); 134 if(!file.exists()){ 135 file.mkdirs(); 136 } 137 return dateDirectory; 138 }*/ 139 //普通表单项 140 private void processFormField(FileItem fileItem) { 141 String fieldname = fileItem.getFieldName(); 142 String fieldvalue = fileItem.getString(); 143 System.out.println(fieldname+"="+fieldvalue); 144 145 } 146 147 public void doPost(HttpServletRequest request, HttpServletResponse response) 148 throws ServletException, IOException { 149 doGet(request, response); 150 } 151 152 }
中文编码问题
1 private void processUploadField(FileItem fileItem) { 2 // 得到文件输入流 3 try { 4 InputStream is = fileItem.getInputStream(); 5 // 创建一个文件存盘的目录 6 String directoryRealPath = this.getServletContext().getRealPath( 7 "/WEB-INF/upload"); 8 // 既代表文件有代表目录 9 File storeDirectory = new File(directoryRealPath); 10 if (!storeDirectory.exists()) { 11 storeDirectory.mkdirs(); 12 } 13 // 得到文件名 14 String filename = fileItem.getName(); 15 if (filename != null) { 16 filename = FilenameUtils.getName(filename); 17 if (filename == "") 18 System.out.println("空文件"); 19 } 20 // 解决重名问题 UUID亦可以 21 filename = UUIDUtil.getUUID() + "_" + filename; 22 String childDirectory = makeChildDirectory(storeDirectory, filename); 23 // 上传文件自动删除临时文件 24 /* 25 * fileItem.write(new 26 * File(storeDirectory,childDirectory+File.separator+filename)); 27 * fileItem.delete(); 28 */ 29 // 上传文件,自动删除临时文件 30 fileItem.write(new File(storeDirectory, childDirectory 31 + File.separator + filename)); 32 fileItem.delete(); 33 } catch (IOException e) { 34 e.printStackTrace(); 35 } catch (Exception e) { 36 e.printStackTrace(); 37 } 38 }
注:这里32行的fileItem.delete();是多余的,请看fileItem.write的帮助文档:(意思大概是说只想去上传,不会保留临时文件)
而有个问题是在所设置的目录下却还会出现临时文件这是为什么呢??
原来是在List<FileItem> fileItems = sfu.parseRequest(request);就创建了 在write里可以自动删除掉temp。
下面放一张大佬给的示例(佐证 在有inputstream时 file无法delete和renameTo操作)
4、文件的下载
1 package com.servlet; 2 3 import java.io.IOException; 4 import java.io.PrintWriter; 5 import java.net.URLEncoder; 6 7 import javax.servlet.ServletException; 8 import javax.servlet.http.HttpServlet; 9 import javax.servlet.http.HttpServletRequest; 10 import javax.servlet.http.HttpServletResponse; 11 12 public class DownloadServlet extends HttpServlet { 13 14 public void doGet(HttpServletRequest request, HttpServletResponse response) 15 throws ServletException, IOException { 16 //设置一个要下载的文件 17 String filename = "销售榜单.csv"; 18 19 //设置文件名的编码 20 if(request.getHeader("user-agent").toLowerCase().contains("msie")){ 21 filename = URLEncoder.encode(filename, "UTF-8");//将不安全的文件名改为UTF-8格式 22 }else{ 23 filename = new String(filename.getBytes("UTF-8"),"iso-8859-1");//火狐浏览器 24 } 25 //告知浏览器要下载文件 26 response.setHeader("content-disposition", "attachment;filename="+filename); 27 //response.setHeader("content-type", "image/jpeg"); 28 //根据文件名自动获得文件类型 29 response.setContentType(this.getServletContext().getMimeType(filename)); 30 //告知服务器使用什么编码 31 response.setCharacterEncoding("UTF-8"); 32 //创建一个文件输出流 33 PrintWriter out = response.getWriter(); 34 out.write("电视机,20 "); 35 out.write("洗衣机,10 "); 36 out.write("冰箱,8 "); 37 } 38 39 public void doPost(HttpServletRequest request, HttpServletResponse response) 40 throws ServletException, IOException { 41 doGet(request, response); 42 } 43 44 }