天天看點

120_檔案上傳

目錄

  • 準備工作
  • 使用類介紹
    • 檔案上傳注意事項
    • 需要用到的類詳解
      • ServletFileUpload類
      • FileItem類
    • 常用方法介紹
  • 編寫代碼
    • maven庫下載下傳依賴包并引入
    • 配置Tomcat
    • 在gitee建立遠端倉庫并關聯
    • 編寫表單
    • 編寫檔案上傳Servlet,web.xml中注冊
    • 編寫轉發頁面

對于檔案上傳,浏覽器在上傳的過程中是将檔案以流的形式送出到伺服器端的。

一般采用Apache的開源工具common-fileupload這個檔案上傳元件。

common-fileupload是依賴于common-io這個包的,是以還需要下載下傳這個包。

  1. 為保證伺服器的安全,上傳的檔案應放在外界無法通路的目錄下,如WEB-INF。
  2. 為防止同名檔案産生覆寫現象,要為檔案指定一個唯一的檔案名。
  3. 要對上傳檔案的大小進行限制。
  4. 限制上傳檔案的類型,收到檔案時,判斷檔案名是否合法。

ServletFileUpload負責處理上傳的檔案資料,并将表單中的每個輸入項封裝成一個FileItem對象,在使用ServletFileUpload對象解析請求時需要DiskFileItemFactory對象。是以,我們需要在進行解析工作前構造好DiskFileItemFactory對象,通過ServletFileItem對象的構造方法或setFileItemFactory()設定ServletFileUpload對象的fileItemFactory屬性。

在HTML頁面中input必須要有name:

<input type="file" name="fileName">

上傳檔案表單中如果包含一個檔案上傳項的話,這個表單的entype屬性必須設定為

multipart/form-data

浏覽器的表單類型為

multipart/form-data

的話,伺服器想擷取資料就要通過流。

commons-fileupload-1.4.jar
commons-io-2.8.0.jar
           

出現問題提示:打包時不會打入lib目錄下的jar,點選fix處理

處理成功

建立遠端倉庫

建立本地倉庫

Administrator@L87Y12K91TH8M2R MINGW64 /d/code/fileUpAndDown
$ git init
Initialized empty Git repository in D:/code/fileUpAndDown/.git/

Administrator@L87Y12K91TH8M2R MINGW64 /d/code/fileUpAndDown (main)
$ ll -a
total 24
drwxr-xr-x 1 Administrator 197121   0 Jun  2 23:25 ./
drwxr-xr-x 1 Administrator 197121   0 May 27 08:48 ../
drwxr-xr-x 1 Administrator 197121   0 Jun  2 23:25 .git/
drwxr-xr-x 1 Administrator 197121   0 Jun  2 23:26 .idea/
-rw-r--r-- 1 Administrator 197121 968 May 15 00:03 fileUpAndDown.iml
drwxr-xr-x 1 Administrator 197121   0 May 15 00:01 lib/
drwxr-xr-x 1 Administrator 197121   0 May 24 23:33 out/
drwxr-xr-x 1 Administrator 197121   0 May 14 23:56 src/
drwxr-xr-x 1 Administrator 197121   0 May 15 00:04 web/

Administrator@L87Y12K91TH8M2R MINGW64 /d/code/fileUpAndDown (main)

           

關聯遠端倉庫,并拉取遠端倉庫檔案

Administrator@L87Y12K91TH8M2R MINGW64 /d/code/fileUpAndDown (main)
$ git remote add origin [email protected]:wl3pbzhyq/fileUpAndDown.git

Administrator@L87Y12K91TH8M2R MINGW64 /d/code/fileUpAndDown (main)
$ git pull origin master
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (5/5), 1.42 KiB | 7.00 KiB/s, done.
From gitee.com:wl3pbzhyq/fileUpAndDown
 * branch            master     -> FETCH_HEAD
 * [new branch]      master     -> origin/master

Administrator@L87Y12K91TH8M2R MINGW64 /d/code/fileUpAndDown (main)

           

配置忽略檔案

# target
target/

# idea
.idea/
*.iml
           

送出main分支到遠端倉庫

D:\code\fileUpAndDown>git add .
warning: LF will be replaced by CRLF in out/artifacts/fileUpAndDown_war_exploded/WEB-INF/web.xml.
The file will have its original line endings in your working directory
warning: LF will be replaced by CRLF in out/artifacts/fileUpAndDown_war_exploded/index.jsp.
The file will have its original line endings in your working directory
warning: LF will be replaced by CRLF in web/WEB-INF/web.xml.
The file will have its original line endings in your working directory
warning: LF will be replaced by CRLF in web/index.jsp.
The file will have its original line endings in your working directory

