使用fileupload实现文件上传

一. fileupload组件工作原理

先来张图片, 帮助大家理解

使用fileupload实现文件上传

fileupload核心API

1. DiskFileItemFactory
构造器
1) DiskFileItemFactory() // 使用默认配置
2) DiskFileItemFactory(int sizeThreshold, File repository)
  sizeThreshold 内存缓冲区, 不能设置太大, 否则会导致JVM崩溃
  repository 临时文件目录

2. ServletFileUpload
1) isMutipartContent(request) // 判断上传表单是否为multipart/form-data类型 true/false
2) parseRequest(request) // 解析request, 返回值为List<FileItem>类型
3) setFileSizeMax(long) // 上传文件单个最大值 fileupload内部通过抛出异常的形式处理, 处理文件大小超出限制, 可以通过捕获这个异常, 提示给用户
4) setSizeMax(long) // 上传文件总量最大值
5) setHeaderEncoding(String) // 设置编码格式
6) setProgressListener(ProgressListener) // 设置监听器, 可以用于制作进度条

 

二. 使用fileupload实现文件上传

1. 编写JSP

使用fileupload实现文件上传

 1 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
 2 <html>
 3 <head>
 4     <title>演示文件上传</title>
 5 </head>
 6 <body>
 7     <form action="${pageContext.request.contextPath}/servlet/FileUpload1" method="post" enctype="multipart/form-data">
 8         用户名: <input type="text" name="username"/><br/>
 9         文件1: <input type="file" name="file1"/><br/>
10         文件2: <input type="file" name="file2"/><br/>
11         <input type="submit"/>
12     </form>
13 </body>
14 </html>

使用fileupload实现文件上传

要点:

1) 表单包含file类型输入项时, enctype属性必须设置为multipart/form-data

2) input:file必须指定name属性

3) 表单提交方式为post, 因为get请求无法携带大量数据

4) 若表单的提交方式为multipart/form-data, 那么在Servlet就无法使用getParameter方法获取表单数据, 可以通过获取客户机提交数据的输入流来获取所有上传数据, 然后进行解析.

1 // 获取客户机提交数据的输入流
2 request.getInputStream();

5) 解析数据难度较大, 一般不自己编写程序, 可以使用开源项目解析数据

2. 编写Servlet

使用fileupload实现文件上传

 1 public class FileUpload1 extends HttpServlet {
 2     @Override
 3     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 4 
 5         InputStream in = null;
 6         OutputStream out = null;
 7 
 8         try {
 9             // 使用默认配置创建解析器工厂
10             DiskFileItemFactory factory = new DiskFileItemFactory();
11             // 获取解析器
12             ServletFileUpload upload = new ServletFileUpload(factory);
13             // 上传表单是否为multipart/form-data类型
14             if (!upload.isMultipartContent(request)) {
15                 return;
16             }
17             // 解析request的输入流
18             List<FileItem> fileItemList = upload.parseRequest(request);
19             // 迭代list集合
20             for (FileItem fileItem : fileItemList) {
21                 if (fileItem.isFormField()) {
22                     // 普通字段
23                     String name = fileItem.getFieldName();
24                     String value = fileItem.getString();
25                     System.out.println(name + "=" + value);
26                 } else {
27                     // 上传文件
28                     // 获取上传文件名
29                     String fileName = fileItem.getName();
30                     fileName = fileName.substring(fileName.lastIndexOf("\\")+1);
31                     // 获取输入流
32                     in = fileItem.getInputStream();
33 
34                     // 获取上传文件目录
35                     String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
36                     // 上传文件名若不存在, 则先创建
37                     File savePathDir = new File(savePath);
38                     if (!savePathDir.exists()) {
39                         savePathDir.mkdir();
40                     }
41 
42                     // 获取输出流
43                     out = new FileOutputStream(savePath + "\\" + fileName);
44                     int len = 0;
45                     byte[] buffer = new byte[1024];
46                     while((len=in.read(buffer)) > 0) {
47                         out.write(buffer, 0, len);
48                     }
49                 }
50             }
51         } catch (Exception e) {
52             e.printStackTrace();
53         } finally {
54             if (in != null) {
55                 in.close();
56             }
57             if (out != null) {
58                 out.close();
59             }
60         }
61 
62     }
63 
64     @Override
65     protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
66         doGet(req, resp);
67     }
68 }

