JavaWeb實(shí)現(xiàn)文件上傳下載功能實(shí)例詳解
在Web應(yīng)用系統(tǒng)開發(fā)中,文件上傳和下載功能是非常常用的功能,今天來講一下JavaWeb中的文件上傳和下載功能的實(shí)現(xiàn)。
文件上傳概述
1、文件上傳的作用
例如網(wǎng)絡(luò)硬盤!就是用來上傳下載文件的。
在智聯(lián)招聘上填寫一個(gè)完整的簡歷還需要上傳照片呢。
2、文件上傳對(duì)頁面的要求
上傳文件的要求比較多,需要記一下:
必須使用表單,而不能是超鏈接
表單的method必須是POST,而不能是GET
表單的enctype必須是multipart/form-data
在表單中添加file表單字段,即<input type=”file” name=”xxx”/>
<form action="${pageContext.request.contextPath }/FileUploadServlet" method="post" enctype="multipart/form-data"> 用戶名:<input type="text" name="username"/><br/> 文件1:<input type="file" name="file1"/><br/> 文件2:<input type="file" name="file2"/><br/> <input type="submit" value="提交"/> </form>
3、比對(duì)文件上傳表單和普通文本表單的區(qū)別
通過httpWatch查看“文件上傳表單”和“普通文本表單”的區(qū)別。
文件上傳表單的enctype=”multipart/form-data”,表示多部件表單數(shù)據(jù);
普通文本表單可以不設(shè)置enctype屬性:
當(dāng)method=”post”時(shí),enctype的默認(rèn)值為application/x-www-form-urlencoded,表示使用url編碼正文
當(dāng)method=”get”時(shí),enctype的默認(rèn)值為null,沒有正文,所以就不需要enctype了
對(duì)普通文本表單的測(cè)試:
<form action="${pageContext.request.contextPath }/FileUploadServlet" method="post"> 用戶名:<input type="text" name="username"/><br/> 文件1:<input type="file" name="file1"/><br/> 文件2:<input type="file" name="file2"/><br/> <input type="submit" value="提交"/> </form>
通過httpWatch測(cè)試,查看表單的請(qǐng)求數(shù)據(jù)正文,我們發(fā)現(xiàn)請(qǐng)求中只有文件名稱,而沒有文件內(nèi)容。也就是說,當(dāng)表單的enctype不是multipart/form-data時(shí),請(qǐng)求中不包含文件內(nèi)容,而只有文件的名稱,這說明普通文本表單中input:file與input:text沒什么區(qū)別了。
對(duì)文件上傳表單的測(cè)試:
<form action="${pageContext.request.contextPath }/FileUploadServlet" method="post" enctype="multipart/form-data"> 用戶名:<input type="text" name="username"/><br/> 文件1:<input type="file" name="file1"/><br/> 文件2:<input type="file" name="file2"/><br/> <input type="submit" value="提交"/> </form>
通過httpWatch測(cè)試,查看表單的請(qǐng)求數(shù)據(jù)正文部分,發(fā)現(xiàn)正文部分是由多個(gè)部件組成,每個(gè)部件對(duì)應(yīng)一個(gè)表單字段,每個(gè)部件都有自己的頭信息。頭信息下面是空行,空行下面是字段的正文部分。多個(gè)部件之間使用隨機(jī)生成的分隔線隔開。
文本字段的頭信息中只包含一條頭信息,即Content-Disposition,這個(gè)頭信息的值有兩個(gè)部分,第一部分是固定的,即form-data,第二部分為字段的名稱。在空行后面就是正文部分了,正文部分就是在文本框中填寫的內(nèi)容。
文件字段的頭信息中包含兩條頭信息,Content-Disposition和Content-Type。Content-Disposition中多出一個(gè)filename,它指定的是上傳的文件名稱。而Content-Type指定的是上傳文件的類型。文件字段的正文部分就是文件的內(nèi)容。
請(qǐng)注意,因?yàn)槲覀兩蟼鞯奈募际瞧胀ㄎ谋疚募磘xt文件,所以在httpWatch中是可以正常顯示的,如果上傳的是exe、mp3等文件,那么在httpWatch看到的就是亂碼了
4、文件上傳對(duì)Servlet的要求
當(dāng)提交的表單是文件上傳表單時(shí),那么對(duì)Servlet也是有要求的。
首先我們要肯定一點(diǎn),文件上傳表單的數(shù)據(jù)也是被封裝到request對(duì)象中的。
request.getParameter(String)方法獲取指定的表單字段字符內(nèi)容,但文件上傳表單已經(jīng)不在是字符內(nèi)容,而是字節(jié)內(nèi)容,所以失效。
這時(shí)可以使用request的getInputStream()方法獲取ServletInputStream對(duì)象,它是InputStream的子類,這個(gè)ServletInputStream對(duì)象對(duì)應(yīng)整個(gè)表單的正文部分(從第一個(gè)分隔線開始,到最后),這說明我們需要的解析流中的數(shù)據(jù)。當(dāng)然解析它是很麻煩的一件事情,而Apache已經(jīng)幫我們提供了解析它的工具:commons-fileupload
可以嘗試把request.getInputStream()這個(gè)流中的內(nèi)容打印出來,再對(duì)比httpWatch中的請(qǐng)求數(shù)據(jù)
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { InputStream in = request.getInputStream(); String s = IOUtils.toString(in); System.out.println(s); }
-----------------------------7ddd3370ab2 Content-Disposition: form-data; name="username" hello -----------------------------7ddd3370ab2 Content-Disposition: form-data; name="file1"; filename="a.txt" Content-Type: text/plain aaa -----------------------------7ddd3370ab2 Content-Disposition: form-data; name="file2"; filename="b.txt" Content-Type: text/plain bbb -----------------------------7ddd3370ab2--
commons-fileupload
為什么使用fileupload:
上傳文件的要求比較多,需要記一下:
必須是POST表單;
表單的enctype必須是multipart/form-data;
在表單中添加file表單字段,即
Servlet的要求:
不能再使用request.getParameter()來獲取表單數(shù)據(jù)
可以使用request.getInputStream()得到所有的表單數(shù)據(jù),而不是一個(gè)表單項(xiàng)的數(shù)據(jù)
這說明不使用fileupload,我們需要自己來對(duì)request.getInputStream()的內(nèi)容進(jìn)行解析
1、fileupload概述
fileupload是由apache的commons組件提供的上傳組件。它最主要的工作就是幫我們解析request.getInputStream()
fileupload組件需要的JAR包有:
commons-fileupload.jar,核心包
commons-io.jar,依賴包
2、fileupload簡單應(yīng)用
fileupload的核心類有:DiskFileItemFactory、ServletFileUpload、FileItem
使用fileupload組件的步驟如下:
//1.創(chuàng)建工廠類DiskFileItemFactory對(duì)象 DiskFileItemFactory factory = new DiskFileItemFactory(); //2.使用工廠創(chuàng)建解析器對(duì)象 ServletFileUpload fileUpload = new ServletFileUpload(factory); //3.使用解析器來解析request對(duì)象 List<FileItem> list = fileUpload.parseRequest(request);
DiskFileItemFactory 磁盤文件項(xiàng)工廠類
public DiskFileItemFactory(int sizeThreshold, File repository)
構(gòu)造工廠時(shí),指定內(nèi)存緩沖區(qū)大小和臨時(shí)文件存放位置
public void setSizeThreshold(int sizeThreshold)
設(shè)置內(nèi)存緩沖區(qū)大小,默認(rèn)10K
public void setRepository(File repository)
設(shè)置臨時(shí)文件存放位置,默認(rèn)System.getProperty(“java.io.tmpdir”).
內(nèi)存緩沖區(qū): 上傳文件時(shí),上傳文件的內(nèi)容優(yōu)先保存在內(nèi)存緩沖區(qū)中,當(dāng)上傳文件大小超過緩沖區(qū)大小,就會(huì)在服務(wù)器端產(chǎn)生臨時(shí)文件
臨時(shí)文件存放位置: 保存超過了內(nèi)存緩沖區(qū)大小上傳文件而產(chǎn)生臨時(shí)文件 ,產(chǎn)生臨時(shí)文件可以通過 FileItem的delete()方法刪除
FileItem 表示文件上傳表單中 每個(gè)數(shù)據(jù)部分
隆重介紹FileItem類,它才是我們最終要的結(jié)果。一個(gè)FileItem對(duì)象對(duì)應(yīng)一個(gè)表單項(xiàng)(表單字段)。一個(gè)表單中存在文件字段和普通字段,可以使用FileItem類的isFormField()方法來判斷表單字段是否為普通字段,如果不是普通字段,那么就是文件字段了
注意事項(xiàng):因?yàn)槲募蟼鞅韱尾捎镁幋a方式multipart/form-data 與傳統(tǒng)url編碼不同,所有g(shù)etParameter ()方法不能使用 setCharacterEncoding()無法解決輸入項(xiàng)亂碼問題
ServletFileUpload 文件上傳核心類
3、簡單上傳示例
寫一個(gè)簡單的上傳示例:
表單包含一個(gè)用戶名字段,以及一個(gè)文件字段;
Servlet保存上傳的文件到uploads目錄,顯示用戶名,文件名,文件大小,文件類型。
第一步:
完成index.jsp,只需要一個(gè)表單。注意表單必須是post的,而且enctype必須是mulitpart/form-data的
<form action="${pageContext.request.contextPath }/FileUploadServlet" method="post" enctype="multipart/form-data"> 用戶名:<input type="text" name="username"/><br/> 文件1:<input type="file" name="file1"/><br/> <input type="submit" value="提交"/> </form>
第二步:完成FileUploadServlet
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 因?yàn)橐褂胷esponse打印,所以設(shè)置其編碼 response.setContentType("text/html;charset=utf-8"); // 創(chuàng)建工廠 DiskFileItemFactory dfif = new DiskFileItemFactory(); // 使用工廠創(chuàng)建解析器對(duì)象 ServletFileUpload fileUpload = new ServletFileUpload(dfif); try { // 使用解析器對(duì)象解析request,得到FileItem列表 List<FileItem> list = fileUpload.parseRequest(request); // 遍歷所有表單項(xiàng) for(FileItem fileItem : list) { // 如果當(dāng)前表單項(xiàng)為普通表單項(xiàng) if(fileItem.isFormField()) { // 獲取當(dāng)前表單項(xiàng)的字段名稱 String fieldName = fileItem.getFieldName(); // 如果當(dāng)前表單項(xiàng)的字段名為username if(fieldName.equals("username")) { // 打印當(dāng)前表單項(xiàng)的內(nèi)容,即用戶在username表單項(xiàng)中輸入的內(nèi)容 response.getWriter().print("用戶名:" + fileItem.getString() + "<br/>"); } } else {//如果當(dāng)前表單項(xiàng)不是普通表單項(xiàng),說明就是文件字段 String name = fileItem.getName();//獲取上傳文件的名稱 // 如果上傳的文件名稱為空,即沒有指定上傳文件 if(name == null || name.isEmpty()) { continue; } // 獲取真實(shí)路徑,對(duì)應(yīng)${項(xiàng)目目錄}/uploads,當(dāng)然,這個(gè)目錄必須存在 String savepath = this.getServletContext().getRealPath("/uploads"); // 通過uploads目錄和文件名稱來創(chuàng)建File對(duì)象 File file = new File(savepath, name); // 把上傳文件保存到指定位置 fileItem.write(file); // 打印上傳文件的名稱 response.getWriter().print("上傳文件名:" + name + "<br/>"); // 打印上傳文件的大小 response.getWriter().print("上傳文件大?。? + fileItem.getSize() + "<br/>"); // 打印上傳文件的類型 response.getWriter().print("上傳文件類型:" + fileItem.getContentType() + "<br/>"); } } } catch (Exception e) { throw new ServletException(e); } }
文件上傳之細(xì)節(jié)
1、把上傳的文件放到WEB-INF目錄下
如果沒有把用戶上傳的文件存放到WEB-INF目錄下,那么用戶就可以通過瀏覽器直接訪問上傳的文件,這是非常危險(xiǎn)的。
假如說用戶上傳了一個(gè)a.jsp文件,然后用戶在通過瀏覽器去訪問這個(gè)a.jsp文件,那么就會(huì)執(zhí)行a.jsp中的內(nèi)容,如果在a.jsp中有如下語句:Runtime.getRuntime().exec(“shutdown –s –t 1”);,那么你就會(huì)…
通常我們會(huì)在WEB-INF目錄下創(chuàng)建一個(gè)uploads目錄來存放上傳的文件,而在Servlet中找到這個(gè)目錄需要使用ServletContext的getRealPath(String)方法,例如在我的upload1項(xiàng)目中有如下語句:
ServletContext servletContext = this.getServletContext(); String savepath = servletContext.getRealPath(“/WEB-INF/uploads”);
其中savepath為:F:\tomcat6_1\webapps\upload1\WEB-INF\uploads。
2、文件名稱(完整路徑、文件名稱)
上傳文件名稱可能是完整路徑:
IE6獲取的上傳文件名稱是完整路徑,而其他瀏覽器獲取的上傳文件名稱只是文件名稱而已。瀏覽器差異的問題我們還是需要處理一下的
String name = file1FileItem.getName(); response.getWriter().print(name);
使用不同瀏覽器測(cè)試,其中IE6就會(huì)返回上傳文件的完整路徑,不知道IE6在搞什么,這給我們帶來了很大的麻煩,就是需要處理這一問題。
處理這一問題也很簡單,無論是否為完整路徑,我們都去截取最后一個(gè)“\”后面的內(nèi)容就可以了
String name = file1FileItem.getName(); int lastIndex = name.lastIndexOf("\\");//獲取最后一個(gè)“\”的位置 if(lastIndex != -1) {//注意,如果不是完整路徑,那么就不會(huì)有“\”的存在。 name = name.substring(lastIndex + 1);//獲取文件名稱 } response.getWriter().print(name);
3、中文亂碼問題
上傳文件名稱中包含中文:
當(dāng)上傳的誰的名稱中包含中文時(shí),需要設(shè)置編碼,commons-fileupload組件為我們提供了兩種設(shè)置編碼的方式:
request.setCharacterEncoding(String):這種方式是我們最為熟悉的方式了
fileUpload.setHeaderEncdoing(String):這種方式的優(yōu)先級(jí)高與前一種
上傳文件的文件內(nèi)容包含中文:
通常我們不需關(guān)心上傳文件的內(nèi)容,因?yàn)槲覀儠?huì)把上傳文件保存到硬盤上!也就是說,文件原來是什么樣子,到服務(wù)器這邊還是什么樣子!
但是如果你有這樣的需求,非要在控制臺(tái)顯示上傳的文件內(nèi)容,那么你可以使用fileItem.getString(“utf-8”)來處理編碼
文本文件內(nèi)容和普通表單項(xiàng)內(nèi)容使用FileItem類的getString(“utf-8”)來處理編碼。
4、上傳文件同名問題(文件重命名)
通常我們會(huì)把用戶上傳的文件保存到uploads目錄下,但如果用戶上傳了同名文件呢?這會(huì)出現(xiàn)覆蓋的現(xiàn)象。處理這一問題的手段是使用UUID生成唯一名稱,然后再使用“_”連接文件上傳的原始名稱
例如用戶上傳的文件是“我的一寸照片.jpg”,在通過處理后,文件名稱為:“891b3881395f4175b969256a3f7b6e10_我的一寸照片.jpg”,這種手段不會(huì)使文件丟失擴(kuò)展名,并且因?yàn)閁UID的唯一性,上傳的文件同名,但在服務(wù)器端是不會(huì)出現(xiàn)同名問題的
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); DiskFileItemFactory dfif = new DiskFileItemFactory(); ServletFileUpload fileUpload = new ServletFileUpload(dfif); try { List<FileItem> list = fileUpload.parseRequest(request); //獲取第二個(gè)表單項(xiàng),因?yàn)榈谝粋€(gè)表單項(xiàng)是username,第二個(gè)才是file表單項(xiàng) FileItem fileItem = list.get(1); String name = fileItem.getName();//獲取文件名稱 // 如果客戶端使用的是IE6,那么需要從完整路徑中獲取文件名稱 int lastIndex = name.lastIndexOf("\\"); if(lastIndex != -1) { name = name.substring(lastIndex + 1); } // 獲取上傳文件的保存目錄 String savepath = this.getServletContext().getRealPath("/WEB-INF/uploads"); String uuid = CommonUtils.uuid();//生成uuid String filename = uuid + "_" + name;//新的文件名稱為uuid + 下劃線 + 原始名稱 //創(chuàng)建file對(duì)象,下面會(huì)把上傳文件保存到這個(gè)file指定的路徑 //savepath,即上傳文件的保存目錄 //filename,文件名稱 File file = new File(savepath, filename); // 保存文件 fileItem.write(file); } catch (Exception e) { throw new ServletException(e); } }
5、一個(gè)目錄不能存放過多的文件(存放目錄打散)
一個(gè)目錄下不應(yīng)該存放過多的文件,一般一個(gè)目錄存放1000個(gè)文件就是上限了,如果在多,那么打開目錄時(shí)就會(huì)很“卡”。你可以嘗試打印C:\WINDOWS\system32目錄,你會(huì)感覺到的
也就是說,我們需要把上傳的文件放到不同的目錄中。但是也不能為每個(gè)上傳的文件一個(gè)目錄,這種方式會(huì)導(dǎo)致目錄過多。所以我們應(yīng)該采用某種算法來“打散”!
打散的方法有很多,例如使用日期來打散,每天生成一個(gè)目錄。也可以使用文件名的首字母來生成目錄,相同首字母的文件放到同一目錄下。
日期打散算法:如果某一天上傳的文件過多,那么也會(huì)出現(xiàn)一個(gè)目錄文件過多的情況;
首字母打散算法:如果文件名是中文的,因?yàn)橹形倪^多,所以會(huì)導(dǎo)致目錄過多的現(xiàn)象。
我們這里使用hash算法來打散:
獲取文件名稱的hashCode:int hCode = name.hashCode()
獲取hCode的低4位,然后轉(zhuǎn)換成16進(jìn)制字符
獲取hCode的5~8位,然后轉(zhuǎn)換成16進(jìn)制字符
使用這兩個(gè)16進(jìn)制的字符生成目錄鏈。例如低4位字符為“5”
這種算法的好處是,在uploads目錄下最多生成16個(gè)目錄,而每個(gè)目錄下最多再生成16個(gè)目錄,即256個(gè)目錄,所有上傳的文件都放到這256個(gè)目錄下。如果每個(gè)目錄上限為1000個(gè)文件,那么一共可以保存256000個(gè)文件
例如上傳文件名稱為:新建 文本文檔.txt,那么把“新建 文本文檔.txt”的哈希碼獲取到,再獲取哈希碼的低4位,和5~8位。假如低4位為:9,5~8位為1,那么文件的保存路徑為uploads/9/1/
int hCode = name.hashCode();//獲取文件名的hashCode //獲取hCode的低4位,并轉(zhuǎn)換成16進(jìn)制字符串 String dir1 = Integer.toHexString(hCode & 0xF); //獲取hCode的低5~8位,并轉(zhuǎn)換成16進(jìn)制字符串 String dir2 = Integer.toHexString(hCode >>> 4 & 0xF); //與文件保存目錄連接成完整路徑 savepath = savepath + "/" + dir1 + "/" + dir2; //因?yàn)檫@個(gè)路徑可能不存在,所以創(chuàng)建成File對(duì)象,再創(chuàng)建目錄鏈,確保目錄在保存文件之前已經(jīng)存在 new File(savepath).mkdirs();
6、上傳的單個(gè)文件的大小限制
限制上傳文件的大小很簡單,ServletFileUpload類的setFileSizeMax(long)就可以了。參數(shù)就是上傳文件的上限字節(jié)數(shù),例如servletFileUpload.setFileSizeMax(1024*10)表示上限為10KB。
一旦上傳的文件超出了上限,那么就會(huì)拋出FileUploadBase.FileSizeLimitExceededException異常。我們可以在Servlet中獲取這個(gè)異常,然后向頁面輸出“上傳的文件超出限制”。
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); DiskFileItemFactory dfif = new DiskFileItemFactory(); ServletFileUpload fileUpload = new ServletFileUpload(dfif); // 設(shè)置上傳的單個(gè)文件的上限為10KB fileUpload.setFileSizeMax(1024 * 10); try { List<FileItem> list = fileUpload.parseRequest(request); //獲取第二個(gè)表單項(xiàng),因?yàn)榈谝粋€(gè)表單項(xiàng)是username,第二個(gè)才是file表單項(xiàng) FileItem fileItem = list.get(1); String name = fileItem.getName();//獲取文件名稱 // 如果客戶端使用的是IE6,那么需要從完整路徑中獲取文件名稱 int lastIndex = name.lastIndexOf("\\"); if(lastIndex != -1) { name = name.substring(lastIndex + 1); } // 獲取上傳文件的保存目錄 String savepath = this.getServletContext().getRealPath("/WEB-INF/uploads"); String uuid = CommonUtils.uuid();//生成uuid String filename = uuid + "_" + name;//新的文件名稱為uuid + 下劃線 + 原始名稱 int hCode = name.hashCode();//獲取文件名的hashCode //獲取hCode的低4位,并轉(zhuǎn)換成16進(jìn)制字符串 String dir1 = Integer.toHexString(hCode & 0xF); //獲取hCode的低5~8位,并轉(zhuǎn)換成16進(jìn)制字符串 String dir2 = Integer.toHexString(hCode >>> 4 & 0xF); //與文件保存目錄連接成完整路徑 savepath = savepath + "/" + dir1 + "/" + dir2; //因?yàn)檫@個(gè)路徑可能不存在,所以創(chuàng)建成File對(duì)象,再創(chuàng)建目錄鏈,確保目錄在保存文件之前已經(jīng)存在 new File(savepath).mkdirs(); //創(chuàng)建file對(duì)象,下面會(huì)把上傳文件保存到這個(gè)file指定的路徑 //savepath,即上傳文件的保存目錄 //filename,文件名稱 File file = new File(savepath, filename); // 保存文件 fileItem.write(file); } catch (Exception e) { // 判斷拋出的異常的類型是否為FileUploadBase.FileSizeLimitExceededException // 如果是,說明上傳文件時(shí)超出了限制。 if(e instanceof FileUploadBase.FileSizeLimitExceededException) { // 在request中保存錯(cuò)誤信息 request.setAttribute("msg", "上傳失?。∩蟼鞯奈募隽?0KB!"); // 轉(zhuǎn)發(fā)到index.jsp頁面中!在index.jsp頁面中需要使用${msg}來顯示錯(cuò)誤信息 request.getRequestDispatcher("/index.jsp").forward(request, response); return; } throw new ServletException(e); } }
7、上傳文件的總大小限制
上傳文件的表單中可能允許上傳多個(gè)文件,例如:
有時(shí)我們需要限制一個(gè)請(qǐng)求的大小。也就是說這個(gè)請(qǐng)求的最大字節(jié)數(shù)(所有表單項(xiàng)之和)!實(shí)現(xiàn)這一功能也很簡單,只需要調(diào)用ServletFileUpload類的setSizeMax(long)方法即可。
例如fileUpload.setSizeMax(1024 * 10);,顯示整個(gè)請(qǐng)求的上限為10KB。當(dāng)請(qǐng)求大小超出10KB時(shí),ServletFileUpload類的parseRequest()方法會(huì)拋出FileUploadBase.SizeLimitExceededException異常。
8、緩存大小與臨時(shí)目錄
大家想一想,如果我上傳一個(gè)藍(lán)光電影,先把電影保存到內(nèi)存中,然后再通過內(nèi)存copy到服務(wù)器硬盤上,那么你的內(nèi)存能吃的消么?
所以fileupload組件不可能把文件都保存在內(nèi)存中,fileupload會(huì)判斷文件大小是否超出10KB,如果是那么就把文件保存到硬盤上,如果沒有超出,那么就保存在內(nèi)存中。
10KB是fileupload默認(rèn)的值,我們可以來設(shè)置它。
當(dāng)文件保存到硬盤時(shí),fileupload是把文件保存到系統(tǒng)臨時(shí)目錄,當(dāng)然你也可以去設(shè)置臨時(shí)目錄
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); DiskFileItemFactory dfif = new DiskFileItemFactory(1024*20, new File("F:\\temp")); ServletFileUpload fileUpload = new ServletFileUpload(dfif); try { List<FileItem> list = fileUpload.parseRequest(request); FileItem fileItem = list.get(1); String name = fileItem.getName(); String savepath = this.getServletContext().getRealPath("/WEB-INF/uploads"); // 保存文件 fileItem.write(path(savepath, name)); } catch (Exception e) { throw new ServletException(e); } } private File path(String savepath, String filename) { // 從完整路徑中獲取文件名稱 int lastIndex = filename.lastIndexOf("\\"); if(lastIndex != -1) { filename = filename.substring(lastIndex + 1); } // 通過文件名稱生成一級(jí)、二級(jí)目錄 int hCode = filename.hashCode(); String dir1 = Integer.toHexString(hCode & 0xF); String dir2 = Integer.toHexString(hCode >>> 4 & 0xF); savepath = savepath + "/" + dir1 + "/" + dir2; // 創(chuàng)建目錄 new File(savepath).mkdirs(); // 給文件名稱添加uuid前綴 String uuid = CommonUtils.uuid(); filename = uuid + "_" + filename; // 創(chuàng)建文件完成路徑 return new File(savepath, filename); }
文件下載
1、通過Servlet下載1
被下載的資源必須放到WEB-INF目錄下(只要用戶不能通過瀏覽器直接訪問就OK),然后通過Servlet完成下載。
在jsp頁面中給出超鏈接,鏈接到DownloadServlet,并提供要下載的文件名稱。然后DownloadServlet獲取文件的真實(shí)路徑,然后把文件寫入到response.getOutputStream()流中。
download.jsp
<body> This is my JSP page. <br> <a href="<c:url value='/DownloadServlet?path=a.avi'/>">a.avi</a><br/> <a href="<c:url value='/DownloadServlet?path=a.jpg'/>">a.jpg</a><br/> <a href="<c:url value='/DownloadServlet?path=a.txt'/>">a.txt</a><br/> </body>
DownloadServlet.java
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String filename = request.getParameter("path"); String filepath = this.getServletContext().getRealPath("/WEB-INF/uploads/" + filename); File file = new File(filepath); if(!file.exists()) { response.getWriter().print("您要下載的文件不存在!"); return; } IOUtils.copy(new FileInputStream(file), response.getOutputStream()); }
上面代碼有如下問題:
1.可以下載a.avi,但在下載框中的文件名稱是DownloadServlet;
2.不能下載a.jpg和a.txt,而是在頁面中顯示它們。
2、通過Servlet下載2
下面來處理上一例中的問題,讓下載框中可以顯示正確的文件名稱,以及可以下載a.jpg和a.txt文件
通過添加content-disposition頭來處理上面問題。當(dāng)設(shè)置了content-disposition頭后,瀏覽器就會(huì)彈出下載框
而且還可以通過content-disposition頭來指定下載文件的名稱!
String filename = request.getParameter("path"); String filepath = this.getServletContext().getRealPath("/WEB-INF/uploads/" + filename); File file = new File(filepath); if(!file.exists()) { response.getWriter().print("您要下載的文件不存在!"); return; } response.addHeader("content-disposition", "attachment;filename=" + filename); IOUtils.copy(new FileInputStream(file), response.getOutputStream());
雖然上面的代碼已經(jīng)可以處理txt和jpg等文件的下載問題,并且也處理了在下載框中顯示文件名稱的問題,但是如果下載的文件名稱是中文的,那么還是不行的
3、通過Servlet下載3
下面是處理在下載框中顯示中文的問題!
其實(shí)這一問題很簡單,只需要通過URL來編碼中文即可!
download.jsp
<a href="<c:url value='/DownloadServlet?path=這個(gè)殺手不太冷.avi'/>">這個(gè)殺手不太冷.avi</a><br/> <a href="<c:url value='/DownloadServlet?path=白冰.jpg'/>">白冰.jpg</a><br/> <a href="<c:url value='/DownloadServlet?path=說明文檔.txt'/>">說明文檔.txt</a><br/>
DownloadServlet.java
String filename = request.getParameter("path"); // GET請(qǐng)求中,參數(shù)中包含中文需要自己動(dòng)手來轉(zhuǎn)換。 // 當(dāng)然如果你使用了“全局編碼過濾器”,那么這里就不用處理了 filename = new String(filename.getBytes("ISO-8859-1"), "UTF-8"); String filepath = this.getServletContext().getRealPath("/WEB-INF/uploads/" + filename); File file = new File(filepath); if(!file.exists()) { response.getWriter().print("您要下載的文件不存在!"); return; } // 所有瀏覽器都會(huì)使用本地編碼,即中文操作系統(tǒng)使用GBK // 瀏覽器收到這個(gè)文件名后,會(huì)使用iso-8859-1來解碼 filename = new String(filename.getBytes("GBK"), "ISO-8859-1"); response.addHeader("content-disposition", "attachment;filename=" + filename); IOUtils.copy(new FileInputStream(file), response.getOutputStream());
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Spring Boot項(xiàng)目中定制攔截器的方法詳解
這篇文章主要介紹了Spring Boot項(xiàng)目中定制攔截器的方法詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10Spring Boot + Thymeleaf + Activiti 快速開發(fā)平臺(tái)項(xiàng)目 附源碼
這篇文章主要介紹了Spring Boot + Thymeleaf + Activiti 快速開發(fā)平臺(tái)項(xiàng)目附源碼,代碼簡單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04