文件的上傳
- 文件上傳的條件和原理
文件上傳頁面書寫必須符合以下條件:
- Form表單的請(qǐng)求方式必須為post
- Form表單中提供type="file"類型的上傳輸入框
- Form表單enctype屬性必須是multipart/form-data,enctype默認(rèn)值為application/x-www-urlencoded
文件上傳原理:
使用request.getInputStream();通過流來取得用戶上傳的數(shù)據(jù),關(guān)鍵問題是流的解析

圖1

圖2
2、借助三方組件實(shí)現(xiàn)文件上傳
使用Apache提供的commons-fileupload組件進(jìn)行上傳功能開發(fā)(內(nèi)部核心封裝的為文件流解析方法)
commons-io為fileupload組件在1.1開始就依賴的jar包,用于輔助文件的io操作
示例代碼:
package com.gaojinze.web.upload.servlet;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
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.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
/**
* 使用第三方組件進(jìn)行上傳
* apache的
* commons-fileupload.jar:核心內(nèi)容解析請(qǐng)求正文實(shí)現(xiàn)上傳
* commons-io.jar:是apache對(duì)jdk對(duì)java.io.*對(duì)擴(kuò)展和增強(qiáng),fileupload從1.1就依賴該包
* @author gaopengfei
*
*/
public class UploadServlet3 extends HttpServlet {
private static final long serialVersionUID = 1L;
public UploadServlet3() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
//檢驗(yàn)表單enctype屬性
boolean isMultipartContent = ServletFileUpload.isMultipartContent(request);
if (!isMultipartContent) {
throw new RuntimeException("Please check your form enctype attribute,the value msut be multipart/form-data");
}
//創(chuàng)建核心解析器實(shí)例
DiskFileItemFactory factory = new DiskFileItemFactory();//創(chuàng)建工廠實(shí)例,該工廠可以創(chuàng)建FileItem對(duì)象
ServletFileUpload upload = new ServletFileUpload(factory);//利用工廠創(chuàng)建解析器實(shí)例
upload.setFileSizeMax(4 * 1024 * 1024);//設(shè)置單個(gè)文件的上傳大小為4M
upload.setSizeMax(8 * 1024 * 1024);//設(shè)置總上傳文件的大小為8M
List<FileItem> fileItems = new ArrayList<>();
try {
fileItems = upload.parseRequest(request);
} catch (FileUploadBase.FileSizeLimitExceededException e) {
response.getWriter().write("單個(gè)文件上傳大小不得超過4M");
}catch (FileUploadBase.SizeLimitExceededException e) {
response.getWriter().write("多文件上傳總大小不得超過8M");
}catch (FileUploadException e) {
e.printStackTrace();
}
for (FileItem fileItem : fileItems) {
if (fileItem.isFormField()) {
//非上傳字段
processFormField(fileItem);
}else {
//上傳字段
processUploadField(fileItem);
}
}
response.getWriter().write("上傳成功!");
response.setHeader("refresh", "1;url=" + request.getContextPath() + "/upload.jsp");
// response.sendRedirect(request.getContextPath() + "/upload.jsp");
}
/**
* 處理非上傳字段
* @param fileItem
*/
private void processFormField(FileItem fileItem) {
String fieldName = fileItem.getFieldName();
String fieldValue = null;
try {
fieldValue = fileItem.getString("UTF-8");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(fieldName + ":" + fieldValue);
}
/**
* 處理上傳文件
* @param fileItem
*/
private void processUploadField(FileItem fileItem) {
try {
//獲取文件名(因?yàn)闉g覽器不同導(dǎo)致在選擇完文件后的name也不同,如IE選擇后,文件名為:D:\XXX\a.txt)
String fileName = fileItem.getName();
//判斷是否有未選擇的空項(xiàng)
if ("".equals(fileName) || null == fileName) {
return;
}
//通過擴(kuò)展名來限制上傳文件類型
String extension = FilenameUtils.getExtension(fileName);
//獲取上傳文件的MIME類型
String contentType = fileItem.getContentType();
if (!extension.equalsIgnoreCase("jpg"))
return;
if (!contentType.startsWith("image")) {
return;
}
fileName = FilenameUtils.getName(fileName);
String uuidFileName = UUID.randomUUID().toString() + "_" + fileName;
//得到存放文件的真實(shí)路徑
String uploadBasePath = getServletContext().getRealPath("/WEB-INF/files");
// String childUploadDir = getChildUploadDir(uploadBasePath);
String childUploadDir = getChildUploadDir2(uploadBasePath, uuidFileName);
//保存文件,并清理緩存
fileItem.write(new File(uploadBasePath + File.separator +childUploadDir, uuidFileName));
} catch (Exception e) {
throw new RuntimeException("上傳失敗!");
}
}
/**
* 按照日期進(jìn)行子目錄創(chuàng)建
* @param uploadPath
* @return
*/
private String getChildUploadDir(String uploadBasePath) {
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String childUploadDir = dateFormat.format(date);
File childDir = new File(uploadBasePath, childUploadDir);
if (!childDir.exists()) {
//若目錄不存在則創(chuàng)建
childDir.mkdirs();
}
return childUploadDir;
}
/**
* 通過uuid的hascode分散目錄
* @param uploadBasePath
* @param uuidFileName
* @return
*/
private String getChildUploadDir2(String uploadBasePath, String uuidFileName){
int hashCode = uuidFileName.hashCode();
//作為一級(jí)目錄名
int dir1 = hashCode&0xf;
//作為二級(jí)目錄名
int dir2 = (hashCode&0xf0)>>4;
String childUploadDir = dir1 + File.separator + dir2;
File childDir = new File(uploadBasePath, childUploadDir);
if (!childDir.exists()) {
//若目錄不存在則創(chuàng)建
childDir.mkdirs();
}
return childUploadDir;
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
3、三方組件核心類介紹以及實(shí)際開發(fā)中需要注意的細(xì)節(jié)
- 如何保證服務(wù)器安全?
將文件上傳目錄放置在WEB-INF目錄下即可 - 如何避免文件重名而導(dǎo)致上傳后文件覆蓋?
創(chuàng)建唯一文件名,使用UUID.randomUUID().toString() + "_" + fileName;即可 - 如何避免同一個(gè)目錄文件過多而查找難?
解決方法:
a)以日期分類目錄
/**
* 按照日期進(jìn)行子目錄創(chuàng)建
* @param uploadPath
* @return
*/
private String getChildUploadDir(String uploadBasePath) {
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String childUploadDir = dateFormat.format(date);
File childDir = new File(uploadBasePath, childUploadDir);
if (!childDir.exists()) {
//若目錄不存在則創(chuàng)建
childDir.mkdirs();
}
return childUploadDir;
}
b)利用UUID文件名的hash碼進(jìn)行分散存儲(chǔ)
/**
* 通過uuid的hascode分散目錄
* @param uploadBasePath
* @param uuidFileName
* @return
*/
private String getChildUploadDir2(String uploadBasePath, String uuidFileName){
int hashCode = uuidFileName.hashCode();
//作為一級(jí)目錄名
int dir1 = hashCode&0xf;
//作為二級(jí)目錄名
int dir2 = (hashCode&0xf0)>>4;
String childUploadDir = dir1 + File.separator + dir2;
File childDir = new File(uploadBasePath, childUploadDir);
if (!childDir.exists()) {
//若目錄不存在則創(chuàng)建
childDir.mkdirs();
}
return childUploadDir;
}

