JavaWeb中(文件上(shàng)傳和(hé / huò)下載)
發表時(shí)間:2020-8-19
發布人(rén):融晨科技
浏覽次數:80
引言:
-
文件的(de)上(shàng)傳和(hé / huò)下載在(zài)web應用中是(shì)非常常用,也(yě)是(shì)非常有用的(de)功能。
- 例如:發送電子(zǐ)郵件時(shí)可以(yǐ)同過上(shàng)傳附件發送文件,OA系統中可以(yǐ)通過上(shàng)傳文件來(lái)提交公文,社交網站通過上(shàng)傳圖片來(lái)自定義頭像等等。
- 例如:下載實際上(shàng)隻要(yào / yāo)資源放在(zài)用戶可訪問的(de)目錄中用戶就(jiù)可以(yǐ)直接通過地(dì / de)址下載,但是(shì)一些資源是(shì)存放到(dào)數據庫中的(de),還有一些資源需要(yào / yāo)一定權限才能下載,這(zhè)裏就(jiù)需要(yào / yāo)我們通過Servlet來(lái)完成下載的(de)功能。
-
可以(yǐ)說(shuō)上(shàng)傳和(hé / huò)下載是(shì)每一個(gè)web應用都需要(yào / yāo)具有的(de)一個(gè)功能,所以(yǐ)需要(yào / yāo)我們掌握。
-
第1章 文件的(de)上(shàng)傳
1.1 文件上(shàng)傳的(de)步驟
文件的(de)上(shàng)傳主要(yào / yāo)分成兩個(gè)步驟:
-
用戶在(zài)頁面中選擇要(yào / yāo)上(shàng)傳的(de)文件,然後将請求提交到(dào)Servlet
-
Servlet收到(dào)請求,解析用戶上(shàng)傳的(de)文件,然後将文件存儲到(dào)服務器
1.2 創建上(shàng)傳文件的(de)表單
- 創建一個(gè)form表單
<form action="" method="post" enctype="multipart/form-data">
<input type="file" name="file" /><br /><br />
<input type="submit" value="上(shàng)傳" />
</form>
-
文件上(shàng)傳的(de)表單和(hé / huò)之(zhī)前的(de)表單類似,但有以(yǐ)下内容需要(yào / yāo)注意:
- 表單的(de)method屬性必須爲(wéi / wèi)post
- 表單的(de)enctype屬性必須爲(wéi / wèi)multipart/form-data
- 上(shàng)傳文件的(de)控件是(shì)input,type屬性爲(wéi / wèi)file
-
該表單打開後是(shì)如下效果:
-
IE
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議将圖片保存下來(lái)直接上(shàng)傳(img-0ohOyE7Q-1597829644379)(尚矽谷_張春勝_文件的(de)上(shàng)傳和(hé / huò)下載.assets/1558975331009.png)]
-
Chrome
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議将圖片保存下來(lái)直接上(shàng)傳(img-bQ5iOiZO-1597829644383)(尚矽谷_張春勝_文件的(de)上(shàng)傳和(hé / huò)下載.assets/1558975309963.png)]
-
火狐
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議将圖片保存下來(lái)直接上(shàng)傳(img-VYT1AJDN-1597829644385)(尚矽谷_張春勝_文件的(de)上(shàng)傳和(hé / huò)下載.assets/1558975370024.png)]
-
-
編寫Servelet
-
頁面的(de)表單控件創建好以(yǐ)後,選中文件點擊上(shàng)傳按鈕請求将會提交到(dào)指定的(de)Servlet來(lái)處理。
-
注意:這(zhè)裏不(bù)能再像以(yǐ)前的(de)Servlet中那樣,通過request.getParamter()來(lái)獲取請求參數了(le/liǎo),當enctype=“multipart/form-data” 時(shí),再使用getParamter()獲取到(dào)内容永遠爲(wéi / wèi)空。因爲(wéi / wèi)浏覽器發送請求的(de)方式已經改變。
-
既然以(yǐ)前的(de)方法不(bù)能使用了(le/liǎo),這(zhè)裏我們必須要(yào / yāo)引入一個(gè)新的(de)工具來(lái)解析請求中的(de)參數和(hé / huò)文件,這(zhè)個(gè)工具就(jiù)是(shì)commons-fileupload。
-
1.3 commons-fileupload
-
commons-fileupload是(shì)Apache開發的(de)一款專門用來(lái)處理上(shàng)傳的(de)工具,它的(de)作用就(jiù)是(shì)可以(yǐ)從request對象中解析出(chū),用戶發送的(de)請求參數和(hé / huò)上(shàng)傳文件的(de)流。
-
commons-fileupload包依賴commons-io,兩個(gè)包需要(yào / yāo)同時(shí)導入。
-
核心類:
-
DiskFileItemFactory
-
工廠類,用于(yú)創建ServletFileUpload,設置緩存等
-
該類一般直接使用構造器直接創建實例
-
方法:
-
public void setSizeThreshold(int sizeThreshold):用于(yú)設置緩存文件的(de)大(dà)小(默認值10kb)
-
public void setRepository(File repository):用于(yú)設置緩存文件位置(默認系統緩存目錄)
-
-
-
ServletFileUpload
-
該類用于(yú)解析request對象從而(ér)獲取用戶發送的(de)請求參數(包括普通參數和(hé / huò)文件參數)
-
該類需要(yào / yāo)調用有參構造器創建實例,構造器中需要(yào / yāo)一個(gè)DiskFileItemFactory作爲(wéi / wèi)參數
-
方法:
-
public List parseRequest(HttpServletRequest request):解析request對象,獲取請求參數,返回的(de)是(shì)一個(gè)List,List中保存的(de)是(shì)一個(gè)FileItem對象,一個(gè)對象代表一個(gè)請求參數。
-
public void setFileSizeMax(long fileSizeMax):設置單個(gè)文件的(de)大(dà)小限制,單位爲(wéi / wèi)B。如果上(shàng)傳文件超出(chū)限制,會在(zài)parseRequest()抛出(chū)異常FileSizeLimitExceededException。
-
public void setSizeMax(long sizeMax):限制請求内容的(de)總大(dà)小,單位爲(wéi / wèi)B。如果上(shàng)傳文件超出(chū)限制,會在(zài)parseRequest()抛出(chū)異常SizeLimitExceededException。
-
-
-
FileItem
-
該類用于(yú)封裝用戶發送的(de)參數和(hé / huò)文件,也(yě)就(jiù)是(shì)用戶發送來(lái)的(de)信息将會被封裝成一個(gè)FileItem對象,我們通過該對象獲取請求參數或上(shàng)傳文件的(de)信息。
-
該類不(bù)用我們手動創建,由ServletFileItem解析request後返回。
-
方法:
-
String getFieldName():獲取表單項的(de)名字,也(yě)就(jiù)是(shì)input當中的(de)name屬性的(de)值。
-
String getName():獲取上(shàng)傳的(de)文件名,普通的(de)請求參數爲(wéi / wèi)null。
-
String getString(String encoding):獲取内容,encoding參數需要(yào / yāo)指定一個(gè)字符集。
? ① 若爲(wéi / wèi)文件,将文件的(de)流轉換爲(wéi / wèi)字符串。
? ② 若爲(wéi / wèi)請求參數,則獲取請求參數的(de)value。
-
boolean isFormField():判斷當前的(de)FileItem封裝的(de)是(shì)普通請求參數,還是(shì)一個(gè)文件。
? ① 如果爲(wéi / wèi)普通參數返回:true
? ② 如果爲(wéi / wèi)文件參數返回:false
-
String getContentType():獲取上(shàng)傳文件的(de)MIME類型
-
long getSize():獲取内容的(de)大(dà)小
-
write():将文件上(shàng)傳到(dào)服務器
-
-
-
-
示例代碼:創建一個(gè)Servlet并在(zài)doPost()方法中編寫如下代碼
//創建工廠類 DiskFileItemFactory factory = new DiskFileItemFactory(); //創建請求解析器 ServletFileUpload fileUpload = new ServletFileUpload(factory); //設置上(shàng)傳單個(gè)文件的(de)的(de)大(dà)小 fileUpload.setFileSizeMax(1024*1024*3); //設置上(shàng)傳總文件的(de)大(dà)小 fileUpload.setSizeMax(1024*1024*3*10); //設置響應内容的(de)編碼 response.setContentType("text/html;charset=utf-8"); try { //解析請求信息,獲取FileItem的(de)集合 List<FileItem> items = fileUpload.parseRequest(request); //遍曆集合 for (FileItem fileItem : items) { //如果是(shì)普通的(de)表單項 if(fileItem.isFormField()){ //獲取參數名 String fieldName = fileItem.getFieldName(); //獲取參數值 String value = fileItem.getString("utf-8"); System.out.println(fieldName+" = "+value); //如果是(shì)文件表單項 }else{ //獲取文件名 String fileName = fileItem.getName(); //獲取上(shàng)傳路徑 String realPath = getServletContext().getRealPath("/WEB-INF/upload"); //檢查upload文件夾是(shì)否存在(zài),如果不(bù)存在(zài)則創建 File f = new File(realPath); if(!f.exists()){ f.mkdir(); }; //爲(wéi / wèi)避免重名生成一個(gè)uuid作爲(wéi / wèi)文件名的(de)前綴 String prefix = UUID.randomUUID().toString().replace("-", ""); //将文件寫入到(dào)服務器中 fileItem.write(new File(realPath+"/"+prefix+"_"+fileName)); //清楚文件緩存 fileItem.delete(); } } } catch (Exception e) { if(e instanceof SizeLimitExceededException){ //文件總大(dà)小超出(chū)限制 response.getWriter().print("上(shàng)傳文件的(de)總大(dà)小不(bù)能超過30M"); }else if(e instanceof FileSizeLimitExceededException){ //單個(gè)文件大(dà)小超出(chū)限制 response.getWriter().print("上(shàng)傳單個(gè)文件的(de)大(dà)小不(bù)能超過3M"); } } response.getWriter().print("上(shàng)傳成功");
第2章 文件的(de)下載
2.1 使用說(shuō)明
-
文件下載最直接的(de)方法就(jiù)是(shì)把文件直接放到(dào)服務器的(de)目錄中,用戶直接訪問該文件就(jiù)可以(yǐ)直接下載。
-
但是(shì)實際上(shàng)這(zhè)種方式并不(bù)一定好用,比如我們在(zài)服務器上(shàng)直接放置一個(gè)MP3文件,然後通過浏覽器訪問該文件的(de)地(dì / de)址,如果是(shì)IE浏覽器可能就(jiù)會彈出(chū)下載窗口,而(ér)如果是(shì)FireFox和(hé / huò)Chrome則有可能直接播放。再有就(jiù)是(shì)有一些文件我們是(shì)不(bù)希望用戶可以(yǐ)直接訪問到(dào)的(de),這(zhè)是(shì)我們就(jiù)要(yào / yāo)通過Servlet來(lái)完成下載功能。
-
下載文件的(de)關鍵是(shì)幾點:
-
服務器以(yǐ)一個(gè)流的(de)形式将文件發送給浏覽器。
-
發送流的(de)同時(shí)還需要(yào / yāo)設置幾個(gè)響應頭,來(lái)告訴浏覽器下載的(de)信息。
- 具體響應頭如下:
- Content-Type
- 下載文件的(de)MIME類型
- 可以(yǐ)通過servletContext. getMimeType(String file)獲取
- 也(yě)可以(yǐ)直接手動指定
- 使用response.setContentType(String type);
- 響應頭樣式:Content-Type: audio/mpeg
- Content-Disposition
- 下載文件的(de)名字,主要(yào / yāo)作用是(shì)提供一個(gè)默認的(de)用戶名
- 通過response.setHeader(“Content-Disposition”, disposition)設置
- 響應頭樣式:Content-Disposition: attachment; filename=xxx.mp3
- Content-Length
- 下載文件的(de)長度,用于(yú)設置文件的(de)長處(不(bù)必須)
- 通過response. setContentLength(int len)設置。
- 設置後樣式:Content-Length: 3140995
- Content-Type
- 具體響應頭如下:
-
接下來(lái)需要(yào / yāo)以(yǐ)輸入流的(de)形式讀入硬盤上(shàng)的(de)文件
- FileInputStream is = new FileInputStream(file);
- 這(zhè)個(gè)流就(jiù)是(shì)我們一會要(yào / yāo)發送給浏覽器的(de)内容
-
通過response獲取一個(gè)輸出(chū)流,并将文件(輸入流)通過該流發送給浏覽器
-
獲取輸出(chū)流:ServletOutputStream out = response.getOutputStream();
-
通過輸出(chū)流向浏覽器發送文件(不(bù)要(yào / yāo)忘了(le/liǎo)關閉輸入流)
byte[] b = new byte[1024]; int len = 0; while((len=is.read(b))> 0){ out.write(b, 0, len); } is.close();
-
-
2.2 步驟演示
-
一下步驟都是(shì)在(zài)同一個(gè)Servlet的(de)doGet()方法中編寫的(de)
-
我所下載的(de)文件是(shì)放在(zài)WEB-INF下mp3文件夾中的(de)文件
-
具體步驟
-
獲取文件的(de)流:
String realPath = getServletContext().getRealPath("/WEB-INF/mp3/中國(guó)話.mp3"); //獲取文件的(de)File對象 File file = new File(realPath); //獲取文件的(de)輸入流 FileInputStream is = new FileInputStream(file);
-
獲取頭信息:
//獲取文件的(de)MIME信息 String contentType = getServletContext().getMimeType(realPath); //設置下載文件的(de)名字 String filename = "zhongguohua.mp3"; //創建Content-Disposition信息 String disposition = "attachment; filename="+ filename ; //獲取文件長度 long size = file.length();
-
設置頭信息
//設置Content-Type response.setContentType(contentType); //設置Content-Disposition response.setHeader("Content-Disposition", disposition); //設置文件長度 response.setContentLength((int)size);
-
發送文件
//通過response獲取輸出(chū)流,用于(yú)向浏覽器輸出(chū)内容 ServletOutputStream out = response.getOutputStream(); //将文件輸入流通過輸出(chū)流輸出(chū) byte[] b = new byte[1024]; int len = 0; while((len=is.read(b))> 0){ out.write(b, 0, len); } //最後不(bù)要(yào / yāo)忘記關閉輸入流,輸出(chū)流由Tomcat自己處理,我們不(bù)用手動關閉 is.close();
-
2.3 亂碼
-
至此實際上(shàng)文件下載的(de)主要(yào / yāo)功能都已經完成。但是(shì)還有一個(gè)問題我們這(zhè)裏沒有體現出(chū)來(lái),因爲(wéi / wèi)目前我們的(de)文件名使用的(de)是(shì)純英文的(de),沒有亂碼問題。這(zhè)裏如果我們要(yào / yāo)使用中文文件名的(de)話,毫無疑問會出(chū)現亂碼問題。
-
解決此問題的(de)方法很簡單,在(zài)獲取文件名之(zhī)後爲(wéi / wèi)文件名進行編碼:
filename = java.net.URLEncoder.encode(filename,"utf-8");
-
但是(shì)注意這(zhè)裏火狐浏覽器比較特殊,因爲(wéi / wèi)他(tā)默認是(shì)以(yǐ)BASE64解碼的(de),所以(yǐ)這(zhè)塊如果需要(yào / yāo)考慮火狐的(de)問題的(de)話還需要(yào / yāo)特殊處理一下。
- 先要(yào / yāo)獲取客戶端信息(通過獲取請求頭中的(de)User-Agent信息)
//獲取客戶端信息 String ua = request.getHeader("User-Agent");
- 然後判斷浏覽器版本,做不(bù)同的(de)處理(通過判斷頭信息中是(shì)否包含Firefox字符串來(lái)判斷浏覽器版本)
//判斷客戶端是(shì)否爲(wéi / wèi)火狐 if(ua.contains("Firefox")){ //若爲(wéi / wèi)火狐使用BASE64編碼 filename = "=?utf-8?B?"+new BASE64Encoder() .encode(filename.getBytes("utf-8"))+"?="; }else{ //否則使用UTF-8 filename = URLEncoder.encode(filename,"utf-8"); }
完整代碼:
//設置響應字符集
response.setContentType("text/html; charset=UTF-8");
DiskFileItemFactory diskFileItemFactory=new DiskFileItemFactory();
//創建解析器
ServletFileUpload servletFileUpload=new ServletFileUpload(diskFileItemFactory);
//設置上(shàng)傳文件的(de)大(dà)小
servletFileUpload.setFileSizeMax(1024*500);
//獲取上(shàng)傳的(de)真實路徑
String realPath = getServletContext().getRealPath("/upload");
//優化1:如果文件路勁不(bù)存在(zài),則重新創建一個(gè)
File refile=new File(realPath);
if(refile.exists()==false){
refile.mkdirs();
}
//優化2:若同名文件 則添加不(bù)上(shàng),因此解決同名文件,讓其添加上(shàng)。
//優化3:設置上(shàng)傳文件大(dà)小
//在(zài)前台傳來(lái)的(de)文件雖然爲(wéi / wèi)同一個(gè)文件,但是(shì)在(zài)後台我們可以(yǐ)将文件名進行擴展。
// 通過ServletFileUpload中的(de)List<FileItem> parseRequest(request),将request解析爲(wéi / wèi)List<FileItem>
try {
List<FileItem> fileItems = servletFileUpload.parseRequest(request);
//遍曆集合
for (FileItem fileItem : fileItems) {
if(fileItem.isFormField()==false){
//獲取文件名
String fname = fileItem.getName();
//處理文件名
String replacename = UUID.randomUUID().toString().replace("-", "");
//将文件上(shàng)傳到(dào)服務器的(de)指定位置
File file=new File(realPath+File.separator+replacename+fname);
fileItem.write(file);
PrintWriter writer = response.getWriter();
writer.write("upload success");
}
}
}
catch (FileUploadBase.FileSizeLimitExceededException e){
response.getWriter().write("上(shàng)傳文件大(dà)小超過50KB");
}
catch (Exception e) {
e.printStackTrace();
}
jsp代碼:
<%--
Created by IntelliJ IDEA.
User: admin
Date: 2020/8/19
Time: 16:29
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>下載demo</title>
</head>
<body>
<a href="DownServlet?fname=gson.jar">jar包下載</a>
<a href="DownServlet?fname=a698e5967c4d4c23a895ce42e3289d260.jpg">千庫網_粉色背景裏的(de)玫瑰花束_攝影圖編号73147.jpg</a>
<a href="">xxx.pptx</a>
</body>
</html>