IT兄弟連 JavaWeb教程 文件上傳技術

  • 2019 年 10 月 5 日
  • 筆記

版權聲明:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。

本文鏈接:https://blog.csdn.net/ITXDL123/article/details/90746120

在Web應用系統開發中,文件上傳和下載功能是非常常用的功能。

對於文件上傳,瀏覽器在上傳的過程中是將文件以流的形式提交到服務器端的,如果直接使用Servlet獲取上傳文件的輸入流然後再解析裏面的請求參數是比較麻煩,所以一般選擇採用apache的開源工具common-fileupload這個文件上傳組件。這個common-fileupload上傳組件的jar包可以去apache官網上面下載。common-fileupload是依賴於common-io這個包的,所以還需要下載這個包。

開發環境搭建

創建一個FileUploadAndDownload項目,加入Apache的commons-fileupload文件上傳組件的相關Jar包,如圖20所示。

圖20 導入Jar包

實現文件上傳

● 編寫文件上傳頁面,upload.jsp頁面代碼如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"

pageEncoding="UTF-8"%>

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>兄弟連IT教育</title>

</head>

<body>

<form action="${pageContext.request.contextPath}/upload"

enctype="multipart/form-data" method="post">

上傳用戶:<input type="text" name="username"><br />

上傳文件1:<input type="file" name="file1"><br />

上傳文件2:<input type="file" name="file2"><br />

<input type="submit" value="提交">

</form>

</body>

</html>

● 編寫消息提示頁面,message.jsp頁面代碼如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"

pageEncoding="UTF-8"%>

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>兄弟連IT教育</title>

</head>

<body>

${message}

</body>

</html>

● 編寫處理文件上傳的Servlet

package com.xdl.servlet;

import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.util.List;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;

import org.apache.commons.fileupload.disk.DiskFileItemFactory;

import org.apache.commons.fileupload.servlet.ServletFileUpload;

public class UploadServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