使用fileupload实现文件上传

1) 在WEB-INF中创建upload文件夹时. IDEA不会在out目录的WEB-INF中创建upload文件夹, 需要手动创建, 所以上面先检查upload文件夹是否存在

2) 在finally中关闭流时, 应该先检查流是否为null, 否则当上传表单不为multipart/form-data类型时, 执行return后再执行finally, 程序就会出现NPE

3) 记得在web.xml中配置servlet的映射路径

3. 测试

使用fileupload实现文件上传

4. 使用浏览器抓包

使用fileupload实现文件上传

 

三. 禁止别人访问上传文件目录

上传文件目录应该放在WEB-INF目录下, 禁止别人访问上传文件目录, 否则黑客可能通过上传脚本, 然后访问该脚本, 对网站发起攻击

举例:

1. 黑客上传一个JSP文件

test.jsp
1 <%
2     Runtime.getRuntime().exec("shutdown -s -t 200")  // 执行Windows命令
3 %>

2. 通过访问该文件, 关闭服务器

http://localhost:8080/upload/test.jsp

备注:

1) Runtime类  // 调用Windows程序

2) Window命令:

  shutdown -a
  format c:\

四. 待解决的问题

1. 解决上传文件名的中文乱码问题

upload.setHeaderEncoding("UTF-8");

2. 解决上传数据的中文乱码问题

1) 表单为文件上传时, 设置request的编码无效

request.setCharacterEncoding("UTF-8"); 

2) 只能手工转化

value = new String(value.getBytes("iso8859-1"), "UTF-8");

3) 调用upload组件的getString的重载方法实现的效果是相同的

value = upload.getString("UTF-8");

3. 上传文件夹存储在WEB-INF中, 防止用户直接访问上传文件

4. 文件名重复问题

使用UUID作为上传文件的名称

1 public String makeFileName(String fileName) {
2     return UUID.randomUUID().toString() + "_" + fileName;
3 }

5. 使用hash算法产生图片上传的随机目录

为了防止一个目录中出现太多文件, 使用算法打散存储

使用fileupload实现文件上传

 1 public String makePath(String savePath, String fileName) {
 2     // 根据文件名产生int型hashcode, 32位二进制
 3     int hashcode = fileName.hashCode();
 4     // 获取第4位 0-15
 5     int dir1 = hashcode&0xf;
 6     // 获取第5-8位 0-15
 7     int dir2 = (hashcode&0xf0)>>4;
 8     // 凭借随机目录
 9     String dir = savePath + "\\" + dir1 + "\\" + dir2;  // upload\2\4
10     // 若目录不存在时, 创建目录
11     File file = new File(dir);
12     if(!file.exists()) {
13         file.mkdirs();
14     }
15     return dir;
16 }

使用fileupload实现文件上传

这里放张图片, 方便大家食用...

使用fileupload实现文件上传

5. 限制上传文件的最大值

ServletFileUpload.setFileSizeMax(1024);方法实现,并通过捕获FileUploadBase.FileSizeLimitExceededException异常以给用户友好提示

6. 确保临时文件被删除

在处理完上传文件后,调用item.delete方法

7. 限制上传文件的类型

在收到上传文件名时,判断后缀名是否合法

8. 监听文件上传进度

使用fileupload实现文件上传

1 ServletFileUpload upload = new ServletFileUpload(factory);
2 upload.setProgressListener(new ProgressListener(){
3     // pBytesRead 当前处理
4     // pContentLength 文件总大小
5     // arg2 当前解析的item
6     public void update(long pBytesRead, long pContentLength, int arg2) {
7         System.out.println("文件大小为:" + pContentLength + ",当前已处理:" + pBytesRead);
8     }
9 });

使用fileupload实现文件上传

备注:

1) 可以配合ajax+div/css生成进度条
2) 监听器在request解析之前设置

附: 改造后的Servlet

