一. 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 }
效果預覽:

