文件上傳
一、注意事項
- 為保證服務器安全,上傳文件應當保存在外界無法直接訪問的路徑(如 WEB-INF 目錄下)
- 為防止文件覆蓋,要為上傳的文件生成一個唯一的文件名(如-時間戳,-uuid,-md5,-位運算算法)
- 要限制上傳文件的大小的最大值。
- 可以限制上傳文件的類型,在獲取上傳文件名時,判斷后綴名是否合法。
二、文件上傳
1、組件
瀏覽器處理上傳文件,是將文件以流的形式提交到服務器端。
commons-fileupload
:Apache 的文件上傳組件,取代原生的文件上傳流。commons-io
:commons-fileupload 組件依賴於該組件。
2、前端表單
form 表單
- 提交方式:
method=“post”
(post傳送的數據量大,可視為不受限制) - 編碼類型:
enctype="multipart/form-data"
(表單包含文件上傳控件時必須使用)- enctype 屬性
application/x-www=form-urlencoded
:默認方式,只處理表單域中的 value 屬性值,將表單域中的值處理成 URL 編碼方式;multipart/form-data
:以二進制流的方式處理表單數據,除了表單域中的 value 屬性值,還會處理表單域的文件內容,將其封裝到請求參數中,不會對字符編碼;text/plain
:將空格轉換為+
號,其它字符不做編碼處理,適用於通過表單發送郵件。
- enctype 屬性
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)
DiskFileItem:FileItem 接口實現類
- 判斷表單項是否上傳文件,即類型是否為
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、實現步驟
帶 * 號的是用於完善業務的輔助功能,不是必要的。
- 判斷表單:是否包含文件上傳控件。
- 創建上傳文件和臨時文件的保存路徑。
- 創建 DiskFileItemFactory 對象。
- 設置臨時文件夾
- *設置緩沖區大小
- 創建 ServletFileUpload 對象
- 設置 FileItemFactory
- *監聽文件上傳進度、處理亂碼問題、設置單個文件和總共上傳文件的最大值
- 解析請求並處理文件傳輸
- 解析前端請求,將每個表單項解析並封裝成 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() 方法時被清除。