一、文件上傳
先創建工程,寫一個簡易的JSP:
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>文件上傳</h3>
<form action="" method="POST">
賬號:<input type="text" name="username"/><br/>
郵箱:<input type="text" name="email"/><br/>
頭像:<input type="file" name="pic" accept="image/*"/><br/>
<input type="submit" value=" 註冊 "/><br/>
</form>
</body>
</html>
在上傳的頭像那裏,類型改爲 file ,後面的 accept 可以用來更改選擇文件的類型(爲圖片類型),效果如下:
要點:
- 上傳控件所在的表單method必須爲 POST,因爲GET方式傳入的數據大小不能超過2KB,而POST大小沒有限制。
- 上傳控件的類型爲 file.
使用一個Servlet來接收參數,並打印表單中的內容,如下:
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 3067915922682828536L;
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
System.out.println(req.getParameter("username"));
System.out.println(req.getParameter("email"));
System.out.println(req.getParameter("pic"));
}
}
顯然這裏的圖片是字符串。
因此表單的編碼必須是二進制編碼
因此我們需要用到表單中的 enctype 屬性中的 multipart/form-data
再提交一遍:
全部爲 NULL 了。
這是因爲在使用二進制編碼 multipart/form-data 後,在Servlet中再也不能通過request.getParameter 方法來獲得參數了,設置編碼都沒有效果了。
沒有設置二進制編碼時,POST爲:
在設置了二進制編碼後的請求頭:
紅色框框裏的就是圖片的二維碼信息。
我們打印一下:
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 3067915922682828536L;
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Scanner sc = new Scanner(req.getInputStream());
while (sc.hasNextLine()) {
System.out.println(sc.nextLine());
}
}
}
基於 Apache FileUpload 組件
Apache commons-fileupload 的jar包下載
Apache commons-io 的jar包下載
我們按照官方文檔的提示來進行文件上傳操作:
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 3067915922682828536L;
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//檢查是否有文件上傳請求:請求方法爲POST,請求編碼爲multipart/form-data
boolean isMultipart = ServletFileUpload.isMultipartContent(req);
if (!isMultipart) {
return;
}
try {
//1.創建FileItemFactory對象
//FileItemFactory是用來創建FileItem對象的
//FileItem對象相當於form表單中的表單控件的封裝
DiskFileItemFactory factory = new DiskFileItemFactory();
//2.創建一個新的文件上傳處理程序(文件上傳處理器)
ServletFileUpload upload = new ServletFileUpload(factory);
//3.解析請求
List<FileItem> items = upload.parseRequest(req);
System.out.println(items);
} catch (FileUploadException e) {
e.printStackTrace();
}
}
}
運行發現報錯:找不到類:
缺少IO包,添加之後,運行結果如下:
我們參照文檔,將表單裏面的內容提取並將文件內容寫到磁盤中的文件裏:
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 3067915922682828536L;
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//檢查是否有文件上傳請求:請求方法爲POST,請求編碼爲multipart/form-data
boolean isMultipart = ServletFileUpload.isMultipartContent(req);
if (!isMultipart) {
return;
}
try {
//1.創建FileItemFactory對象
//FileItemFactory是用來創建FileItem對象的
//FileItem對象相當於form表單中的表單控件的封裝
DiskFileItemFactory factory = new DiskFileItemFactory();
//2.創建一個新的文件上傳處理程序(文件上傳處理器)
ServletFileUpload upload = new ServletFileUpload(factory);
//3.解析請求
List<FileItem> items = upload.parseRequest(req);
for (int i = 0; i < items.size(); i++) {
System.out.println(items.get(i));
}
//4.迭代出每一個FileItem
for (FileItem item : items) {
//獲取表單屬性名稱
String fieldName = item.getFieldName();
if (item.isFormField()) {
//普通表單控件
String value = item.getString("UTF-8");//獲取當前普通表單控件的參數值
System.out.println(fieldName + "-" + value);
} else {
//上傳文件控件
System.out.println(fieldName + "-" + item.getName());//字段名-上傳的文件名
item.write(new File("E:/", item.getName()));//把二進制文件寫到哪個文件中
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
最終結果如下:
文件上傳的文件名處理
文件名
在 IE6 中,通過 FileItem.getName 方法獲取到的文件名會帶有完整路徑,因此要進行處理。
我們可以用 FilenameUtils.getName(item.getName())
進行轉化
解釋如下:
public class FileNameUtilsTest {
public static void main(String[] args) {
String path = "E:/Judy.png";
//獲取文件名稱
System.out.println(FilenameUtils.getName(path));
//獲取文件名稱,不包括拓展名
System.out.println(FilenameUtils.getBaseName(path));
//獲取文件拓展名
System.out.println(FilenameUtils.getExtension(path));
}
}
上傳的文件名問題
我們要給上傳的文件起一個唯一的名稱:通過UUID:
String fileName = UUID.randomUUID().toString() + "." +
FilenameUtils.getExtension(item.getName());
item.write(new File("E:/", fileName));
上傳文件的保存路徑
如果我們想將文件保存在應用中,可以用super.getServletContext().getRealPath()
函數獲得真實路徑:
String dir = super.getServletContext().getRealPath("/upload");
System.out.println(dir);
item.write(new File(dir, fileName));
這樣就可以在瀏覽器中訪問項目中的文件了,就不必從磁盤中訪問了。
注意:在寫文件的時候只能寫到文件中,不能寫到文件夾中。
緩存大小和臨時目錄
當文件大小超過 10KB 就不能放在緩存中了。
文件不放在內存中,其實放在了所謂的 “臨時目錄”,在 Tomcat 根目錄中的 temp 文件夾下。
DiskFileItemFactory factory = new DiskFileItemFactory();
//設置緩存大小
factory.setSizeThreshold(20 * 1024);
//設置臨時目錄
factory.setRepository(repository);
文件類型約束
//允許接收的圖片類型
private static final String ALLOWED_IMAGE_TYPE = "jpg;png;jpeg;gif";
String[] allowedImageType = ALLOWED_IMAGE_TYPE.split(";");
String ext = FilenameUtils.getExtension(item.getName());
List<String> list = Arrays.asList(allowedImageType);
//上傳文件類型不合法
if (!list.contains(ext)) {
req.setAttribute("errorMsg", "上傳文件類型錯誤");
req.getRequestDispatcher("/input.jsp").forward(req, resp);
System.out.println("Invalid Type!");
return;
}
jsp中:
<span style="color: red">${errorMsg}</span>
或者更通用的辦法是通過上傳文件的 mime-type 進行判斷是否爲 image 類型:
String mimeType = super.getServletContext().getMimeType(FilenameUtils.getName(item.getName()));
抽取FileUtil工具類
在工具類中,因爲要複用代碼,因此應該具有一般性,並儘可能簡潔,我們可以刪掉方法中響應參數,並且通過拋出自定義異常來讓調用者接收:
if (!list.contains(ext)) {
throw new LogicException("上傳文件類型錯誤,請上傳圖片文件...");
}
...
catch (LogicException e) {
throw e;
}
調用類 UploadServlet:
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 3067915922682828536L;
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
try {
FileUtil.upload(req);
} catch (LogicException e) {
String errorMsg = e.getMessage();
req.setAttribute("errorMsg", errorMsg);
req.getRequestDispatcher("/input.jsp").forward(req, resp);
}
}
}
這樣就重構了 FileUtil 類
文件大小約束問題
情況一:單個文件超過指定大小;
upload.setFileSizeMax(2*1024*1024); //2M
否則會報出 FileUploadBase$FileSizeLimitExceededException 的異常
因爲是異常,我們要再次捕捉這個異常:
catch (FileSizeLimitExceededException e) {
throw new LogicException("文件大小超過2M,請重試", e);
}
情況二:該次請求的全部數據超過指定大小
upload.setSizeMax(10 * 1024 * 1024); //10M
否則會報出 SizeLimitExceededException 異常
繼續接受這個異常並拋出:
catch (SizeLimitExceededException e) {
throw new LogicException("該次請求信息量超過10M,請重試", e);
}
效果如下:
使用Map封裝請求信息
我們想要將表單中的值封裝到一個對象中,例如 User 類,我們可以在 upload 方法中加入 User 參數,將 Servlet 中創建的類的實例對象作爲參數傳遞進來。:
User類
public class User {
private String username;
private String email;
private String imageUrl;//圖片路徑
private String imageName;//圖片原始名稱
}
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 3067915922682828536L;
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
try {
User user = new User(); //新建User實例
FileUtil.upload(req, user);//作爲參數傳遞
System.out.println(user);
} catch (LogicException e) {
e.printStackTrace();
String errorMsg = e.getMessage();
req.setAttribute("errorMsg", errorMsg);
req.getRequestDispatcher("/input.jsp").forward(req, resp);
}
}
}
將User賦值
if ("username".equals(fieldName)) {
user.setUsername(value);
} else if ("email".equals(fieldName)) {
user.setEmail(value);
}
但是這樣並不能處理通用情況,因此我們可以考慮設置一個Map進行傳遞,而我們知道類和接口對象都是引用傳遞,類似於C中的指針,因此得到Map中的值後再設置到User中。但是Map不能封裝圖片、複選框等需要有多個信息的控件。
如果還要用Map進行封裝的話,我們可以將圖片的兩個信息(imageUrl和imageName)再用一個類進行封裝,例如 CFile 類,然後作爲 Map 的參數傳遞進去:
CFile類:
@Data
public class CFile {
private String imageUrl;//圖片路徑
private String imageName;//圖片原始名稱
CFile(String imageUrl, String imageName) {
this.imageName = imageName;
this.imageUrl = imageUrl;
};
}
Map<String, CFile> binaryMap = new HashMap<>();
FileUtil.upload(req, fieldMap, binaryMap);
binaryMap.put(fileName, new CFile("/upload" + fileName, FilenameUtils.getName(item.getName())));
設置值到 User 中,並共享到 show.jsp 中
user.setUsername(fieldMap.get("username"));
user.setEmail(fieldMap.get("email"));
user.setImageName(binaryMap.get("pic").getImageName());
user.setImageUrl(binaryMap.get("pic").getImageUrl());
req.setAttribute("user", user);
req.getRequestDispatcher("/show.jsp").forward(req, resp);
然後我們在網頁中顯示:
<body>
註冊名稱:${user.username}</br>
註冊郵箱:${user.email}</br>
頭像原始名稱:${user.imageName}</br>
頭像:<img src="${user.imageUrl}">
</body>
結果如下:
完整代碼
UploadServlet.java:
package com.cherry.upload;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.Templates;
import org.apache.commons.fileupload.FileItem;
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;
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 3067915922682828536L;
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
try {
User user = new User();
//封裝普通表單的數據
//key:參數名稱
//value:參數的值
Map<String, String> fieldMap = new HashMap<>();
Map<String, CFile> binaryMap = new HashMap<>();
FileUtil.upload(req, fieldMap, binaryMap);
//再從fieldMap中設置到User中
System.out.println(fieldMap);
System.out.println(binaryMap);
user.setUsername(fieldMap.get("username"));
user.setEmail(fieldMap.get("email"));
user.setImageName(binaryMap.get("pic").getImageName());
user.setImageUrl(binaryMap.get("pic").getImageUrl());
req.setAttribute("user", user);
req.getRequestDispatcher("/show.jsp").forward(req, resp);
System.out.println(user);
} catch (LogicException e) {
e.printStackTrace();
String errorMsg = e.getMessage();
req.setAttribute("errorMsg", errorMsg);
req.getRequestDispatcher("/input.jsp").forward(req, resp);
}
}
}
FileUtil.java
package com.cherry.upload;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException;
import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import lombok.Value;
public class FileUtil {
//允許接收的圖片類型
private static final String ALLOWED_IMAGE_TYPE = "jpg;png;jpeg;gif;pdf";
public static void upload(HttpServletRequest req, Map<String, String> fieldMap,
Map<String, CFile> binaryMap) {
//檢查是否有文件上傳請求:請求方法爲POST,請求編碼爲multipart/form-data
boolean isMultipart = ServletFileUpload.isMultipartContent(req);
if (!isMultipart) {
return;
}
try {
//1.創建FileItemFactory對象
//FileItemFactory是用來創建FileItem對象的
//FileItem對象相當於form表單中的表單控件的封裝
DiskFileItemFactory factory = new DiskFileItemFactory();
//設置緩存大小
//factory.setSizeThreshold(20 * 1024);
//設置臨時目錄
//factory.setRepository(repository);
//2.創建一個新的文件上傳處理程序(文件上傳處理器)
ServletFileUpload upload = new ServletFileUpload(factory);
//設置單個文件上傳的大小限制
upload.setFileSizeMax(2 * 1024 * 1024); //2M
//設置所有請求的總數據的大小
upload.setSizeMax(4 * 1024 * 1024); //10M
//3.解析請求
List<FileItem> items = upload.parseRequest(req);
/*for (int i = 0; i < items.size(); i++) {
System.out.println(items.get(i));
}*/
//4.迭代出每一個FileItem
for (FileItem item : items) {
//獲取表單屬性名稱
String fieldName = item.getFieldName();
if (item.isFormField()) {
//普通表單控件
String value = item.getString("UTF-8");//獲取當前普通表單控件的參數值
System.out.println(fieldName + "-" + value);
fieldMap.put(fieldName, value);
} else {
//當前上傳文件的mime類型
/*String mimeType = req.getServletContext()
.getMimeType(FilenameUtils.getName(item.getName()));
System.out.println(mimeType);*/
String[] allowedImageType = ALLOWED_IMAGE_TYPE.split(";");
String ext = FilenameUtils.getExtension(item.getName()).toLowerCase();
List<String> list = Arrays.asList(allowedImageType);
//上傳文件類型不合法
if (!list.contains(ext)) {
throw new LogicException("上傳文件類型錯誤,請上傳圖片文件...");
}
//上傳文件控件
System.out.println(fieldName + "-" + FilenameUtils.getName(item.getName()));//字段名-上傳的文件名
String fileName = UUID.randomUUID().toString() + "."
+ FilenameUtils.getExtension(item.getName());
String dir = req.getServletContext().getRealPath("/upload");
item.write(new File(dir, fileName));//把二進制文件寫到哪個文件中
//是否存儲在內存中
System.out.println(item.isInMemory());
binaryMap.put(fieldName, new CFile("/upload/" + fileName,
FilenameUtils.getName(item.getName())));
}
}
} catch (FileSizeLimitExceededException e) {
throw new LogicException("文件大小超過2M,請重試", e);
} catch (SizeLimitExceededException e) {
throw new LogicException("該次請求信息量超過10M,請重試", e);
} catch (LogicException e) {
throw e;
} catch (Exception e) {
e.printStackTrace();
}
}
}
User.java:
package com.cherry.upload;
import lombok.Data;
@Data
public class User {
private String username;
private String email;
private String imageUrl;//圖片路徑
private String imageName;//圖片原始名稱
}
CFile.java:
package com.cherry.upload;
import lombok.Data;
@Data
public class CFile {
private String imageUrl;//圖片路徑
private String imageName;//圖片原始名稱
CFile(String imageUrl, String imageName) {
this.imageName = imageName;
this.imageUrl = imageUrl;
};
}
input.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>文件上傳</h3>
<span style="color: red">${errorMsg}</span>
<form action="/upload" method="POST" enctype="multipart/form-data">
賬號:<input type="text" name="username"/><br/>
郵箱:<input type="text" name="email"/><br/>
頭像:<input type="file" name="pic" accept="image/*"/><br/>
<input type="submit" value=" 註冊 "/><br/>
</form>
</body>
</html>
show.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!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>Insert title here</title>
</head>
<body>
註冊名稱:${user.username}</br>
註冊郵箱:${user.email}</br>
頭像原始名稱:${user.imageName}</br>
頭像:<img src="${user.imageUrl}">
</body>
</html>
二、文件下載
沒什麼好說的,需要注意的是,下載資源不能暴露在 WEB-INF 外面,必須通過 Servlet 響應下載,直接上代碼:
DownloadServlet.java
package com.cherry._02_download;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/down")
public class DownloadServlet extends HttpServlet {
private static final long serialVersionUID = -6406195121839970110L;
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//權限檢查等
//1.獲取被下載的資源文件名
String fileName = req.getParameter("filename");
System.out.println(fileName);
if (hasLength(fileName)) {
fileName = new String(fileName.getBytes("ISO-8859-1"), "UTF-8");
}
//2.從服務器中找到被下載資源的絕對路徑
String realPath = super.getServletContext().getRealPath("/WEB-INF/download/" + fileName);
System.out.println(realPath);
//①不要讓瀏覽器打開文件,而是要彈出下載框並保存文件
resp.setContentType("application/x-msdownload");
//②設置下載文件的建議文件名
String userAgent = req.getHeader("User-Agent");
if (userAgent.contains("MSIE")) {
//IE
fileName = URLEncoder.encode(fileName, "UTF-8");
} else {
//非IE
fileName = new String(fileName.getBytes("UTF-8"), "ISO-8859-1");
}
resp.setHeader("Content-Disposition", "attachment; filename =" + fileName);
//3.通過文件輸出流將磁盤中的文件讀取到程序中,然後輸出響應到瀏覽器
Files.copy(Paths.get(realPath), resp.getOutputStream());
}
private boolean hasLength(String str) {
return str != null && "".equals(str.trim());
}
}
download.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!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>Insert title here</title>
</head>
<body>
<h3>下載資源列表</h3>
<a href="/down?filename=Judy.zip">Judy.zip</a></br>
<a href="/down?filename=Tom貓.zip">Tom貓.zip</a></br>
</body>
</html>