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