1. 程式人生 > 其它 >Java Web 檔案上傳

Java Web 檔案上傳

檔案上傳

一、注意事項

  1. 為保證伺服器安全,上傳檔案應當儲存在外界無法直接訪問的路徑。(如WEB-INF目錄下)
  2. 為防止檔案覆蓋,要為上傳的檔案生成一個唯一的檔名。(如-時間戳,-uuid,-md5,-位運算演算法)
  3. 要限制上傳檔案的大小的最大值。
  4. 可以限制上傳檔案的型別,在獲取上傳檔名時,判斷後綴名是否合法。

二、元件

瀏覽器處理上傳檔案,是將檔案以流的形式提交到伺服器端。

  • commons-fileupload:Apache的檔案上傳元件,取代原生的檔案上傳流。
  • commons-io:commons-fileupload元件依賴於該元件。

三、前端表單

  • form
    1. 提交方式:method=“post”
      (post傳送的資料量大,可視為不受限制)
    2. 編碼型別:enctype="multipart/form-data"(表單包含檔案上傳控制元件時必須使用)
  • input
    1. 型別:type=“file”
    2. 屬性:帶有name屬性

四、實用類介紹

PS:一個表單項即一個欄位,一個FileItem物件封裝了一個表單項

  • DiskFileItemFactory類
// 1、設定臨時資料夾
void setRepository(File repository)

// *2、設定檔案快取區大小
void setSizeThreshold(int sizeThreshold)
  • ServletFileUpload類
// 1、靜態方法:判斷表單是否包含檔案上傳控制元件,負責處理檔案資料
static boolean isMultipartContent(HttpServletRequest request)
    
// 2、父類方法:設定FileItemFactory屬性,也可通過構造方法設定
void setFileItemFactory(FileItemFactory factory)
   
// 3、解析前端請求,將每個表單項解析並封裝成FileItem物件,以List列表的形式返回。
List<FileItem> parseRequest(HttpServletRequest request)
    
// *4、父類方法:監聽檔案上傳進度
void setProgressListener(ProgressListener pListener)
    
// *5、父類方法:處理亂碼問題
void setHeaderEncoding(String encoding)
    
// *6、父類方法:設定單個檔案的最大值
void setFileSizeMax(long fileSizeMax)
    
// *7、父類方法:設定總共能夠上傳的檔案大小
void setSizeMax(long sizeMax)
  • FileItem介面實現類:DiskFileItem
// 1、判斷表單項是否為上傳檔案:普通文字返回true,否則返回false
boolean isFormField();

// 2、獲取欄位名:表單項name屬性的值
String getFieldName();

// 3、FileItem物件中儲存的資料流內容:即表單項為普通文字的輸入值
String getString();

// 4、獲取表單項為檔案上傳的檔名
String getName();

// 5、獲取上傳檔案的輸入流
InputStream getInputStream();

// 6、清空FileItem類物件中存放的主體內容,如果主體內容被儲存在臨時檔案中,delete方法將刪除該臨時檔案
void delete();

五、程式碼實現

1、匯入Maven依賴

(普通專案則匯入jar包)

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>

2、前端頁面

<h1>${msg}</h1>
<form action="${pageContext.request.contextPath}/upload.do" method="post" enctype="multipart/form-data">
    <p>上傳使用者:<input type="text" name="username"></p>
    <p><input type="file" name="file"></p>
    <p>
        <input type="submit">|<input type="reset">
    </p>
</form>

3、Servlet

思路

  1. 判斷表單是否包含檔案上傳控制元件。
  2. 建立上傳檔案和臨時檔案的儲存路徑。
  3. 建立DiskFileItemFactory物件。
    • 設定臨時資料夾
    • *設定緩衝區大小
  4. 建立ServletFileUpload物件
    • 設定FileItemFactory
    • *監聽檔案上傳進度、處理亂碼問題、設定單個檔案和總共上傳檔案的最大值
  5. 解析請求並處理檔案傳輸
    • 解析前端請求,將每個表單項解析並封裝成FileItem物件
    • 判斷表單項是否為上傳檔案
    • 處理普通文字:
      • 獲取欄位名
      • 獲取資料流內容
    • 處理檔案:
      • 獲取上傳檔名
      • 生成隨機UUID作為檔案儲存路徑,併為其生成資料夾
      • 通過IO流傳輸檔案
