文件上傳



一、文件上傳的原理
        1、文件上傳的前提:
                a、form表單的method必須是post
                b、form表單的enctype必須是multipart/form-data(決定了POST請求方式,請求正文的數據類型)
                          注意:當表單的enctype是multipart/form-data,傳統的獲取請求參數的方法失效。
                          
                          ---------------------------------------------------------------------------------------------------------------------------
                          請求正文:(MIME協議進行描述的,正文是多部分組成的)
                          -----------------------------7dd32c39803b2    (// 分界符)
                          Content-Disposition: form-data; name="username"


                          wzhting
                          -----------------------------7dd32c39803b2
                          Content-Disposition: form-data; name="f1"; filename="C:\Documents and Settings\wzhting\妗岄潰\a.txt"
                          Content-Type: text/plain


                          aaaaaaaaaaaaaaaaaa
                          -----------------------------7dd32c39803b2
                          Content-Disposition: form-data; name="f2"; filename="C:\Documents and Settings\wzhting\妗岄潰\b.txt"
                          Content-Type: text/plain


                          bbbbbbbbbbbbbbbbbbb
                          -----------------------------7dd32c39803b2--
                          ---------------------------------------------------------------------------------------------------------------------------
                          
                c、form中提供input的type是file類型的文件上傳域



二、利用第三方組件實現文件上傳


            1、commons-fileupload組件:
                        jar:commons-fileupload.jar 和 commons-io.jar
                        
            2、 fileupload組件工作流程
                       
            
            3、核心類或接口
                    DiskFileItemFactory:設置環境
                                  public void setSizeThreshold(int?sizeThreshold) :
                                                      設置緩衝區大小。默認是10Kb。
                                                      當上傳的文件超出了緩衝區大小,fileupload組件將使用臨時文件緩存上傳文件
                                  public void setRepository(java.io.File repository):
                                                     設置臨時文件的存放目錄。默認是系統的臨時文件存放目錄。
                          
                    ServletFileUpload:核心上傳類(主要作用:解析請求的正文內容)
                                  boolean isMultipartContent(HttpServletRequest?request):
                                                      判斷用戶的表單的enctype是否是multipart/form-data類型的。
                                  List parseRequest(HttpServletRequest request):
                                                      解析請求正文中的內容
                                  setFileSizeMax(4*1024*1024);           //設置單個上傳文件的大小
                                  upload.setSizeMax(6*1024*1024);   //設置總文件大小
                            
                    FileItem:代表表單中的一個輸入域。
                                  boolean isFormField():     是否是普通字段
                                  String getFieldName:    獲取普通字段的字段名
                                  String getString():            獲取普通字段的值
                                  
                                  InputStream getInputStream():      獲取上傳字段的輸入流
                                  String getName():                            獲取上傳的文件名                                 
                                  getContentType();                            得到上傳文件的MIME類型
                                  
                                  FileItem.delete();                              方法刪除臨時文件。但一定要在關閉流之後。
                                  
          4、 文件上傳案例          
                        -------------------------------------------------------------------------------------------------------------
                        <body>
                              <form action="${pageContext.request.contextPath}/servlet/UploadServlet3" enctype="multipart/form-data" method="post">         
                                      用戶名: <input type="text" name="username" /> <br/><br/>
                                      文件1: <input type="file" name="f1" /> <br/><br/>
                                      文件2: <input type="file" name="f2" /> <br/><br/>
                                      <input type="submit" value="保存" />
                              </form>    
                        </body>
                        -------------------------------------------------------------------------------------------------------------
                        // 存儲目的地: tomacate/webapp/day21/files 
                        public class UploadServlet2 extends HttpServlet {
                        
                                public void doGet(HttpServletRequest request, HttpServletResponse response)
                                    throws ServletException, IOException {
                                        try {
                                                // 設置環境
                                                DiskFileItemFactory factory = new DiskFileItemFactory();
                                                // 得到存放上傳文件的真實路徑
                                                String storePath = getServletContext().getRealPath("/WEB-INF/files");
                                                
                                                // 判斷一下form是否爲enctype=multipart/file-data
                                                boolean isMultipart = ServletFileUpload.isMultipartContent(request);
                                                if(!isMultipart){
                                                        System.out.println("大傻鳥,快去將表單的enctype的值設置爲multipart/form-data");
                                                        return;
                                                }
                                          
                                                // ServletFileUpload核心類
                                                ServletFileUpload upload = new ServletFileUpload(factory);
                                                // 解析
                                                List<FileItem> items = upload.parseRequest(request);
                                                for(FileItem item : items){
                                                        if(item.isFormField()){
                                                                // 是普通字段
                                                                String fieldName = item.getFieldName();
                                                                String fieldValue = item.getString();
                                                                System.out.println(fieldName+"="+fieldValue);
                                                        }else{
                                                                // 是上傳字段
                                                                InputStream in = item.getInputStream();
                                                                // 上傳的文件名
                                                                // 注: 有的瀏覽器爲: c:\Documents and Settings\wzhting\妗岄潰\a.txt    有的爲  a.txt         
                                                                String fileName = item.getName();   
                                                                fileName = fileName.substring(fileName.lastIndexOf("\\")+1);   // 得到文件名:  a.txt
                                                                // 構件輸出流
                                                                String storeFile = storePath + "\\" + fileName;
                                                                OutputStream out = new FileOutputStream(storeFile);
                                                                byte[] b = new byte[1024];
                                                                int len = 0;
                                                                while((len=in.read(b))!=-1){
                                                                        out.write(b, 0, len);
                                                                }
                                                                out.close();
                                                                in.close();
                                                        }
                                                }
                                        } catch (FileUploadException e) {
                                                throw new RuntimeException("服務器繁忙");
                                        }
                                }


                                public void doPost(HttpServletRequest request, HttpServletResponse response)
                                    throws ServletException, IOException {
                                        doGet(request, response);
                                }
                                
                        }
                        -------------------------------------------------------------------------------------------------------------
          

三、文件上傳中要注意的9個問題
            1、如何保證服務器的安全
                        把保存上傳文件的目錄放到WEB-INF目錄中。
                        
            2、中文亂碼問題
                        2.1普通字段的中文請求參數
                                解決辦法:String value = FileItem.getString("UTF-8");
                        2.2上傳的文件名是中文
                                解決辦法:request.setCharacterEncoding("UTF-8");
                                
            3、重名文件被覆蓋的問題
                       
                        System.currentMillions()+"_"+a.txt(樂觀, 可能重複)
                        UUID+"_"+a.txt:保證文件名唯一
                      
            4、分目錄存儲上傳的文件
                        方式一:當前日期建立一個文件夾,當前上傳的文件都放到此文件夾中。
                        方式二:利用文件名的hash碼打散目錄來存儲。
                                      --------------------------------------------------------------------------------------
                                            int hashCode = fileName.hashCode();
                                            執行運算;   hashCode&0xf; 
                                                              1001 1010 1101 0010 1101 1100 1101 1010
                                                          & 0000 0000 0000 0000 0000 0000 0000 1111 
                                                              ---------------------------------------------------
                                                              0000 0000 0000 0000 0000 0000 0000 1010   
                                                              取hashCode的後4位   範圍:0000~1111:整數0~15共16個
                                              
                                             執行運算: (hashCode&0xf0)>>4
                                                              1001 1010 1101 0010 1101 1100 1101 1010
                                                        &   0000 0000 0000 0000 0000 0000 1111 0000  
                                                              ---------------------------------------------------
                                                              0000 0000 0000 0000 0000 0000 1101 0000  >>4
                                                              ---------------------------------------------------
                                                              0000 0000 0000 0000 0000 0000 0000 1101
                                                                                0000~1111:整數0~15共16個
                                   --------------------------------------------------------------------------------------
                                    
            5、限制用戶上傳的文件類型
                        通過判斷文件的擴展名來限制是不可取的。
                        通過判斷其Mime類型才靠譜。FileItem.getContentType();
              
            6、如何限制用戶上傳文件的大小
                        6.1單個文件大小限制。超出了大小友好提示
                                    抓異常進行提示:
                                          org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException
                        6.2總文件大小限制。超出了大小友好提示
                                    抓異常進行提示:
                                          org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException
                
            7、臨時文件的問題
                        commons-fileupload組件不會刪除超出緩存的臨時文件。                       
                        FileItem.delete()方法刪除臨時文件。但一定要在關閉流之後。
              
            8、多個文件上傳時,沒有上傳內容的問題
                        if(fileName==null||"".equals(fileName.trim())){
                                continue;
                        }
                      
            9、上傳進度檢測
                      給ServletFileUpload註冊一個進度監聽器即可,把上傳進度傳遞給頁面去顯示
                            //pBytesRead:當前以讀取到的字節數
                            //pContentLength:文件的長度
                            //pItems:第幾項
                      public void update(long pBytesRead, long pContentLength,
                          int pItems) {
                              System.out.println("已讀取:"+pBytesRead+",文件大小:"+
                                                                    pContentLength+",第幾項:"+pItems);
                      }
                      
                      
             例:
             --------------------------------------------------------------------------------------------------
               public class UploadServlet3 extends HttpServlet {


                      public void doGet(HttpServletRequest request, HttpServletResponse response)
                          throws ServletException, IOException {


                              // 解決上傳文件名的中文編碼問題
                              request.setCharacterEncoding("UTF-8");
                              response.setContentType("text/html;charset=UTF-8");
                              PrintWriter pw = response.getWriter();
                              
                              try {
                                      // 設置環境
                                      DiskFileItemFactory factory = new DiskFileItemFactory();
                                      // 設置臨時存放目錄  在webroot下創建一個temp的文件夾  查看的時候在apatch查看
                                      factory.setRepository(new File(getServletContext().getRealPath("/temp")));
                                      // 得到存放上傳文件的真實路徑
                                      String storePath = getServletContext().getRealPath("/WEB-INF/files");
                                      
                                      // 判斷一下form是否爲enctype=multipart/file-data
                                      boolean isMultipart = ServletFileUpload.isMultipartContent(request);
                                      if(!isMultipart){
                                              System.out.println("大傻鳥,快去將表單的enctype的值設置爲multipart/form-data");
                                              return;
                                      }
                                      
                                      // ServletFileUpload核心類
                                      ServletFileUpload upload = new ServletFileUpload(factory);
                                      // 進度監聽
                                      upload.setProgressListener(new ProgressListener() {
                                              // 關聯源代碼(common-fileupload.jar 顯示參數)
                                              // pBytesRead: 當前已讀取到的字節數    pContentLength: 文件的長度   pItem: 第幾項,從1開始
                                              public void update(long pBytesRead, long pContentLength,int pItems) {
                                                      System.out.println("已讀取:"+pBytesRead+",文件大小:"+pContentLength+
                                                                                      ",第幾項:"+pItems);
                                              }
                                      });
                                      
                                      // 設置單個上傳文件的大小不能超過8M
                                // upload.setFileSizeMax(8*1024*1024);
                                      // 設置上傳的總文件的大小不能超過15M
                                // upload.setSizeMax(15*1024*1024);
                                      
                                      // 解析
                                      List<FileItem> items = upload.parseRequest(request);
                                      for(FileItem item : items){
                                              if(item.isFormField()){
                                                      // 是普通字段
                                                      String fieldName = item.getFieldName();
                                                      String fieldValue = item.getString("UTF-8");  // 解決form表單請求參數的中文編碼問題
                                                      System.out.println(fieldName+"="+fieldValue);
                                              }else{
                                                
                                                      // 得到上傳文件的MIME類型
                                            // String mimeType = item.getContentType();
                                                      // 限制上傳文件的類型: 只允許爲圖片類型
                                            // if(mimeType.startsWith("image")){
                                                        
                                                              // 是上傳字段
                                                              InputStream in = item.getInputStream();
                                                              // 上傳的文件名
                                                              String fileName = item.getName();   // 注: 有的瀏覽器爲: c:\Documents and Settings\wzhting\妗岄潰\a.txt    有的爲  a.txt
                                                              // 如果上傳的文件爲null
                                                              if(fileName==null||"".equals(fileName.trim())){
                                                                      continue;
                                                              }
                                                              
                                                              fileName = fileName.substring(fileName.lastIndexOf("\\")+1);   // 得到文件名:  a.txt
                                                              fileName = UUID.randomUUID()+"_"+fileName;  // 保證新的文件名不重複,避免了上傳的文件的重名覆蓋問題
                                                              
                                                              // 打散存儲目錄
                                                              String newStorePath = makeStorePath(storePath,fileName);   // 根據WEB-INF/files和文件名,創建一個新的存儲路徑: /WEB-INF/files/1/13/文件名            
                                                              String storeFile = newStorePath + "\\" + fileName;   // WEN-INF/files/1/13/文件名
                                                              // 構件輸出流
                                                              OutputStream out = new FileOutputStream(storeFile);
                                                              byte[] b = new byte[1024];
                                                              int len = 0;
                                                              while((len=in.read(b))!=-1){
                                                                      out.write(b, 0, len);
                                                              }
                                                              out.close();
                                                              in.close();
                                                              // 刪除臨時文件   注:一定要在關閉流之後  臨時文件在上傳完畢後會調用該方法自動刪除
                                                              item.delete();
                                                        }
                                          // }
                                        }
                              } catch (org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException e){
                                      // 該異常爲: 單個文件超出大小時的異常     注: 異常的捕獲: 子類異常必須在父類異常的前面捕獲  
                                      pw.write("單個文件的大小不能超過8M");
                              } catch (org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException e){
                                      // 該異常爲: 上傳的總文件超出大小時的異常
                                      pw.write("上傳的總文件的大小不能超過15M");
                              }catch (FileUploadException e) {
                                      e.printStackTrace();
                              } 
                      }


                            
                    // 根據/WEN-INF/files和文件名 創建一個新的存儲路徑: /WEB-INF/files/1/13
                    private String makeStorePath(String storePath, String fileName) {
                            int hashCode = fileName.hashCode();
                            int dir1 = hashCode&0xf;        // 0000~1111  整數0~15 共16個
                            int dir2 = (hashCode&0xf0)>>4; // 0000~1111  整數0~15 共16個
                            String path = storePath+"\\"+dir1+"\\"+dir2;  // WEB-INF/files/1/13
                            File file = new File(path);
                            if(!file.exists()){
                                    file.mkdirs();
                            }
                            return path;
                      }


                      public void doPost(HttpServletRequest request, HttpServletResponse response)
                          throws ServletException, IOException {
                              doGet(request, response);
                      }


              }
       
             --------------------------------------------------------------------------------------------------         
             
             
             
             
             
             
                      
                      
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章