Java Web:文件上傳


文件上傳

一、注意事項

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

二、文件上傳

1、組件

瀏覽器處理上傳文件,是將文件以的形式提交到服務器端。

  • commons-fileuploadApache 的文件上傳組件,取代原生的文件上傳流。
  • commons-iocommons-fileupload 組件依賴於該組件。

2、前端表單

form 表單

  • 提交方式method=“post”(post傳送的數據量大,可視為不受限制)
  • 編碼類型enctype="multipart/form-data"(表單包含文件上傳控件時必須使用)
    • enctype 屬性
      • application/x-www=form-urlencoded:默認方式,只處理表單域中的 value 屬性值,將表單域中的值處理成 URL 編碼方式;
      • multipart/form-data:以二進制流的方式處理表單數據,除了表單域中的 value 屬性值,還會處理表單域的文件內容,將其封裝到請求參數中,不會對字符編碼;
      • text/plain:將空格轉換為+號,其它字符不做編碼處理,適用於通過表單發送郵件。

input 輸入框

  • 類型type=“file”

  • 屬性:要求具有 name 屬性

3、實用類介紹

注:

  • 一個表單項(即一個字段)會被解析為一個 FileItem 對象;
  • 也就是說,一個 FileItem 對象封裝了一個表單項。

DiskFileItemFactory

  • 設置臨時文件夾;
  • 設置文件緩存區大小。
// 1、設置臨時文件夾
void setRepository(File repository)

// *2、設置文件緩存區大小
void setSizeThreshold(int sizeThreshold)

ServletFileUpload

  • 判斷表單是否包含文件上傳控件:即是否設置了enctype="multipart/form-data"
  • 設置 FileItemFactory 屬性;
  • 解析前端請求:將表單項解析成 FileItem 對象;
  • *監聽文件上傳進度、處理亂碼問題、設置文件上傳最大值
// 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)

DiskFileItemFileItem 接口實現類

  • 判斷表單項是否上傳文件,即類型是否為type=“file”
  • 獲取字段名;
  • 獲取數據流內容;
  • 獲取上傳文件名;
  • 獲取上傳文件輸入流;
  • 清空 FileItem 對象。
// 1、判斷表單項是否為上傳文件:普通文本返回true,否則返回false
boolean isFormField();

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

// 3、FileItem對象中保存的數據流內容:即表單項為普通文本的輸入值
String getString();

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

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

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

4、實現步驟

帶 * 號的是用於完善業務的輔助功能,不是必要的。

  1. 判斷表單:是否包含文件上傳控件。
  2. 創建上傳文件和臨時文件的保存路徑
  3. 創建 DiskFileItemFactory 對象。
    • 設置臨時文件夾
    • *設置緩沖區大小
  4. 創建 ServletFileUpload 對象
    • 設置 FileItemFactory
    • *監聽文件上傳進度、處理亂碼問題、設置單個文件和總共上傳文件的最大值
  5. 解析請求並處理文件傳輸
    • 解析前端請求,將每個表單項解析並封裝成 FileItem 對象
    • 判斷表單項是否為上傳文件
    • 處理普通文本:
      • 獲取字段名
      • 獲取數據流內容
    • 處理文件:
      • 獲取上傳文件名
      • 生成隨機 UUID 作為文件存儲路徑,並為其生成文件夾
      • 通過 IO 流傳輸文件

三、代碼實現

1、導入Maven依賴

(普通項目則導入jar包)

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

2、前端頁面

fileUpload.jsp

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

success.jsp

<h1>${msg}</h1>

3、Servlet

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("/success.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() 方法時被清除。


免責聲明!

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



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