Java Web(十二) commons-fileupload上傳下載


      今天心態正常。。。繼續努力。。

                      --WZY

一、上傳原理和代碼分析。

      上傳:我們把需要上傳的資源,發送給服務器,在服務器上保存下來。

      下載:下載某一個資源時,將服務器上的該資源發送給瀏覽器。

      難點:服務器端獲取資源時比較麻煩,

            

 

      瀏覽器端

          

      注意:enctype=multipart/form-data:該屬性表明發送的請求體的內容是多表單元素的,通俗點講,就是有各種各樣的數據,可能有二進制數據,也可能有表單數據,等等,所以使用該屬性也進行其區分,發送的格式如下(使用火狐中的Firebug插件進行捕捉的信息。)

          

          

      使用multipart/form-data會有一個boundary屬性,來用將提交的表單數據進行分隔,以用來讓服務器知道哪個是我們上傳的資源,哪個是普通的表單數據。

 

      服務器端,

          如果不使用commons-fileupload插件來幫我們處理上傳后的數據而讓我們自己手動處理的話,也是可以的,但是十分麻煩,因為我們需要將所有的請求體獲取到,然后通過字符串的分割,通過boundary這個屬性進行分割,然后一步步獲取到我們想要的數據。

          使用commons-fileupload進行處理上傳內容。

          

          

          

        代碼

 1     try {
 2             
 3             //1 工廠
 4             FileItemFactory fileItemFactory = new DiskFileItemFactory();
 5             
 6             //2 核心類
 7             ServletFileUpload servletFileUpload = new ServletFileUpload(fileItemFactory);
 8             
 9             //3 解析request  ,List存放 FileItem (表單元素的封裝對象,一個<input>對應一個對象)
10             List<FileItem> list = servletFileUpload.parseRequest(request);
11             
12             //4 遍歷集合獲得數據
13             for (FileItem fileItem : list) {
14                 if(fileItem.isFormField()){
15                     // 5 是否為表單字段(普通表單元素)
16                     //5.1.表單字段名稱
17                     String fieldName = fileItem.getFieldName();
18                     System.out.println(fieldName);
19                     //5.2.表單字段值
20                     String fieldValue = fileItem.getString();    //中文會出現亂碼
21                     System.out.println(fieldValue);
22                 } else {
23                     //6 上傳字段(上傳表單元素)
24                     //6.1.表單字段名稱  fileItem.getFieldName();
25                     //6.2.上傳文件名
26                     String fileName = fileItem.getName();
27                     // * 兼容瀏覽器, IE : C:\Users\xxx\Desktop\abc.txt  ; 其他瀏覽器 : abc.txt
28                     fileName = fileName.substring(fileName.lastIndexOf("\\") + 1);
29                     System.out.println(fileName);    //上傳的文件名會中文亂碼,
30                     //6.3.上傳內容
31                     InputStream is = fileItem.getInputStream();    //獲得輸入流,
32                     String parentDir = this.getServletContext().getRealPath("/WEB-INF/upload");
33                     File file = new File(parentDir,fileName);
34                     if(! file.getParentFile().exists()){  //父目錄不存在
35                         file.getParentFile().mkdirs();        //mkdirs():創建文件夾,如果上級目錄沒有的話,也一並創建出來。
36                     }
37                     FileOutputStream out = new FileOutputStream(file);    
38                     byte[] buf = new byte[1024];
39                     int len = -1;
40                     while( (len = is.read(buf)) != -1){
41                         out.write(buf, 0, len);
42                     }
43                     
44                     //關閉
45                     out.close();
46                     is.close();
47                 }
48             }
49             
50         } catch (Exception e) {
51             e.printStackTrace();
52             
53             throw new RuntimeException(e);
54             
55         }
56     }
View Code

 

          

        其中需要注意的是流的操作,和File類的操作,mkdirs()和mkdir()的區別是什么?它們都是用來創建文件夾的,區別在mkdirs能創建多級目錄,而mkdir()只能創建當前的文件夾,如果發現上一層目錄並還沒有創建時,它也會無動於衷,什么也不干,也就不能創建當前文件夾,必須先要創建了上一級文件夾才可以。

        上面還有很多的問題需要解決,比如上傳文件名的亂碼問題,比如單表內容的亂碼問題,比如上傳文件同名問題,如果上傳的文件很大,該如何進行處理,比如,放在同一級目錄下的文件過多如何處理。每次都自己手動寫輸出流,將內容存到指定位置,太過麻煩,等等問題,現在寫一個加強版,在解決上面所有的問題。

         上傳文件名亂碼問題:使用servletFileUpload.setHeaderEncoding("UTF-8");或者request.setCharacterEncoding("UTF-8")都可以

         表單內容亂碼問題:使用getString("utf-8")即可,也就是在獲取內容時,就可以設置碼表。

         上傳文件同名問題:使用UUID.randomUUID().toString().replace("-", "").獲得一個獨一無二的32位數字

         使用FileUtils.copyInputStreamToFile(is, file);來將內容輸出到指定路徑文件中去,mkdirs() 自動創建目錄

         同一級目錄下的文件過多問題:創建多級目錄,通過文件名的hashcode的值,hashCode為int型,占4個字節,一個字節占8位,也就是占32位,將其分組,4位4位一組,就能分8組,每一次代表一層目錄,也就能夠分8層目錄,每一層中,有可以創建16種不同的文件夾。這樣算下來,就能有很多很多個文件夾了,每個文件夾下面都可以存很多文件,看我們的業務需求,來決定要創建幾層目錄,就從hashcode中拿幾組數據出來。原理圖如下

            

        生成兩級目錄

          

      代碼

 1 public class StringUtils {
 2 
 3     /**
 4      * 生成二級目錄
 5      * @param fileName  abc.txt
 6      * @return  /4/5
 7      */
 8     public static String getDir(String fileName) {
 9         //1 hashCode值
10         int hashCode = fileName.hashCode();
11         System.out.println(hashCode);
12         //2 第一層 0xf表示15的16進制數。
13         int dir1 = hashCode & 0xf;
14         //3 第二層目錄
15         int dir2 = hashCode >>> 4 & 0xf;
16         
17         //4 拼寫
18         return "/" + dir1 + "/" + dir2;
19     }
20     
21     public static void main(String[] args) {
22         System.out.println(getDir("abc.txt"));
23     }
24 
25 }
View Code

 

        上傳代碼

          

          

      

 1     public void doGet(HttpServletRequest request, HttpServletResponse response)
 2             throws ServletException, IOException {
 3         try {
 4             
 5             //0.5 檢查是否支持文件上傳  ,檢查請求頭Content-Type : multipart/form-data 
 6             if(!ServletFileUpload.isMultipartContent(request)){
 7                 throw new RuntimeException("不要得瑟,沒用");
 8             }
 9             
10             //1 工廠
11             DiskFileItemFactory fileItemFactory = new DiskFileItemFactory();
12             // 1.1 設置是否生產臨時文件臨界值。大於2M生產臨時文件。保證:上傳數據完整性。
13             fileItemFactory.setSizeThreshold(1024 * 1024 * 2);  //2MB
14             // 1.2 設置臨時文件存放位置
15             // * 臨時文件擴展名  *.tmp  ,臨時文件可以任意刪除。
16             String tempDir = this.getServletContext().getRealPath("/temp");
17             fileItemFactory.setRepository(new File(tempDir));
18             
19             //2 核心類
20             ServletFileUpload servletFileUpload = new ServletFileUpload(fileItemFactory);
21             // 2.1 如果使用無參構造  ServletFileUpload() ,手動設置工廠
22             //servletFileUpload.setFileItemFactory(fileItemFactory);
23             // 2.2 單個上傳文件大小
24             //servletFileUpload.setFileSizeMax(1024*1024 * 2);  //2M
25             // 2.3 整個上傳文件總大小
26             //servletFileUpload.setSizeMax(1024*1024*10);        //10M
27             // 2.4 設置上傳文件名的亂碼
28             // * 首先使用  setHeaderEncoding 設置編碼
29             // * 如果沒有設置將使用請求編碼 request.setCharacterEncoding("UTF-8")
30             // * 以上都沒有設置,將使用平台默認編碼
31             servletFileUpload.setHeaderEncoding("UTF-8");
32             // 2.5 上傳文件進度,提供監聽器進行監聽。
33             servletFileUpload.setProgressListener(new MyProgressListener());
34             
35             
36             //3 解析request  ,List存放 FileItem (表單元素的封裝對象,一個<input>對應一個對象)
37             List<FileItem> list = servletFileUpload.parseRequest(request);
38             
39             //4 遍歷集合獲得數據
40             for (FileItem fileItem : list) {
41                 // 判斷
42                 if(fileItem.isFormField()){
43                     // 5 是否為表單字段(普通表單元素)
44                     //5.1.表單字段名稱
45                     String fieldName = fileItem.getFieldName();
46                     System.out.println(fieldName);
47                     //5.2.表單字段值 , 解決普通表單內容的亂碼
48                     String fieldValue = fileItem.getString("UTF-8");
49                     System.out.println(fieldValue);
50                 } else {
51                     //6 上傳字段(上傳表單元素)
52                     //6.1.表單字段名稱  fileItem.getFieldName();
53                     //6.2.上傳文件名
54                     String fileName = fileItem.getName();
55                     // * 兼容瀏覽器, IE : C:\Users\liangtong\Desktop\abc.txt  ; 其他瀏覽器 : abc.txt
56                     fileName = fileName.substring(fileName.lastIndexOf("\\") + 1);
57                     // * 文件重名
58                     fileName = UUID.randomUUID().toString().replace("-", "") + fileName;
59                     // * 單個文件夾文件個數過多?
60                     String subDir = StringUtils.getDir(fileName);
61                     
62                     System.out.println(fileName);
63                     //6.3.上傳內容
64                     InputStream is = fileItem.getInputStream();
65                     String parentDir = this.getServletContext().getRealPath("/WEB-INF/upload");
66                     File file = new File(parentDir + subDir,fileName);
67 
68                     // 將指定流 寫入 到 指定文件中  -- mkdirs() 自動創建目錄
69                     FileUtils.copyInputStreamToFile(is, file);
70                     
71                     //7刪除臨時文件
72                     fileItem.delete();
73                 }
74             }
75             
76         } catch (Exception e) {
77             e.printStackTrace();
78             
79             throw new RuntimeException(e);
80             
81         }
82     }
View Code

 

        其中如果需要做上傳的進度條時,就可以使用監聽器來進行監聽上傳數據的進度,實現ProgressListener即可。

            

 

    總結上傳:

        其實理解了也不是很難,就是上傳文件后的處理比較麻煩,各種小問題,存儲過程最為麻煩。

        1、創建工廠類

        2、使用核心類,

        3、解析request請求,

        4、遍歷請求體的內容,將上傳內容和普通表單內容都獲取出來

        5、獲取到上傳內容時,對其存儲位置進行設置

        其中很多細節問題都在上面已經說清楚了,代碼中也有很多注釋。

 