public class FileServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 判斷表單是否包含檔案上傳控制元件
        if (!ServletFileUpload.isMultipartContent(req)) { // 不包含檔案上傳控制元件,即普通表單
            return;
        }

        // 建立上傳檔案的儲存路徑:外界無法直接訪問
        String uploadPath = this.getServletContext().getRealPath("/WEB-INF/upload");
        File uploadFile = new File(uploadPath);
        makeDirIfNotExist(uploadFile);

        // 建立臨時檔案的儲存路徑
        String tmpPath = this.getServletContext().getRealPath("/WEB-INF/tmp");
        File tmpFile = new File(tmpPath);
        makeDirIfNotExist(tmpFile);


        // 1、建立DiskFileItemFactory物件
        DiskFileItemFactory factory = getDiskFileItemFactory(tmpFile);
        // 2、建立ServletFileUpload物件
        ServletFileUpload servletFileUpload = getServletFileUpload(factory);
        // 3、解析請求並處理檔案傳輸
        String msg = "";
        try {
            msg = uploadParseRequest(req, servletFileUpload, uploadPath);
        } catch (FileUploadException e) {
            e.printStackTrace();
        }

        req.setAttribute("msg", msg);
        req.getRequestDispatcher("/index.jsp").forward(req, resp);
    }


    private String uploadParseRequest(HttpServletRequest httpServletRequest, ServletFileUpload servletFileUpload, String uploadPath)
            throws FileUploadException, IOException {
        String msg = "";

        // 解析前端請求,將每個表單項解析並封裝成FileItem物件
        List<FileItem> fileItems = servletFileUpload.parseRequest(httpServletRequest);
        for (FileItem fileItem : fileItems) {
            // 判斷表單項是否為上傳檔案
            if (fileItem.isFormField()) { // 普通文字
                String filedName = fileItem.getFieldName(); // 獲取欄位名:表單項name屬性的值
                String value = fileItem.getString("UTF-8"); // FileItem物件中儲存的資料流內容:即表單項為普通文字的輸入值
                System.out.println(filedName + ":" + value);
            } else { // 上傳檔案
                // ------------------------1、處理檔案------------------------


                String uploadFileName = fileItem.getName();// 上傳檔名
                System.out.println("上傳的檔名:" + uploadFileName);

                if (uploadFileName == null || uploadFileName.trim().equals("")) { // 檔名為空值或空
                    continue; // 進入下一輪迴圈,判斷下一個FileItem物件
                }

                String fileExtName = uploadFileName.substring(uploadFileName.lastIndexOf(".") + 1); // 檔案字尾名
                System.out.println("檔案資訊【檔名:" + uploadFileName + ",檔案型別:" + fileExtName + "】");

                // 使用UUID保證檔名唯一
                String uuidPath = UUID.randomUUID().toString();

                // ------------------------2、處理路徑------------------------

                // 儲存路徑:uploadPath
                String realPath = uploadPath + '/' + uuidPath; // 真實存在的路徑
                // 給每個檔案建立一個資料夾
                File realPathFile = new File(realPath);
                makeDirIfNotExist(realPathFile);

                // ------------------------3、檔案傳輸------------------------

                // 獲得輸入流
                InputStream is = fileItem.getInputStream();
                // 獲得輸出流
                FileOutputStream fos = new FileOutputStream(realPathFile + "/" + uploadFileName);
                // 建立緩衝區
                byte[] buffer = new byte[1024];

                int len;
                while ((len = is.read(buffer)) > 0) {
                    fos.write(buffer, 0, len);
                }
                msg = "檔案上傳成功!";

                fileItem.delete(); // 上傳成功,清除臨時檔案

                fos.close();
                is.close();
            }
        }

        return msg;
    }


    private ServletFileUpload getServletFileUpload(DiskFileItemFactory factory) {
        // ServletFileUpload servletFileUpload = new ServletFileUpload(factory); // 構造方法設定FileItemFactory

        ServletFileUpload servletFileUpload = new ServletFileUpload();
        servletFileUpload.setFileItemFactory(factory); // 設定FileItemFactory

        // ------------------------輔助功能------------------------
        // 監聽檔案上傳進度
        servletFileUpload.setProgressListener(new ProgressListener() {
            /**
             *
             * @param pBytesRead      已讀取的檔案大小
             * @param pContentLength  檔案大小
             * @param pItems
             */
            @Override
            public void update(long pBytesRead, long pContentLength, int pItems) {
                String percentage = (int) (((double) pBytesRead / pContentLength) * 100) + "%";
                System.out.println("總大小:" + pContentLength + ",已上傳:" + pBytesRead + "\t" + percentage);
            }
        });
        // 處理亂碼問題
        servletFileUpload.setHeaderEncoding("UTF-8");
        // 設定單個上傳檔案的最大值
        servletFileUpload.setFileSizeMax(1024 * 1024 * 10); // 10M
        // 設定總共上傳檔案的最大值
        servletFileUpload.setSizeMax(1024 * 1024 * 10); // 10M

        return servletFileUpload;
    }

    /**
     * 獲得磁碟檔案專案工程,設定緩衝資料夾及緩衝區大小
     *
     * @param tmpFile 緩衝資料夾
     * @return
     */
    private DiskFileItemFactory getDiskFileItemFactory(File tmpFile) {
        // return new DiskFileItemFactory(1024 * 1024, tmpFile);

        DiskFileItemFactory factory = new DiskFileItemFactory();

        // ------------------------輔助功能------------------------
        factory.setSizeThreshold(1024 * 1024); // 1M(緩衝區大小):上傳檔案大於緩衝區大小時,fileupload元件將使用臨時檔案快取上傳檔案
        factory.setRepository(tmpFile); // 臨時資料夾
        return factory;
    }

    /**
     * 如果檔案目錄不存在,為其建立目錄
     *
     * @param file
     */
    private void makeDirIfNotExist(File file) {
        if (!file.exists()) {
            file.mkdir();
        }
    }
}

4、註冊Servlet

<servlet>
    <servlet-name>FileServlet</servlet-name>
    <servlet-class>indi.jaywee.file.FileServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>FileServlet</servlet-name>
    <url-pattern>/upload.do</url-pattern>
</servlet-mapping>

5、執行結果

上傳的檔案會儲存在Target目錄下對應專案的儲存路徑下。

  • 非臨時檔案:

    • 儲存在upload資料夾下。
    • 檔案格式和檔名與上傳檔案完全相同。
    • 每個檔案儲存在一個子資料夾中,資料夾名是隨機生成的UUID。
  • 臨時檔案:

    • 儲存在upload資料夾下,同時在tmp資料夾下生成一個臨時檔案。
    • upload資料夾下的檔案:檔案格式和檔名與上傳檔案完全相同。
    • tmp資料夾下的檔案:tmp格式,檔名是隨機生成的UUID,在Servlet執行到fileItem.delete()方法時被清除。