1. 程式人生 > 其它 >檔案上傳下載和三層架構

檔案上傳下載和三層架構

檔案上傳下載和三層架構

一 、檔案上傳

  • method 需要使用 post 提交,get 限制了資料大小。
  • enctype 需使用 multipart/form-data,不然直接報錯(需要二進位制資料)。
  • 需要提供 fifile 控制元件。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<span style="color: red">${errorMsg}</span><br/>
<form action="/fileType" method="post" enctype="multipart/form-data">
    <p>賬號:<input type="text" name="username"/></p>
    <%--input 便籤 type選擇file 即選擇了一個上傳檔案控制元件--%>
    <p>頭像:<input type="file" name="Default"/></p>
    <input type="submit" value="註冊">
</form>
</body>
</html>

注意: enctype="multipart/form-data" 提交的資料,getParameter() 無法獲取到。

二、Servlet3.0 檔案上傳

1. API

HttpServletRequest 提供了兩個方法用於從請求中解析上傳的檔案

返回值 方法 作用
Part getPart(String name) 用於獲取請求中指定 name 的檔案
Collection getParts() 獲取請求中全部的檔案

Part 中常用方法:

返回值 方法 作用
void write(String fifileName) 直接把接收到的檔案儲存到磁碟中
void getContentType() 獲取檔案的型別 MIME
String getHeader(String name) 獲取請求頭資訊
long getSize() 獲取檔案的大小

​ 要給 Servlet 貼一個標籤 @MultipartConfig 然後使用 getPart() 獲取請求中指定 name 的檔案到 Part 物件中,再使用 write 方法把檔案儲存到指定目錄就可以了

2、程式碼示例

@WebServlet("/fileUpload")
@MultipartConfig 
public class FileUploadServlet extends HttpServlet { 
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 
        // 普通控制元件資料還是使用 getParameter 方法來獲取 
        System.out.println("username:" + req.getParameter("username")); 
        // 檔案控制元件資料獲取 
        Part part = req.getPart("headImg"); 
        // 儲存到磁碟上 
        part.write("D:/headImg.png"); 
    }
}

三、檔案上傳細節

1. 獲取上傳檔名

​ 以前是拷貝檔案(自傳自存),知道檔案型別;現在是使用者傳,程式接收,接收到檔案時,涉及儲存到磁碟使用什麼檔名以及檔案型別的問題,所有需要先獲取到檔名及檔案型別。可使用 Part API 獲取:

返回值 方法 作用
String getHeader("contentdisposition") Tocmat 8.0 之前使用通過請求頭獲取檔名,需擷取字串
String getSubmittedFileName() Tomcat8.0 之後提供的直接獲取檔名方式

2. 檔名相同覆蓋現有檔案

​ 若上傳得檔名相同會導致覆蓋伺服器之前已上傳的的檔案,咱們的解決方法就是自己給檔案起一個唯一的名稱,確保不被覆蓋,這裡我們使用的是 UUID。

// 檔案控制元件資料獲取
Part part = req.getPart("headImg");
// 獲取上傳檔名 
String realFileName = part.getSubmittedFileName(); 
// 獲取上傳副檔名 
String ext = realFileName.substring(realFileName.lastIndexOf(".")); 
// 生成唯一字串拼接檔名
String fileName = UUID.randomUUID().toString() + ext; 
// 儲存到磁碟上 
part.write("D:/" + fileName);

3. 檔案儲存位置問題

​ 檔案在磁碟某個位置,不在專案下,無法使用 HTTP 協議訪問,所以要把使用者上傳的檔案存放到專案中才可通過 HTTP 協議來訪問,且儲存的位置路徑不可以寫絕對路徑,那麼怎麼辦?可以通過 ServletContext 物件的 getRealPath("專案中儲存上傳檔案的資料夾的相對路徑") 來獲取其的絕對路徑。

​ 在專案的 web 目錄下新建一個名為 upload 資料夾,修改 UploadServlet.java 的程式碼如下:

@WebServlet("/fileUpload")
@MultipartConfig 
public class FileUploadServlet extends HttpServlet {
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 
        // 檔案控制元件資料獲取 
        Part part = req.getPart("headImg"); 
        // 獲取上傳檔名 
        String realFileName = part.getSubmittedFileName(); 
        // 獲取上傳副檔名
        String ext = realFileName.substring(realFileName.lastIndexOf("."));
        // 生成唯一字串拼接檔名 
        String fileName = UUID.randomUUID().toString() + ext; 
        // 獲取專案下的 upload 目錄的絕對路徑,拼接成檔案的儲存路徑
        String realPath = getServletContext().getRealPath("/upload") +"/"+ fileName;
        // 儲存到磁碟上 
        part.write(realPath); 
    }
}

3.1 無法獲取專案下的 upload 目錄

以上方式沒什麼效果,原因是 IDEA 工具使用 打包 web 專案(war) 的方式來部署,所以位置有偏 差,需要還原 Web 專案的原本目錄結構,以及調整部署方式。調整步驟如下:

  • WEB-INF 下 建立 classes 目錄,用於存放 class 檔案

  • 修改專案 class 檔案輸出位置到 /WEB-INF/class 中(File -> Project Structure)

  • 調整專案部署方式為 External Source

    以上操作之後可以獲取到專案下的 upload 目錄了,但是之前的重新部署無效了,往後可使用編譯類的 方式來代替重新部署。


4. 檔案型別約束問題

UploadServlet.java

@WebServlet("/fileUpload") 
@MultipartConfig 
public class FileUploadServlet extends HttpServlet {
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 
        // 檔案控制元件資料獲取 
        Part part = req.getPart("headImg"); 
        // 判斷上傳的檔案型別合法不 
        if(!part.getContentType().startsWith("img/")) {
            req.setAttribute("errorMsg", "請上傳圖片"); 
            req.getRequestDispatcher("/register.jsp").forward(req, resp); return; 
        }
        String realFileName = part.getSubmittedFileName(); 
        // 獲取副檔名
        String ext = realFileName.substring(realFileName.lastIndexOf(".")); 
        // 生成唯一字串拼接檔名 
        String fileName = UUID.randomUUID().toString() + ext; 
        // 獲取專案下的 upload 目錄的絕對路徑,拼接成檔案的儲存路徑
        String realPath = getServletContext().getRealPath("/upload") +"/"+ fileName;
        // 儲存到磁碟上 
        part.write(realPath); 
    }
}

register.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html> 
    <head>
        <title>註冊</title> 
    </head>
    <body>
        <span style="color: red">${errorMsg}</span><br/> 
        <form action="/fileUpload" method="post" enctype="multipart/form-data"> 
            <p>賬號:<input type="text" name="username"/></p> 
            <p>頭像:<input type="file" name="headImg"/></p> 
            <input type="submit" value="註冊"> 
        </form>
    </body>
</html>

5. 檔案大小約束問題

檔案上傳限制大小可提高伺服器硬碟的使用率,防止使用者惡意上傳檔案造成伺服器磁碟資源緊張。可以 通過設定 @MutipartConfifig 的屬性做限制,其屬性如下:

  • maxFileSize
    :單個上傳檔案大小限制,單位:bytes
  • maxRequestSize:顯示請求中資料的大小,單位:bytes

四、檔案下載

將伺服器中的資源下載儲存到使用者電腦中。

1. 檔案下載的簡單實現

在 web 下新建 download 目錄,裡面提供兩個資源 dog.rar 和 貓.rar

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>下載</title> 
    </head> 
    <body>
        <h3>檔案下載</h3>
        <a href="/download/dog.rar">dog.rar</a><br/> 
        <a href="/download/貓.rar">貓.rar</a><br/>
    </body> 
</html>

2. 檔案下載限制