二、下載原理和代碼實現

      實現下載有兩種方式,

      第一種,使用a標簽,也就是使用超鏈接,如果瀏覽器能夠解析,則直接顯示出來,如果不能解析,則進行下載,這種方式不太好,

          

        我使用的是火狐,第一個和第三個都能直接解析出來,而第二個則不能解析,顯示的是下載頁面

            

 

      第二種方式:

            1、設置響應頭,讓瀏覽器知道應該下載,而不是解析

            response.setHeader("content-disposition", "attachment;filename=" +fileName);  //設置content-disposition響應頭

            2、獲取輸入流,指向需要下載的文件

            InputStream is = this.getServletContext().getResourceAsStream("/download/1.jpg");

            3、獲取輸出流,將其文件傳到瀏覽器端

            ServletOutputStream out = response.getOutputStream();

            4、使用IOUtils.copy(is,out);直接將輸入流和輸出流傳進去,就會幫我們把輸出流讀到的內容通過輸出流輸出到瀏覽器。內部實現原理應該就如下所示

                int b = -1;

                byte[] bf = new byte[1024];

                while((b=is.read(bf)) != -1){

                  out.write(bf,0,b);

                }

 

            

            

            

 

          注意:這里下載時寫的下載文件名不包含中文,所以能夠正常顯示,如果寫中文的話,則會亂碼,甚至是中文都不會顯示出來。有兩種方式處理這個中文亂碼問題

          1、簡單處理

             fileName = new String(fileName.getBytes("gbk"),"ISO8859-1");

             fileName = new String(fileName.getBytes("utf-8"),"ISO8859-1");

             fileName = new String(fileName.getBytes(),"ISO8859-1"); //這種跟第一種是一樣的,默認使用的編碼就是gbk。

           原理

             上面代碼意思是,先將fileName使用gbk或者utf-8進行編碼,然后在使用ISO8859-1進行解碼,此時的fileName是一個亂碼文字。具體原理圖可以看下面兩張圖

               這張圖解釋了上面這段代碼所做的事情,而整個編碼過程,我來簡單口述一下,在服務器端,寫上面這段代碼,美女是中文字,將其使用GBK碼表進行編碼后,就會獲得一個計算機認識的符號,比如是123,然后我們在將這個計算機認識的符號,123使用ISO8859-1碼表進行解碼,變成我們所認識的漢字,但是因為編碼和解碼所用碼表不一樣,所以不能正常顯示,也就是此時的fileName本身就是一個亂碼的文字,然后在響應頭中,因為是使用ISO8859-1的碼表進行編碼,所以fileName本身亂碼的文字,就會變為機器認識的123,當到了瀏覽器端,因為123是由gbk進行編碼的,所以如果使用gbk進行的解碼的話,就會使其正確的顯示,過程就是這樣

                

                

 

            如果還不知道什么是編碼,什么是解碼,那么就去看一下request和response亂碼問題的解決那篇博文,應該能解決你的疑問。

        代碼:

  

 1         
 2         String fileName = "美女.jpg";
 3         fileName = new String(fileName.getBytes("gbk"),"ISO8859-1");
 4         //設置響應頭,通知瀏覽器應該進行下載,而不是解析。
 5         response.setHeader("content-disposition", "attachment;filename=" +fileName);
 6         
 7         // 讀取資源,發送給瀏覽器
 8         InputStream is = this.getServletContext().getResourceAsStream("/download/1.jpg");
 9         ServletOutputStream out = response.getOutputStream();