hashcode
- 文件的限制
- 限制文件上傳大小
Web上傳不適合上傳太大的文件,一般為2M
/創(chuàng)建核心解析器實(shí)例
DiskFileItemFactory factory = new DiskFileItemFactory();//創(chuàng)建工廠實(shí)例,該工廠可以創(chuàng)建FileItem對(duì)象
ServletFileUpload upload = new ServletFileUpload(factory);//利用工廠創(chuàng)建解析器實(shí)例
upload.setFileSizeMax(4 * 1024 * 1024);//設(shè)置單個(gè)文件的上傳大小為4M
upload.setSizeMax(8 * 1024 * 1024);//設(shè)置總上傳文件的大小為8M
- 限制上傳文件的類型
通過MIME類型
操作系統(tǒng)是根據(jù)擴(kuò)展名來區(qū)分文件類型的
信息傳輸?shù)臄?shù)據(jù)通過MIME來區(qū)分類型,詳細(xì)的對(duì)應(yīng)關(guān)系,可以查看tomcat目錄下的web.xml有記錄
//通過擴(kuò)展名來限制上傳文件類型,該方式不靠譜
String extension = FilenameUtils.getExtension(fileName);
if (!extension.equalsIgnoreCase("jpg")) {
return;
}
------------------------------------------------------------
//獲取上傳文件的MIME類型
String contentType = fileItem.getContentType();
if (!extension.equalsIgnoreCase("jpg"))
return;
if (!contentType.startsWith("image")) {
return;
}
------------------------------------------------------------
//判斷是否有未選擇的空項(xiàng)
if ("".equals(fileName) || null == fileName) {
return;
}
- 上傳時(shí)的臨時(shí)文件處理
DiskFileItemFactory創(chuàng)建FileItem對(duì)象時(shí)會(huì)使用緩存,默認(rèn)10kb,若上傳文件大小超過10kb,則會(huì)采用磁盤進(jìn)行緩存(磁盤緩存即為垃圾文件
//創(chuàng)建核心解析器實(shí)例
DiskFileItemFactory factory = new DiskFileItemFactory();//創(chuàng)建工廠實(shí)例,該工廠可以創(chuàng)建FileItem對(duì)象
//設(shè)置緩存文件大小
factory.setSizeThreshold(100 * 1024);
//設(shè)置緩存文件的存放目錄,默認(rèn)是系統(tǒng)的臨時(shí)文件目錄
factory.setRepository(new File("f:/"));
若使用原生io流進(jìn)行文件讀寫存儲(chǔ),則在關(guān)閉流后使用fileItem.delete();方法進(jìn)行緩存清理
若使用fileItem.write();進(jìn)行讀寫存儲(chǔ),則無需處理,其內(nèi)部已經(jīng)進(jìn)行流緩存清理
- 中文亂碼問題
- 普通字段中文亂碼解決
String fieldValue = fileItem.getString("UTF-8"); - 中文文件名亂碼解決
request.setCharacterEncoding("UTF-8");
文件的下載
- FilelistServlet(文件列表獲?。?/li>
package com.gaojinze.web.upload.servlet.download;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 文件列表Servlet
* @author gaopengfei
*/
public class FilelistServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public FilelistServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//創(chuàng)建存儲(chǔ)key為文件在服務(wù)器中的名稱,value為用戶上傳文件名
Map<String, String> fileMap = new HashMap<>();
String fileBasePath = getServletContext().getRealPath("/WEB-INF/files");
File uploadRootDir = new File(fileBasePath);
findAllFiles(uploadRootDir, fileMap);
request.setAttribute("fileMap", fileMap);
request.getRequestDispatcher("/filelist.jsp").forward(request, response);
}
/**
* 遍歷服務(wù)器所有文件并將文件信息存儲(chǔ)到map中
* @param file
* @param fileMap
*/
private void findAllFiles(File file, Map<String, String> fileMap) {
if (file.isFile()) {
String fileUUIDName = file.getName();
String fileRealName = fileUUIDName.substring(fileUUIDName.indexOf("_") + 1);
fileMap.put(fileUUIDName, fileRealName);
}else {
//如果是目錄則遞歸進(jìn)行遍歷
File[] listFiles = file.listFiles();
if (listFiles == null || listFiles.length == 0) {
return;
}
for (File f : listFiles) {
findAllFiles(f, fileMap);
}
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
- filelist.jsp文件列表展示頁
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>文件列表</title>
</head>
<body>
<h3 align="center">文件列表</h3>
<table align="center" border="1">
<tr>
<th>文件名</th>
<th>操作</th>
</tr>
<c:forEach items="${ fileMap }" var="mk">
<!-- 以下標(biāo)簽用于構(gòu)建一個(gè)URL /Web-Upload/servlet/Download?filename=經(jīng)過url編碼的中文 -->
<c:url var="url" value="/servlet/DownloadServlet">
<c:param name="filename" value="${ mk.key }"></c:param>
</c:url>
<tr>
<td>${ mk.value }</td>
<!-- 中文名跟在URL后需要進(jìn)行URL編碼 -->
<!-- ${pageContext.request.contextPath }"/servlet/DownloadServlet?filename=${ mk.key } -->
<td><a href="${ url }">下載</a></td>
</tr>
</c:forEach>
</table>
</body>
</html>
- DownloadServlet(文件下載)
package com.gaoshiyi.web.upload.servlet.download;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 文件下載
* @author gaopengfei
*
*/
public class DownloadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public DownloadServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String uuidName = request.getParameter("filename");
String realName = uuidName.substring(uuidName.indexOf("_") + 1);
// 上傳文件跟目錄
String uploadRootPath = getServletContext().getRealPath("/WEB-INF/files");
// 獲取對(duì)應(yīng)uuidname文件的子目錄
String childDirPath = getChildDirPathByUUIDName(uploadRootPath, uuidName);
// 構(gòu)建文件讀取流
InputStream in = new FileInputStream(new File(uploadRootPath + File.separator + childDirPath, uuidName));
//處理下載時(shí)的文件名稱中文問題
String userAgent = request.getHeader("User-Agent");
if (userAgent.contains("Firefox")) {
//單獨(dú)處理火狐瀏覽器
realName = new String(realName.getBytes("UTF-8"), "ISO-8859-1");
}else {
//其他瀏覽器
realName = URLEncoder.encode(realName, "UTF-8");
}
// 告知瀏覽器以下載方式下載
response.setHeader("Content-Disposition", "attachment;filename=" + realName);
// 設(shè)置頭信息告知客戶端文件大小
response.setContentLength(in.available());
// 設(shè)置頭信息告知客戶端文件MIME類型
response.setHeader("Content-Type", "application/octet-stream");
// 輸出
OutputStream out = response.getOutputStream();
int len = 0;
byte[] buff = new byte[1024];
while ((len = in.read(buff)) != 1) {
out.write(buff, 0, len);
}
in.close();
out.close();
}
/**
* 獲取子目錄
*
* @param uploadRootPath
* @param uuidName
* @return
*/
private String getChildDirPathByUUIDName(String uploadRootPath, String uuidName) {
int hashCode = uuidName.hashCode();
int dir1 = hashCode&0xf;
int dir2 = (hashCode&0xf0) >> 4;
String cDir = dir1 + File.separator + dir2;
File childDir = new File(uploadRootPath, cDir);
if (!childDir.exists()) {
childDir.mkdirs();
}
return cDir;
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}