​ 下載功能已經實現,但是檔案放在 WEB-INF 外面不安全,使用者只需要拿到下載的超連結都能夠下載, 實際開發中,我們的檔案通常需要使用者有一定的許可權才能下載,所以檔案應該放在 WEB-INF 下,這樣 的話,使用者就不可以直接訪問到了,須請求到交由 Servlet 來處理,我們就可以在其 service 方法中編 寫下載限制操作。

2.1 移動下載資源WEB-INF下

2.2 修改 download.jsp 修改下載資源的連結地址 bar

<%@ page contentType="text/html;charset=UTF-8" language="java" %> 
<html> 
    <head>
        <title>下載</title>
    </head> 
    <body>
        <h3>檔案下載</h3> 
        <a href="/download?fileName=dog.rar">dog.rar</a><br/> 
        <a href="/download?fileName=貓.rar">貓.rar</a><br/>
    </body>
</html>

2.3 編寫 DownloadServlet.java

根據請求傳遞檔名引數獲取對應檔案響應給瀏覽器。

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //獲取使用者想要下載的檔名稱
        String fileName = req.getParameter("fileName");
        //獲取瀏覽器型別
        String header = req.getHeader("User-Agent");
        //根據瀏覽器型別設定下載檔名
        //三目運演算法
        String mozilla = header.contains("Mozilla") ? URLEncoder.encode(fileName, "UTF-8") : new String(fileName.getBytes("UTF-8"), "ISO-8859-1");
        //設定下載檔名
        resp.setHeader("Content-Disposition", "attachment;filename=" + mozilla);

        //獲取檔案所在跟路徑
        String realPath = req.getServletContext().getRealPath("/WEB-INF/download/");
        //使用工具類File的copy方法獲取檔案輸入流,響應會瀏覽器
        //Files.copy(原始檔,輸出)
        // Path path = Paths.get("C:/", "Xmp");Path用於來表示檔案路徑和檔案。可以有多種方法來構造一個Path物件來表示一個檔案路徑,或者一個檔案:
        //resp.getOutputStream()響應一個輸出流,作用下載來用
        Files.copy(Paths.get(realPath, fileName), resp.getOutputStream());


    }

3. 下載檔名稱問題(能使用即可)

預設情況下,Tomcat 伺服器未告知瀏覽器檔案的名稱,所以需要手動設定響應頭來告知瀏覽器檔名稱,方法如下:

// 無需記,知道需要設定即可 // 給瀏覽器一個推薦名稱 
resp.setHeader("Content-Disposition", "attachment;filename=檔名稱");

處理中檔名稱的問題

  • IE 使用 URL 編碼方式:URLEncoder.encode(fifileName, "UTF-8")
  • 非 IE使用 ISO-8859-1 編碼:new String (fifileName.getBytes("UTF-8"), "ISO-8859-1")

具體程式碼見上面一份

三層架構

1.三層架構介紹

​ Web 開發中的最佳實踐:分層開發模式(技術層面的"分而治之")。三層架構(3-tier architecture):通 常意義上的三層架構就是將整個業務應用劃分為:表現層、業務邏輯層、資料訪問層。區分層次的目的 即為了“高內聚低耦合”的思想。在軟體體系架構設計中,分層式結構是最常見,也是最重要的一種結

構。

  • 表現層(Predentation Layer):MVC,負責處理與介面互動的相關操作。
  • 業務層(Business Layer):Service,負責複雜的業務邏輯計算和判斷。
  • 持久層(Persistent Layer):DAO,負責將業務邏輯資料進行持久化儲存


2. 業務層命名規範

  • 包名:
    • 公司域名倒寫.模組名.service:存放業務介面程式碼。
    • 公司域名倒寫.模組名.service.impl:存放業務層介面的實現類。
  • 類名:
    • IXxxService:業務層介面,Xxx 表示對應模型,比如操作 User 的就起名為 IUserService。
    • XxxServiceImpl:業務層介面對應的實現類,比如操作 User 的就起名為 UserServiceImpl。
    • XxxServiceTest:業務層實現的測試類。