10         IOUtils.copy(is, out);
View Code

 

 

        2、復雜處理

          根據每個瀏覽器的不同,而進行不同的操作。根據瀏覽器不同進行設置。IE和谷歌 URL編碼,火狐采用BASE64編碼

           IE和谷歌   

                  // * IE 谷歌 采用 URL編碼,就用URL對fileName進行編碼即可。其內部跟我們上面講解編碼解碼時的思路類似,可以將fileName輸出看看是個什么結果,還是一個xxx.jpg,說明就是將fileName進行編碼,解碼的過程。
                  if(userAgent.contains("MSIE") || userAgent.contains("Chrome")){  //判斷是不是google或者IE瀏覽器
                          fileName = URLEncoder.encode(fileName, "UTF-8");
                  }

            火狐 采用 Base64編碼 

              

              這里會出現兩個問題,

                  一個是BASE64Encoder這個類找不到包,這個問題的解決參考

                          http://blog.csdn.net/jbxiaozi/article/details/7351768

                           http://www.cnblogs.com/silentjesse/archive/2013/03/07/2948146.html

                   另一個是格式問題。這個非常麻煩,我是記不住,具體查看圖中的注釋,格式已經寫出來了。

       下載原理總代碼,包含上面所講解到的。

 1         //下載 中文文件名 亂碼
 2         String fileName = "美眉.jpg";
 3         //方案1:簡單方案
 4         //fileName = new String(fileName.getBytes("GBK"),"ISO8859-1");
 5         //方案2:不同的瀏覽器對文件名解析采用不同方案。
 6         String userAgent = request.getHeader("User-Agent");
 7         // * IE 谷歌 采用 URL編碼
 8         if(userAgent.contains("MSIE") || userAgent.contains("Chrome")){
 9             fileName = URLEncoder.encode(fileName, "UTF-8");
10         }
11         // * 火狐 采用 Base64編碼
12         if(userAgent.contains("Firefox")){
13             BASE64Encoder base64Encoder = new BASE64Encoder();
14             String encStr = base64Encoder.encode(fileName.getBytes("UTF-8"));
15             // 格式 :  =?字符集?編碼方式?....?=
16             // * 編碼方式:B base64 ,Q q編碼 
17             fileName = "=?UTF-8?B?"+encStr+"?=";
18         }
19         
20         //設置響應頭,通知瀏覽器應該進行下載,而不是解析。
21         response.setHeader("content-disposition", "attachment;filename=" +fileName);
22         
23         // 讀取資源,發送給瀏覽器
24         InputStream is = this.getServletContext().getResourceAsStream("/download/1.jpg");
25         ServletOutputStream out = response.getOutputStream();
26         IOUtils.copy(is, out);
View Code

 

    總結:下載其實非常簡單,就是在編寫下載文件名出現的中文亂碼問題比較麻煩,其他的套路很容易,

        1、設置響應頭,讓瀏覽器知道該文件是需要下載的

        2、就是找准要下載的文件的路徑從而拿到輸入流,在通過response拿到輸出流,

        3、通過OUtils.copy(is, out);就解決了,不用在乎其中內部的實現。

        

 

三、總結

      到這里,上傳和下載的原理和代碼都已經講解完了,你學會了嗎?其中只能總結一點,基礎知識很重要,我在編寫這博文時,發現連最基礎的對File類的操作都模糊不清,對流的操作也不是很感冒,但是通過翻閱資料,也差不多知道了自己曾不知道的東西。

 


免責聲明!

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



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