D:\code\fileUpAndDown>git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   .gitignore
        new file:   .idea/vcs.xml
        new file:   lib/commons-fileupload-1.4.jar
        new file:   lib/commons-io-2.8.0.jar

D:\code\fileUpAndDown>git commit -m "初始化"
[main 0680e1f] 初始化
 31 files changed, 105 insertions(+)
 create mode 100644 .idea/vcs.xml
 create mode 100644 lib/commons-fileupload-1.4.jar
 create mode 100644 lib/commons-io-2.8.0.jar

D:\code\fileUpAndDown>git push --set-upstream origin main
Enumerating objects: 38, done.
Counting objects: 100% (38/38), done.
Delta compression using up to 4 threads
Compressing objects: 100% (27/27), done.
Writing objects: 100% (36/36), 662.53 KiB | 5.22 MiB/s, done.
Total 36 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Powered by GITEE.COM [GNK-5.0]
remote: Create a pull request for 'main' on Gitee by visiting:
remote:     https://gitee.com/wl3pbzhyq/fileUpAndDown/pull/new/wl3pbzhyq:main...wl3pbzhyq:master
To gitee.com:wl3pbzhyq/fileUpAndDown.git
 * [new branch]      main -> main
Branch 'main' set up to track remote branch 'main' from 'origin'.

D:\code\fileUpAndDown>
           

input必須要有name:

<input type="file" name="fileName">

表單中如果包含檔案上傳項的話,這個表單的entype屬性必須設定為

multipart/form-data

multipart/form-data

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>

  <%--
  通過表單上傳檔案
  get:上傳檔案大小有限制
  post:上傳檔案大小沒有限制
  表單中如果包含檔案上傳項的話,這個表單的entype屬性必須設定為multipart/form-data
  浏覽器的表單類型為multipart/form-data的話,伺服器想擷取資料就要通過流
  --%>
  <%--${pageContext.request.contextPath}擷取伺服器路徑--%>
  <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="file1"></p>
    <p><input type="file" name="file2"></p>
    <p><input type="submit"> | <input type="reset"></p>
  </form>

  </body>
</html>

           

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>FileUploadServlet</servlet-name>
        <servlet-class>com.qing.servlet.FileUploadServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>FileUploadServlet</servlet-name>
        <url-pattern>/upload.do</url-pattern>
    </servlet-mapping>
</web-app>
           
package com.qing.servlet;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;