使用fileupload实现文件上传

  1 public class FileUpload1 extends HttpServlet {
  2     @Override
  3     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  4 
  5         InputStream in = null;
  6         OutputStream out = null;
  7 
  8         // 获取上传文件目录
  9         String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
 10 
 11         try {
 12             // 使用默认配置创建解析器工厂
 13             DiskFileItemFactory factory = new DiskFileItemFactory();
 14             // 获取解析器
 15             ServletFileUpload upload = new ServletFileUpload(factory);
 16             upload.setProgressListener(new ProgressListener() {
 17                 @Override
 18                 public void update(long l, long l1, int i) {
 19                     System.out.println("文件大小为:" + l1 + ",当前已处理:" + l);
 20                 }
 21             });
 22             // 解决上传文件名的中文乱码问题
 23             upload.setHeaderEncoding("UTF-8");
 24             // 上传表单是否为multipart/form-data类型
 25             if (!upload.isMultipartContent(request)) {
 26                 return;
 27             }
 28             // 解析request的输入流
 29             List<FileItem> fileItemList = upload.parseRequest(request);
 30             // 迭代list集合
 31             for (FileItem fileItem : fileItemList) {
 32                 if (fileItem.isFormField()) {
 33                     // 普通字段
 34                     String name = fileItem.getFieldName();
 35                     // 调用getString重载方法, 解决上传数据的中文乱码问题
 36                     String value = fileItem.getString("UTF-8");
 37                     System.out.println(name + "=" + value);
 38                 } else {
 39                     // 上传文件
 40                     // 获取上传文件名
 41                     String fileName = fileItem.getName();
 42                     // input:file没有指定上传文件时, 结束本次循环并继续下一次循环
 43                     if(fileName == null && fileName.trim().equals("")) {
 44                         continue;
 45                     }
 46                     fileName = fileName.substring(fileName.lastIndexOf("\\")+1);
 47                     // 使用UUID作为上传文件的名称
 48                     fileName = makeFileName(fileName);
 49                     // 获取输入流
 50                     in = fileItem.getInputStream();
 51 
 52                     // 上传文件名若不存在, 则先创建
 53                     File savePathDir = new File(savePath);
 54                     if (!savePathDir.exists()) {
 55                         savePathDir.mkdir();
 56                     }
 57 
 58                     // 使用hash算法产生当前上传图片的随机目录
 59                     String currentFileSavePath = makePath(savePath, fileName);
 60 
 61                     // 获取输出流
 62                     out = new FileOutputStream(currentFileSavePath + "\\" + fileName);
 63                     int len = 0;
 64                     byte[] buffer = new byte[1024];
 65                     while((len=in.read(buffer)) > 0) {
 66                         out.write(buffer, 0, len);
 67                     }
 68                 }
 69             }
 70         } catch (Exception e) {
 71             e.printStackTrace();
 72         } finally {
 73             if (in != null) {
 74                 in.close();
 75             }
 76             if (out != null) {
 77                 out.close();
 78             }
 79         }
 80 
 81     }
 82     public String makeFileName(String fileName) {
 83         return UUID.randomUUID().toString() + "_" + fileName;
 84     }
 85     public String makePath(String savePath, String fileName) {
 86         // 根据文件名产生int型hashcode, 32位二进制
 87         int hashcode = fileName.hashCode();
 88         // 获取第4位 0-15
 89         int dir1 = hashcode&0xf;
 90         // 获取第5-8位 0-15
 91         int dir2 = (hashcode&0xf0)>>4;
 92         // 凭借随机目录
 93         String dir = savePath + "\\" + dir1 + "\\" + dir2;  // upload\2\4
 94         // 若目录不存在时, 创建目录
 95         File file = new File(dir);
 96         if(!file.exists()) {
 97             file.mkdirs();
 98         }
 99         return dir;
100     }
101 
102     @Override
103     protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
104         doGet(req, resp);
105     }
106 }

使用fileupload实现文件上传

 效果预览:

使用fileupload实现文件上传

 

上一篇:org.apache.tomcat.util.http.fileupload.FileUploadBase$FileSizeLimitExceededException: The field xxx


下一篇:《微服务架构设计模式》读书笔记【TBC】