public void service(HttpServletRequest request,

HttpServletResponse response) throws ServletException, IOException {

// 得到上傳文件的保存目錄,將上傳的文件存放於WEB-INF目錄下,

// 不允許外界直接訪問,保證上傳文件的安全

String savePath = this.getServletContext().

getRealPath("/WEB-INF/ upload");

File file = new File(savePath);

// 判斷上傳文件的保存目錄是否存在

if (!file.exists() && !file.isDirectory()) {

System.out.println(savePath + "目錄不存在,需要創建");

// 創建目錄

file.mkdir();

}

// 消息提示

String message = "";

try {

// 使用Apache文件上傳組件處理文件上傳步驟:

// 1、創建一個DiskFileItemFactory工廠

DiskFileItemFactory factory = new DiskFileItemFactory();

// 2、創建一個文件上傳解析器

ServletFileUpload upload = new ServletFileUpload(factory);

// 解決上傳文件名的中文亂碼

upload.setHeaderEncoding("UTF-8");

// 3、判斷提交上來的數據是否是上傳表單的數據

if (!ServletFileUpload.isMultipartContent(request)) {

// 按照傳統方式獲取數據

return;

}

// 4、使用ServletFileUpload解析器解析上傳數據,

//解析結果返回的是一個List<FileItem>集合,

//每一個FileItem對應一個Form表單的輸入項

List<FileItem> list = upload.parseRequest(request);

for (FileItem item : list) {

// 如果fileitem中封裝的是普通輸入項的數據

if (item.isFormField()) {

String name = item.getFieldName();

// 解決普通輸入項的數據的中文亂碼問題

String value = item.getString("UTF-8");

System.out.println(name + "=" + value);

} else {// 如果fileitem中封裝的是上傳文件

// 得到上傳的文件名稱,

String filename = item.getName();

System.out.println(filename);

if (filename == null || filename.trim().equals("")) {

continue;

}

// 處理獲取到的上傳文件的文件名的路徑部分,只保留文件名部分

filename = filename.substring

(filename.lastIndexOf ("\") + 1);

// 獲取item中的上傳文件的輸入流

InputStream in = item.getInputStream();

// 創建一個文件輸出流

FileOutputStream out =

new FileOutputStream(savePath + "\" + filename);

// 創建一個緩衝區

byte buffer[] = new byte[1024];

// 判斷輸入流中的數據是否已經讀完的標識

int len = 0;

// 循環將輸入流讀入到緩衝區當中,

(len=in.read(buffer))>0就表示in裏面還有數據

while ((len = in.read(buffer)) > 0) {

out.write(buffer, 0, len);

}

// 關閉輸入流

in.close();

// 關閉輸出流

out.close();

// 刪除處理文件上傳時生成的臨時文件

item.delete();

message = "文件上傳成功!";

}

}

} catch (Exception e) {

message = "文件上傳失敗!";

e.printStackTrace();

}

request.setAttribute("message", message);

request.getRequestDispatcher("/message.jsp").forward(request, response);

}

}

● 在web.xml文件中註冊UploadServlet。

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns="http://xmlns.jcp.org/xml/ns/javaee"

xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"

version="3.1">

<servlet>

<servlet-name>UploadServlet</servlet-name>

<servlet-class>com.xdl.servlet.UploadServlet</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>UploadServlet</servlet-name>

<url-pattern>/upload</url-pattern>

</servlet-mapping>

</web-app>

啟動Tomcat服務器,運行效果如圖21、圖22、圖23和圖24所示。

圖21 上傳文件

圖22 上傳文件成功

圖23 控制台中打印了上傳文件的文件名

圖24 服務器端接收到了客戶端上傳的文件

文件上傳的細節

上述的代碼雖然可以成功將文件上傳到服務器上面的指定目錄當中,但是文件上傳功能有許多需要注意的小細節問題,以下列出的幾點需要特別注意的

● 為保證服務器安全,上傳文件應該放在外界無法直接訪問的目錄下,比如放於WEB-INF目錄下。

● 為防止文件覆蓋的現象發生,要為上傳文件產生一個唯一的文件名。

● 為防止一個目錄下面出現太多文件,要使用hash算法打散存儲。

● 要限制上傳文件的最大值。

● 要限制上傳文件的類型,在收到上傳文件名時,判斷後綴名是否合法。

● 針對上述提出的5點細節問題,我們來改進一下UploadServlet,改進後的代碼如下:

package com.xdl.servlet;

import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.util.List;

import java.util.UUID;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;

import org.apache.commons.fileupload.FileUploadBase;

import org.apache.commons.fileupload.ProgressListener;

import org.apache.commons.fileupload.disk.DiskFileItemFactory;

import org.apache.commons.fileupload.servlet.ServletFileUpload;

public class UploadServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

public void service(HttpServletRequest request,

HttpServletResponse response) throws ServletException, IOException {

// 得到上傳文件的保存目錄,將上傳的文件存放於WEB-INF目錄下,

// 不允許外界直接訪問,保證上傳文件的安全

String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");

// 上傳時生成的臨時文件保存目錄

String tempPath = this.getServletContext().getRealPath("/WEB-INF/temp");

File tmpFile = new File(tempPath);

if (!tmpFile.exists()) {

// 創建臨時目錄

tmpFile.mkdir();

}

// 消息提示

String message = "";

try {

// 使用Apache文件上傳組件處理文件上傳步驟:

// 1、創建一個DiskFileItemFactory工廠

DiskFileItemFactory factory = new DiskFileItemFactory();

// 設置工廠的緩衝區的大小,當上傳的文件大小超過緩衝區的大小時,

//就會生成一個臨時文件存放到指定的臨時目錄當中。

factory.setSizeThreshold(1024 * 100);

// 設置緩衝區的大小為100KB,如果不指定,那麼緩衝區的大小默認是10KB

// 設置上傳時生成的臨時文件的保存目錄

factory.setRepository(tmpFile);

// 2、創建一個文件上傳解析器

ServletFileUpload upload = new ServletFileUpload(factory);

// 監聽文件上傳進度

upload.setProgressListener(new ProgressListener() {

public void update(long pBytesRead, long pContentLength,

int arg2) {

System.out.println("文件大小為:" + pContentLength

+ ",當前已處理:" + pBytesRead);

}

});

// 解決上傳文件名的中文亂碼

upload.setHeaderEncoding("UTF-8");

// 3、判斷提交上來的數據是否是上傳表單的數據

if (!ServletFileUpload.isMultipartContent(request)) {

// 按照傳統方式獲取數據

return;

}

// 設置上傳單個文件的大小的最大值,目前是設置為1024*1024位元組,也就是1MB

upload.setFileSizeMax(1024 * 1024);

// 設置上傳文件總量的最大值

upload.setSizeMax(1024 * 1024 * 10);

// 4、使用ServletFileUpload解析器解析上傳數據,解析結果返回的是一個

// List<FileItem>集合,每一個FileItem對應一個Form表單的輸入項

List<FileItem> list = upload.parseRequest(request);

for (FileItem item : list) {

// 如果fileitem中封裝的是普通輸入項的數據

if (item.isFormField()) {

String name = item.getFieldName();

// 解決普通輸入項的數據的中文亂碼問題

String value = item.getString("UTF-8");

System.out.println(name + "=" + value);

} else {// 如果fileitem中封裝的是上傳文件

// 得到上傳的文件名稱

String filename = item.getName();

System.out.println(filename);

if (filename == null || filename.trim().equals("")) {

continue;

}

// 處理獲取到的上傳文件的文件名的路徑部分,只保留文件名部分

filename = filename.substring(

filename.lastIndexOf("\") + 1);

// 得到上傳文件的擴展名

String fileExtName =

filename.substring(filename.

lastIndexOf(".") + 1);

// 如果需要限制上傳的文件類型,那麼可以通過文件的擴展名來判斷上

// 傳的文件類型是否合法

System.out.println("上傳的文件的擴展名是:" + fileExtName);

// 獲取item中的上傳文件的輸入流

InputStream in = item.getInputStream();

// 得到文件保存的名稱

String saveFilename = makeFileName(filename);

// 得到文件的保存目錄

String realSavePath = makePath(saveFilename, savePath);

// 創建一個文件輸出流

FileOutputStream out = new FileOutputStream

(realSavePath + "\" + saveFilename);

// 創建一個緩衝區

byte buffer[] = new byte[1024];

// 判斷輸入流中的數據是否已經讀完的標識

int len = 0;

// 循環將輸入流讀入到緩衝區當中

while ((len = in.read(buffer)) > 0) {

out.write(buffer, 0, len);

}

// 關閉輸入流

in.close();

// 關閉輸出流

out.close();

// 刪除處理文件上傳時生成的臨時文件

// item.delete();

message = "文件上傳成功!";

}

}

} catch (FileUploadBase.FileSizeLimitExceededException e) {

e.printStackTrace();

request.setAttribute("message", "單個文件超出最大值!!!");

request.getRequestDispatcher("/message.jsp").

forward(request, response);

return;

} catch (FileUploadBase.SizeLimitExceededException e) {

e.printStackTrace();

request.setAttribute("message",

"上傳文件的總的大小超出限制的最大值!!!");

request.getRequestDispatcher("/message.jsp").

forward(request, response);

return;

} catch (Exception e) {

message = "文件上傳失敗!";

e.printStackTrace();

}

request.setAttribute("message", message);

request.getRequestDispatcher("/message.jsp").

forward(request, response);

}

private String makeFileName(String filename) {

// 為防止文件覆蓋的現象發生,要為上傳文件產生一個唯一的文件名

return UUID.randomUUID().toString() + "_" + filename;

}

/**

* 為防止一個目錄下面出現太多文件,要使用hash算法打散存儲

*/

private String makePath(String filename, String savePath) {

// 得到文件名的hashCode的值,得到的就是filename這個字符串對象在內存中的地址

int hashcode = filename.hashCode();

int dir1 = hashcode & 0xf;

int dir2 = (hashcode & 0xf0) >> 4;

// 構造新的保存目錄

String dir = savePath + "\" + dir1 + "\" + dir2;

// File既可以代表文件也可以代表目錄

File file = new File(dir);

// 如果目錄不存在

if (!file.exists()) {

// 創建目錄

file.mkdirs();

}

return dir;

}

}

針對上述提出的5點小細節問題進行改進之後,我們的文件上傳功能就算是做得比較完善了。