public class FileUploadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
        // 判斷上傳的表單是普通表單還是帶檔案的表單
        if (! ServletFileUpload.isMultipartContent(request)) {
            return; // 終止方法運作,這是一個普通表單
        }

        // 建立上傳檔案的儲存目錄,建議在WEB-INF路徑下,安全,使用者無法直接通路上傳的檔案
        String uploadPath = this.getServletContext().getRealPath("/WEB-INF/upload");
        File uploadDir = new File(uploadPath);
        if (! uploadDir.exists()) {
            uploadDir.mkdir(); // 建立檔案上傳目錄
        }

        // 緩存,臨時檔案
        // 臨時路徑,假如檔案超過了預期的大小,我們就把它放到一個臨時目錄中,過幾天自動删除或者提醒使用者轉存為永久
        String tmpPath = this.getServletContext().getRealPath("/WEB-INF/tmp");
        File tmpDir = new File(tmpPath);
        if (! tmpDir.exists()) {
            tmpDir.mkdir(); // 建立臨時目錄
        }

        /*
        處理上傳的檔案,一般都需要通過流來擷取,可以使用request.getInputStream(),原生态的檔案上傳流擷取,十分麻煩
        建議使用Apache的檔案上傳元件來實作,common-fileupload,它需要依賴common-io元件
         */
        // 1.建立DiskFileItemFactory對象,負責處理檔案上傳路徑或者大小限制
        DiskFileItemFactory factory = getDiskFileItemFactory(tmpDir);

        // 2.擷取ServletFileUpload
        ServletFileUpload upload = getServletFileUpload(factory);

        // 3.處理上傳的檔案
        try {
            String msg = uploadParseRequest(request, uploadPath, upload);

            // Servlet請求轉發消息
            request.setAttribute("msg",msg);
            request.getRequestDispatcher("info.jsp").forward(request,response);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (FileUploadException e) {
            e.printStackTrace();
        } catch (ServletException e) {
            e.printStackTrace();
        }

    }

    /**
     * 處理上傳的檔案
     * @param request
     * @param uploadPath
     * @param upload
     * @return
     * @throws IOException
     */
    private String uploadParseRequest(HttpServletRequest request, String uploadPath, ServletFileUpload upload) throws IOException, FileUploadException {
        StringBuilder msg = new StringBuilder();
        // 把前端請求解析,封裝成一個FileItem對象(表單對象)
        List<FileItem> fileItems = upload.parseRequest(request);
        for (FileItem fileItem : fileItems) {
            // 判斷表單是普通表單還是檔案表單,普通表單傳回true,檔案表單傳回false
            if (fileItem.isFormField()) {
                String name = fileItem.getFieldName();
                String vlaue = fileItem.getString("UTF-8");// 處理亂碼
                System.out.println(name + ":" + vlaue);
            } else {
                System.out.println("=================處理檔案==============================");
                String name = fileItem.getFieldName();
                String fileName = fileItem.getName();
                System.out.println(name + ":" + fileName);
                // 過濾檔案名不合法的檔案
                if (fileName == null || "".equals(fileName.trim())) {
                    continue;
                }
                // 擷取檔案字尾名,如果檔案字尾名不是我們需要的檔案類型,就直接return,告訴使用者檔案類型錯誤
                String fileExtName = fileName.substring(fileName.lastIndexOf(".") + 1);
                System.out.println("=================檔案存放位址==============================");
                // 生成日期目錄
                String datePath = LocalDate.now().toString();
                // 生成唯一目錄
                String uuidPath = UUID.randomUUID().toString();
                // 組成檔案實際路徑
                String realPath = uploadPath + "/" + datePath + "/" + uuidPath;
                File realDir = new File(realPath);
                if (! realDir.exists()) {
                    // 注:建立多層目錄使用mkdirs,mkdir隻能建立一層目錄
                    realDir.mkdirs();// 建立檔案實際路徑
                }
                System.out.println("=================檔案傳輸==============================");
                // 擷取檔案上傳輸入流
                InputStream inputStream = fileItem.getInputStream();
                // 建立檔案輸出流
                FileOutputStream fos = new FileOutputStream(realPath + "/" + fileName);
                // 建立緩沖區
                byte[] buffer = new byte[1024 * 1024];
                // 判斷是否讀取完畢
                int len = 0;
                // 如果大于0,說明還存在資料
                while ((len = inputStream.read(buffer)) > 0) {
                    fos.write(buffer,0,len);
                }
                // 關閉流
                fos.close();
                inputStream.close();

                msg.append(fileName).append("檔案上傳成功!");
                fileItem.delete(); // 上傳成功,清除臨時檔案
            }
        }
        return msg.toString();
    }

    /**
     * 擷取ServletFileUpload
     * @param factory
     * @return
     */
    private ServletFileUpload getServletFileUpload(DiskFileItemFactory factory) {
        ServletFileUpload upload = new ServletFileUpload(factory);

        // 監聽檔案上傳進度
        upload.setProgressListener(new ProgressListener() {
            @Override
            public void update(long read, long total, int i) {
                // read 已讀取的檔案大小
                // total 檔案總大小
                System.out.println("總大小:" + total + ",已上傳:" + read);
            }
        });

        // 處理亂碼問題
        upload.setHeaderEncoding("UTF-8");
        // 設定單個檔案的最大值
        upload.setFileSizeMax(1024 * 1024 * 10); // 10M
        // 設定總共能夠上傳檔案的大小
        upload.setSizeMax(1024 * 1024 * 1000); // 1000M
        return upload;
    }

    /**
     * 建立DiskFileItemFactory對象,負責處理檔案上傳路徑或者大小限制
     * @param tmpDir
     * @return
     */
    private DiskFileItemFactory getDiskFileItemFactory(File tmpDir) {
        DiskFileItemFactory factory = new DiskFileItemFactory();
        // 通過工廠設定一個緩沖區,當上傳的檔案大于緩沖區的時候,将它放到臨時目錄
        factory.setSizeThreshold(1024 * 1024); // 緩沖區大小為1M
        factory.setRepository(tmpDir); // 設定臨時目錄
        return factory;
    }
}

           

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>

  ${msg}

  </body>
</html>