一、文件上傳的原理
1、文件上傳的前提:
a、form表單的method必須是post
b、form表單的enctype必須是multipart/form-data(決定了POST請求方式,請求正文的數據類型)
注意:當表單的enctype是multipart/form-data,傳統的獲取請求參數的方法失效。
---------------------------------------------------------------------------------------------------------------------------
請求正文:(MIME協議進行描述的,正文是多部分組成的)
-----------------------------7dd32c39803b2 (// 分界符)
Content-Disposition: form-data; name="username"
wzhting
-----------------------------7dd32c39803b2
Content-Disposition: form-data; name="f1"; filename="C:\Documents and Settings\wzhting\妗岄潰\a.txt"
Content-Type: text/plain
aaaaaaaaaaaaaaaaaa
-----------------------------7dd32c39803b2
Content-Disposition: form-data; name="f2"; filename="C:\Documents and Settings\wzhting\妗岄潰\b.txt"
Content-Type: text/plain
bbbbbbbbbbbbbbbbbbb
-----------------------------7dd32c39803b2--
---------------------------------------------------------------------------------------------------------------------------
c、form中提供input的type是file類型的文件上傳域
二、利用第三方組件實現文件上傳
1、commons-fileupload組件:
jar:commons-fileupload.jar 和 commons-io.jar
2、 fileupload組件工作流程
3、核心類或接口
DiskFileItemFactory:設置環境
public void setSizeThreshold(int?sizeThreshold) :
設置緩衝區大小。默認是10Kb。
當上傳的文件超出了緩衝區大小,fileupload組件將使用臨時文件緩存上傳文件
public void setRepository(java.io.File repository):
設置臨時文件的存放目錄。默認是系統的臨時文件存放目錄。
ServletFileUpload:核心上傳類(主要作用:解析請求的正文內容)
boolean isMultipartContent(HttpServletRequest?request):
判斷用戶的表單的enctype是否是multipart/form-data類型的。
List parseRequest(HttpServletRequest request):
解析請求正文中的內容
setFileSizeMax(4*1024*1024); //設置單個上傳文件的大小
upload.setSizeMax(6*1024*1024); //設置總文件大小
FileItem:代表表單中的一個輸入域。
boolean isFormField(): 是否是普通字段
String getFieldName: 獲取普通字段的字段名
String getString(): 獲取普通字段的值
InputStream getInputStream(): 獲取上傳字段的輸入流
String getName(): 獲取上傳的文件名
getContentType(); 得到上傳文件的MIME類型
FileItem.delete(); 方法刪除臨時文件。但一定要在關閉流之後。
4、 文件上傳案例
-------------------------------------------------------------------------------------------------------------
<body>
<form action="${pageContext.request.contextPath}/servlet/UploadServlet3" enctype="multipart/form-data" method="post">
用戶名: <input type="text" name="username" /> <br/><br/>
文件1: <input type="file" name="f1" /> <br/><br/>
文件2: <input type="file" name="f2" /> <br/><br/>
<input type="submit" value="保存" />
</form>
</body>
-------------------------------------------------------------------------------------------------------------
// 存儲目的地: tomacate/webapp/day21/files
public class UploadServlet2 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
// 設置環境
DiskFileItemFactory factory = new DiskFileItemFactory();
// 得到存放上傳文件的真實路徑
String storePath = getServletContext().getRealPath("/WEB-INF/files");
// 判斷一下form是否爲enctype=multipart/file-data
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if(!isMultipart){
System.out.println("大傻鳥,快去將表單的enctype的值設置爲multipart/form-data");
return;
}
// ServletFileUpload核心類
ServletFileUpload upload = new ServletFileUpload(factory);
// 解析
List<FileItem> items = upload.parseRequest(request);
for(FileItem item : items){
if(item.isFormField()){
// 是普通字段
String fieldName = item.getFieldName();
String fieldValue = item.getString();
System.out.println(fieldName+"="+fieldValue);
}else{
// 是上傳字段
InputStream in = item.getInputStream();
// 上傳的文件名
// 注: 有的瀏覽器爲: c:\Documents and Settings\wzhting\妗岄潰\a.txt 有的爲 a.txt
String fileName = item.getName();
fileName = fileName.substring(fileName.lastIndexOf("\\")+1); // 得到文件名: a.txt
// 構件輸出流
String storeFile = storePath + "\\" + fileName;
OutputStream out = new FileOutputStream(storeFile);
byte[] b = new byte[1024];
int len = 0;
while((len=in.read(b))!=-1){
out.write(b, 0, len);
}
out.close();
in.close();
}
}
} catch (FileUploadException e) {
throw new RuntimeException("服務器繁忙");
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
-------------------------------------------------------------------------------------------------------------
三、文件上傳中要注意的9個問題
1、如何保證服務器的安全
把保存上傳文件的目錄放到WEB-INF目錄中。
2、中文亂碼問題
2.1普通字段的中文請求參數
解決辦法:String value = FileItem.getString("UTF-8");
2.2上傳的文件名是中文
解決辦法:request.setCharacterEncoding("UTF-8");
3、重名文件被覆蓋的問題
System.currentMillions()+"_"+a.txt(樂觀, 可能重複)
UUID+"_"+a.txt:保證文件名唯一
4、分目錄存儲上傳的文件
方式一:當前日期建立一個文件夾,當前上傳的文件都放到此文件夾中。
方式二:利用文件名的hash碼打散目錄來存儲。
--------------------------------------------------------------------------------------
int hashCode = fileName.hashCode();
執行運算; hashCode&0xf;
1001 1010 1101 0010 1101 1100 1101 1010
& 0000 0000 0000 0000 0000 0000 0000 1111
---------------------------------------------------
0000 0000 0000 0000 0000 0000 0000 1010
取hashCode的後4位 範圍:0000~1111:整數0~15共16個
執行運算: (hashCode&0xf0)>>4
1001 1010 1101 0010 1101 1100 1101 1010
& 0000 0000 0000 0000 0000 0000 1111 0000
---------------------------------------------------
0000 0000 0000 0000 0000 0000 1101 0000 >>4
---------------------------------------------------
0000 0000 0000 0000 0000 0000 0000 1101
0000~1111:整數0~15共16個
--------------------------------------------------------------------------------------
5、限制用戶上傳的文件類型
通過判斷文件的擴展名來限制是不可取的。
通過判斷其Mime類型才靠譜。FileItem.getContentType();
6、如何限制用戶上傳文件的大小
6.1單個文件大小限制。超出了大小友好提示
抓異常進行提示:
org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException
6.2總文件大小限制。超出了大小友好提示
抓異常進行提示:
org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException
7、臨時文件的問題
commons-fileupload組件不會刪除超出緩存的臨時文件。
FileItem.delete()方法刪除臨時文件。但一定要在關閉流之後。
8、多個文件上傳時,沒有上傳內容的問題
if(fileName==null||"".equals(fileName.trim())){
continue;
}
9、上傳進度檢測
給ServletFileUpload註冊一個進度監聽器即可,把上傳進度傳遞給頁面去顯示
//pBytesRead:當前以讀取到的字節數
//pContentLength:文件的長度
//pItems:第幾項
public void update(long pBytesRead, long pContentLength,
int pItems) {
System.out.println("已讀取:"+pBytesRead+",文件大小:"+
pContentLength+",第幾項:"+pItems);
}
例:
--------------------------------------------------------------------------------------------------
public class UploadServlet3 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 解決上傳文件名的中文編碼問題
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
PrintWriter pw = response.getWriter();
try {
// 設置環境
DiskFileItemFactory factory = new DiskFileItemFactory();
// 設置臨時存放目錄 在webroot下創建一個temp的文件夾 查看的時候在apatch查看
factory.setRepository(new File(getServletContext().getRealPath("/temp")));
// 得到存放上傳文件的真實路徑
String storePath = getServletContext().getRealPath("/WEB-INF/files");
// 判斷一下form是否爲enctype=multipart/file-data
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if(!isMultipart){
System.out.println("大傻鳥,快去將表單的enctype的值設置爲multipart/form-data");
return;
}
// ServletFileUpload核心類
ServletFileUpload upload = new ServletFileUpload(factory);
// 進度監聽
upload.setProgressListener(new ProgressListener() {
// 關聯源代碼(common-fileupload.jar 顯示參數)
// pBytesRead: 當前已讀取到的字節數 pContentLength: 文件的長度 pItem: 第幾項,從1開始
public void update(long pBytesRead, long pContentLength,int pItems) {
System.out.println("已讀取:"+pBytesRead+",文件大小:"+pContentLength+
",第幾項:"+pItems);
}
});
// 設置單個上傳文件的大小不能超過8M
// upload.setFileSizeMax(8*1024*1024);
// 設置上傳的總文件的大小不能超過15M
// upload.setSizeMax(15*1024*1024);
// 解析
List<FileItem> items = upload.parseRequest(request);
for(FileItem item : items){
if(item.isFormField()){
// 是普通字段
String fieldName = item.getFieldName();
String fieldValue = item.getString("UTF-8"); // 解決form表單請求參數的中文編碼問題
System.out.println(fieldName+"="+fieldValue);
}else{
// 得到上傳文件的MIME類型
// String mimeType = item.getContentType();
// 限制上傳文件的類型: 只允許爲圖片類型
// if(mimeType.startsWith("image")){
// 是上傳字段
InputStream in = item.getInputStream();
// 上傳的文件名
String fileName = item.getName(); // 注: 有的瀏覽器爲: c:\Documents and Settings\wzhting\妗岄潰\a.txt 有的爲 a.txt
// 如果上傳的文件爲null
if(fileName==null||"".equals(fileName.trim())){
continue;
}
fileName = fileName.substring(fileName.lastIndexOf("\\")+1); // 得到文件名: a.txt
fileName = UUID.randomUUID()+"_"+fileName; // 保證新的文件名不重複,避免了上傳的文件的重名覆蓋問題
// 打散存儲目錄
String newStorePath = makeStorePath(storePath,fileName); // 根據WEB-INF/files和文件名,創建一個新的存儲路徑: /WEB-INF/files/1/13/文件名
String storeFile = newStorePath + "\\" + fileName; // WEN-INF/files/1/13/文件名
// 構件輸出流
OutputStream out = new FileOutputStream(storeFile);
byte[] b = new byte[1024];
int len = 0;
while((len=in.read(b))!=-1){
out.write(b, 0, len);
}
out.close();
in.close();
// 刪除臨時文件 注:一定要在關閉流之後 臨時文件在上傳完畢後會調用該方法自動刪除
item.delete();
}
// }
}
} catch (org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException e){
// 該異常爲: 單個文件超出大小時的異常 注: 異常的捕獲: 子類異常必須在父類異常的前面捕獲
pw.write("單個文件的大小不能超過8M");
} catch (org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException e){
// 該異常爲: 上傳的總文件超出大小時的異常
pw.write("上傳的總文件的大小不能超過15M");
}catch (FileUploadException e) {
e.printStackTrace();
}
}
// 根據/WEN-INF/files和文件名 創建一個新的存儲路徑: /WEB-INF/files/1/13
private String makeStorePath(String storePath, String fileName) {
int hashCode = fileName.hashCode();
int dir1 = hashCode&0xf; // 0000~1111 整數0~15 共16個
int dir2 = (hashCode&0xf0)>>4; // 0000~1111 整數0~15 共16個
String path = storePath+"\\"+dir1+"\\"+dir2; // WEB-INF/files/1/13
File file = new File(path);
if(!file.exists()){
file.mkdirs();
}
return path;
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
--------------------------------------------------------------------------------------------------