JavaWeb20-檔案上傳;下載(Java真正的全棧開發)
檔案上傳&下載一.檔案上傳
1. 檔案上傳介紹
要將客戶端(瀏覽器)大資料儲存到伺服器端,不將資料直接儲存到資料庫中,而是要將資料儲存到伺服器所在的磁碟上,這就要使用檔案上傳。
作用:減少了資料庫伺服器的壓力,對資料的操作更加靈活
2. 檔案上傳原理分析
所謂的檔案上傳就是伺服器端通過request物件獲取輸入流,將瀏覽器端上傳的資料讀取出來,儲存到伺服器端
瀏覽器端操作
1.請求方式必須是post
2.使用<input type=’file’ name=’xxx’>,必須有name屬性且有值
3.表單必須設定encType=”multipart/form-data” (ajax見過enctype)
伺服器端操作
通過request物件,獲取inputStream,就可以將瀏覽器提交的所有資料讀取到.
注意:
這時候,獲取其他上傳引數的時候,通過request.getParameter(“”)獲取不到了.
3. Commons-fileupload介紹與入門案例
commons-fileupload介紹
Apache 開源組織提供了一個用來處理表單檔案上傳的一個開源元件( Commons-fileupload ),該元件效能優異,並且其API使用極其簡單,可以讓開發人員輕鬆實現web檔案上傳功能,因此在web開發中實現檔案上傳功能,通常使用Commons-fileupload元件實現。
使用Commons-fileupload元件實現檔案上傳,需要匯入該元件相應的支撐jar包:
Commons-fileupload和commons-io。
commons-io 不屬於檔案上傳元件的開發jar檔案,但Commons-fileupload 元件從1.1 版本開始,它工作時需要commons-io包的支援。
commons-fileupload API簡單介紹
DiskFileItemFactory,用於設定快取大小及臨時檔案儲存位置
ServletFileUpload,真正用於檔案上傳的核心類
FIleItem,代表的是一個上傳項
使用步驟:
1.匯入jar包(commons-fileupload-1.2.1.jar和commons-io-1.4.jar)
2.建立一個磁碟檔案項工廠
DiskFileItemFactory fatory=new DiskFileItemFactory()
3.建立檔案上傳核心物件
ServletFileUpload upload=new SerlvetFileUpload(fatory)
4.核心物件解析request
List<FileItem> list=upload.parseReqeust(request);
5.遍歷list獲取裡面的內容
for (FileItem f : list) {
if(!f.isFormField()){
InputStream is = f.getInputStream();
/*byte[] b=new byte[1024];
intlen=-1;
FileOutputStream os = new FileOutputStream(new File("f:/1.txt"));
while((len=is.read(b))!=-1){
os.write(b);
}
os.close();
is.close();
*/
FileOutputStream os = new FileOutputStream(new File("c:/1.txt"));
IOUtils.copy(is, os);
os.close();
is.close();
}
}
入門案例分析
在瀏覽器端建立一個可以上傳檔案的表單,在伺服器端通過commons-fileupload完成檔案上傳。
瀏覽器端注意三件事情:
表單的提交方式為post
在表單上新增屬性 encType=”multipart/form-data”
使用<input type=’file’ >,新增name屬性且有值
伺服器端:
建立DiskFileItemFactory
建立ServletFileUpload
通過ServletFileUpload的parseRequest方法得到所有的FileItem
入門案例實現
瀏覽器端
<form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data">
<input type="file" name="f">
<input type="submit" value="上傳">
</form>
伺服器端
// 1.建立一個 DiskFileItemFactory
DiskFileItemFactory factory = new DiskFileItemFactory();
// 2.建立一個ServletFileUpload
ServletFileUpload upload = new ServletFileUpload(factory);
// 3.完成上傳操作
try {
// 3.1 得到所有上傳項
List<FileItem> items = upload.parseRequest(request);
// 3.2遍歷上傳項
for (FileItem item : items) {
// 3.3判斷是否是上傳元件
if (!item.isFormField()) {
// 3.4 將檔案真正上傳
IOUtils.copy(item.getInputStream(), new FileOutputStream(
"f:/upload/a.txt"));
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
4. Commons-fileupload詳解-DiskFileItemFactory
:磁碟檔案項工廠
常用方法:
new DiskFileItemFactory();構造方法
new DiskFileItemFactory(sizeThreshold, repository);構造方法
設定快取大小
factory.setSizeThreshold(1024*1024); //設定為1m 預設是10k 單位為byte
設定臨時檔案儲存位置
File temp=new File(this.getServletContext().getRealPath("/temp"));
factory.setRepository(file); //可以指定臨時檔案儲存位置,預設是系統的臨時檔案儲存位置(設定緩衝區位置)
5. Commons-fileupload詳解-ServletFileUpload
:檔案上傳核心物件
new ServletFileUpload(factory);
parseRequest方法
List<FileItem> pareRequest(HttpServletRequest request)
得到所有的上傳資訊,將每一部分對映成FileItem物件.
isMultipartContent方法
boolean isMultipartContent(HttpServletRequest request)
這個方法返回值是boolean,它是用於判斷當前表單是否是一個上傳的表單,
簡單說,就判斷它的encType的值是否是 multipart/form-data.
若使用了enctype=multipart/form-data,在後臺就不可以使用request.getParameter(name)
upload.setHeaderEncoding(“utf-8”)方法 設定檔名稱的編碼
用於解決上傳檔名稱中文亂碼問題
設定上傳檔案大小
void setFileSizeMax(long fileSizeMax) 設定單個檔案上傳大小
void setSizeMax(long sizeMax) 設定總檔案上傳大小
6. Commons-fileupload詳解-FileItem
:檔案項
isFormField方法
這個方法返回的是boolean型別,
它是判斷當前元件是否是上傳元件 簡單說,就是判斷type="file",
如果返回true,代表不是上傳元件,返回false,代表是上傳元件
getName方法
獲取上傳元件的上傳檔案的名稱,如果是非上傳元件,返回的是null
getFieldName方法
獲取元件名稱,簡單說,就是表單的元素的name值。(獲取前臺頁面的name屬性)
getString方法
獲取非上傳元件的value值,(獲取的是表單填寫的內容)
通過它也可以獲取上傳的檔案內容,但是,使用它獲取不合適。
如果使用了commons-fileupload進行檔案上傳,而上傳表單中包含了非上傳元件,獲取 其值,不能使用request獲取.
getString()有一個過載的方法 getString(String encoding)
getString(utf-8)可以解決亂 碼問題
getInputStream方法
通過FileItem.getInputStream();可以獲取一個輸入流,這個輸入流就可以
讀取出上傳檔案內容。
InputStream is = item.getInputStream(); // 用於讀取上傳檔案內容的輸入流.
FileOutputStream fos = new FileOutputStream("f:/upload/" + filename);
int len = -1;
byte[] b = new byte[1024 * 10];
while ((len = is.read(b)) != -1) {
fos.write(b, 0, len);
fos.flush();
}
fos.close();
is.close();
可以使用IOUtils工具完成檔案copy操作
IOUtils.copy(is, fos);
delete()方法
它是用於上傳完成後,刪除臨時檔案的
7. 多檔案上傳
我們在寫郵件中可以新增多個附件,那麼我們在檔案上傳時,是不是也可以上傳多個檔案哪,答案是一定的,那麼怎樣實現多個檔案上傳哪?
我們可以通過js實現瀏覽器端的上傳檔案框的動態新增。伺服器端程式碼不需要改變.
function addFile() {
//1.得到id叫content的div
var div=document.getElementById("content");
//2.向其中新增一段html程式碼
div.innerHTML+="<div><input type='file' name='f'><input type='button' value='remove File' onclick='removeFile(this);'></div>";
}
function removeFile(btn){
document.getElementById("content").removeChild(btn.parentNode);
}
///////////////////////////////////////////////////////////////////////////
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="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>
<script type="text/javascript">
//新增一個上傳框
functionaddFile(){
//獲取新增位置
varobj=document.getElementById("fileDiv");
//新增檔案筐
obj.innerHTML+=("<div><input type='file' name='file'><input type='button' value='刪除' onclick='delFile(this)'/></div>");
}
//刪除當前檔案上傳框
functiondelFile(obj){
document.getElementById("fileDiv").removeChild(obj.parentNode);
}
</script>
<body>
<input type='button' value='新增檔案' onclick="addFile()">
<form action="${pageContext.request.contextPath }/upload4" method='post' enctype="multipart/form-data">
<input type="file" name="file"><br>
<div id='fileDiv'></div>
<input type='submit' >
</form>
</body>
</html>
8. 檔案上傳問題-檔案重名
每一個客戶端都可以進行檔案上傳操作,那麼當我們上傳的檔案過多,一定會出現同名的檔案,那麼在伺服器端只能儲存一個,對於這個問題,我們在上傳檔案時,就需要考慮檔案重名問題.
一般情況下,對於上傳檔案,為了保證不重名,會給檔案起一個隨機名.
一種方案是使用uuid.
一種方案是使用毫秒值
9. 檔案上傳問題-儲存位置
本質就是上傳的檔案是否允許瀏覽器端直接訪問。
例如:商品新增時需要一個圖片,這個圖片一定是可以直接被瀏覽器端訪問的。
1.允許被瀏覽器端訪問:放置在WebRoot下,但不能是WEB-INF或META-INF下其及子目錄下
2.不允許被瀏覽器端訪問:
若放在工程下則放置在WEB-INF或META-INF及其子目錄下.
也可以不放在工程下
10. 檔案上傳問題-目錄分離
當我們上傳檔案過多,並且儲存在同一個目錄下時,我們就需要考慮怎樣處理它們,因為一個目錄下檔案過多,不僅降低效能,操作時也不方便。
為了防止同一個目錄下方上傳檔案數量過多
1) 按照上傳時間進行目錄分離 (周、月 )
2) 按照上傳使用者進行目錄分離 ----- 為每個使用者建立單獨目錄
3) 按照固定數量進行目錄分離 ------ 假設每個目錄只能存放3000個檔案 ,每當一個目錄存滿3000個檔案後,建立一個新的目錄
4).按照唯一檔名的hashcode 進行目錄分離
public static String generateRandomDir(String uuidFileName) {
// 獲得唯一檔名的hashcode
int hashcode = uuidFileName.hashCode();
// 獲得一級目錄
int d1 = hashcode & 0xf;
// 獲得二級目錄
int d2 = (hashcode >>> 4) & 0xf;
return "/" + d2 + "/" + d1;// 共有256目錄
}
11. 案例-新增商品資訊(上傳商品圖片)
1.匯入jar包
2.新增一個
3.在資料庫中新增一個欄位 String imgurl
Alter table products add imgurl varchar(100)
4.在javabean中新增一個欄位 String imgurl
5.add
二.檔案下載
1. 下載介紹與超連結下載
所謂的下載,其實就是將伺服器端的資源通過io流寫回到瀏覽器端。
超連結實現下載
如果檔案可以直接被瀏覽器解析,會直接在瀏覽器上開啟。
如果不能解析,可以下載
通過另存為進行下載
這種下載方式:當路徑提交時,會通過預設的servlet將檔案直接寫回到瀏覽器端
超連結下載問題分析
原因是在http響應頭中content-type,如果它的值可以被瀏覽器解析,那麼響應回的內容就會被瀏覽器端直接解析,如果content-type的值,不可以被瀏覽器直接解析,那麼就會下載。
注意:不能被瀏覽器直接訪問的檔案,不可以使用超連線下載.
2. 下載操作-伺服器端編碼實現下載
伺服器端編碼下載原理分析
通過response可以獲取輸出流,我們將要下載的資源,通過response獲取的輸出流直接寫回到瀏覽器端就可以。
伺服器端下載兩個響應頭設定(下載步驟)
1.通過response.setContentType()正設定響應資料的mimeType型別.
獲取一個檔案的mimeType型別
ServletContext.getMimeType(String filename);
2.再新增一個頭資訊,下載的時候就可以彈出一個提示框了
response.setHeader("Content-Disposition","attachment;filename=下載檔名");
3. 關於下載時亂碼問題分析與解決
亂碼分析:
對於下載時,我們在顯示下載檔名稱時,如果包含了中文,就可能出現亂碼問題,出現的原因,是對於不同的瀏覽器,它們在處理下載檔案時的編碼不一致,ie瀏覽器使用的是utf-8編碼,而firefox瀏覽器使用的是base64編碼
解決方案1:根據瀏覽器型別
下載檔名稱中文亂碼問題
response.setHeader("content-disposition", "attachment;filename="+ filename); 這段程式碼中的filename是指定下載檔案時的名稱
對於IE瀏覽器、谷歌,它要求必須給一個UTF-8碼
對於firefox瀏覽器,它要求必須給一個base64編碼
程式碼如下:
if (agent.contains("MSIE")) {
// IE瀏覽器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", " ");
} else if (agent.contains("Firefox")) {
// 火狐瀏覽器
BASE64Encoder base64Encoder = new BASE64Encoder();
filename = "=?utf-8?B?"+ base64Encoder.encode(filename.getBytes("utf-8")) + "?=";
} else {
// 其它瀏覽器
filename = URLEncoder.encode(filename, "utf-8");
}
解決方案2:簡單處理
response.setHeader( "Content-Disposition", "attachment;filename=" + new String( fileName.getBytes("gb2312"), "ISO8859-1" ) );
4. 案例-下載銷售榜單分析
我們下載的檔案的格式是csv.它是一種以”.”來分隔的檔案,可以使用excel開啟
Excel要求它的檔案內容必須是gbk(gb2312)(gb18030),也就是我們可以通過response.setCharacterEncoding(“gbk”)來設定響應資料的編碼
5. 案例-下載銷售榜單實現
servlet寫法:
sql的寫法
SELECT
products.name,SUM(buynum) total
FROM
orders,orderitem,products
WHERE
orders.id=orderitem.order_id
AND
orderitem.product_id=products.id
AND
orders.paystat=1
GROUP BY
products.name
ORDER BY
total DESC;