使用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

 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>

要點:

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

 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 }

1) 在WEB-INF中創建upload文件夾時. IDEA不會在out目錄的WEB-INF中創建upload文件夾, 需要手動創建, 所以上面先檢查upload文件夾是否存在

2) 在finally中關閉流時, 應該先檢查流是否為null, 否則當上傳表單不為multipart/form-data類型時, 執行return后再執行finally, 程序就會出現NPE

3) 記得在web.xml中配置servlet的映射路徑

3. 測試

4. 使用瀏覽器抓包

 

三. 禁止別人訪問上傳文件目錄

上傳文件目錄應該放在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算法產生圖片上傳的隨機目錄

為了防止一個目錄中出現太多文件, 使用算法打散存儲

 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 }

這里放張圖片, 方便大家食用...

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

ServletFileUpload.setFileSizeMax(1024);方法實現,並通過捕獲FileUploadBase.FileSizeLimitExceededException異常以給用戶友好提示

6. 確保臨時文件被刪除

在處理完上傳文件后,調用item.delete方法

7. 限制上傳文件的類型

在收到上傳文件名時,判斷后綴名是否合法

8. 監聽文件上傳進度

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 });

備注:

1) 可以配合ajax+div/css生成進度條
2) 監聽器在request解析之前設置

附: 改造后的Servlet

  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 }

 效果預覽:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM