Javaweb學習筆記——(二十二)——————檔案上傳、下載、Javamail
檔案上傳概述 1.檔案上傳的作用 例如網路硬碟,就是用來上傳下載檔案的。 在網路瀏覽器中,時常需要上傳照片
2.檔案上傳對頁面的要求 上傳檔案的要求比較多。需要記住 1.必須使用表單,而且不能是超連結。 2.表單的method必須是POST,不能是GET; 3.表單的enctype必須是meltipart/form-data 4.在表單中新增file表單欄位,即<input type="file".../>
<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.比對檔案上傳表單和普通文字表單的區別 通過httpWatch檢視“檔案上傳表單”和“普通文字表單”的區別 *檔案上傳表單是enctype="multipart/form-data",表示多部件表單資料; *普通檔案表單可以不設定enctype屬性: **當method="post"時,enctype的預設值為application/x-www-form-urlencoded,表示使用url編碼正文; **當method="get"時,enctype的預設值為null,沒有正文,所以就不需要enctype了。
對普通文字表單的測試: <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測試,查看錶單的請求資料正文,我們發現請求中只有檔名稱,而沒有檔案內容。也就是說,當表單的enctype不是multipart/form -data時,請求中不包含檔案內容,而只用檔案的民粹,這說明普通文字表單中input:file和input:text沒有什麼區別。
對檔案上傳表單的測試 <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測試,查看錶單的請求資料正文部分,發現正文部位是由多個部件組成,每個部件對應一個表單欄位,每個部件都有自己的頭資訊。頭資訊下面是空行,空行下面是欄位是正文部分。多個部件之間使用隨機生成的分割線隔開。 文字欄位的頭資訊中只包含一條頭資訊,即Content-Disposition,這個頭資訊的值有兩部分,第一部分是固定的,即form-data,第二部分為欄位的名稱。在空行後面就是正文部分了,正文部分就是在文字框中填寫的內容。 檔案欄位的頭資訊中包含了頭資訊,Content-Disposition和Content-Type。Content-Disposition中多出了一個filename,它指定的是上傳的檔名稱。而Content-Type指定的是上傳檔案的型別。檔案欄位的正文部分就是檔案的內容。
請注意,因為我們上傳的檔案都是普通文字檔案,即txt檔案,所以在httpWatch中是可以正常顯示的,如果上傳的是exe、mp3等檔案,那麼在httpWatch中看到的就是亂碼了。
4.檔案上傳對Servlet的要求 當提交的表單是檔案上傳表單時,那麼對Servlet也是有要求的。首先我們要肯定一點,檔案上傳表單的資料也是被封裝到request物件中的。 request.getParameter(String)方法獲取指定的表單欄位字元內容,單檔案上傳表單已經不再是字元內容,而是位元組內容,所以失效。
這時可以使用request的getInputStream()方法獲取ServletInputStream物件,它是InputStream的子類,這個ServletInputStream物件對應整個表單的正文部分(從第一個分隔線開始,到最後),這說明我們需要的解析流中的資料。當然解析它是很麻煩的一件事情,而Apache已經幫我們提供了了解它的工具:commons-fileupload。
可以嘗試吧request.getInputStream()這個流中的內容打印出來,再對比httpWatch中請求資料。 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 為什麼使用filleupload: 上傳檔案的要求比較多,需要記一下: *必須是POST表單; *表單的enctype必須是multipart/form-data; *在表單中新增file表單欄位,即<input type="file" .../>
Servlet的要求: *不能再使用request.getParameter()來獲取表單資料; *可以使用request.getInputStream()得到所有的表單資料,而不是一個表單項的資料; *這說明不適用fileupload,我們需要自己來對request.getInputStream()的內容進行解析。
1.fileupload概述 fileupload是由apache的commons元件提供的上傳元件。它最主要的工作就是幫我們解析request.getInputStream()。 fileupload元件需要的jar包有: *commons-fileupload.jar,核心包; *commons-io.jar,依賴包。
2.fileupload簡單應用 fileupload的核心類有:DiskFileItemFactory、ServletFileUpload、FileItem。 使員工fileupload元件的步驟如下: 1.建立工廠類DiskFileItemFactory物件:DiskFileItemFactory factory = new DiskFileItemFactory() 2.使用工廠建立解析器物件:ServetFileUpload fileUpload = new ServletFileUpload(factory); 3.使用解析器來解析request物件:List<FileItem> list = fileUpload.parseRquest(request);
隆重介紹FileItem類,它才是我們最終想要的結果。一個FileItem物件對應一個表單項(表單欄位)。一個表單中存在檔案欄位和普通欄位,可以使用FileItem類的isFormField()方法來判斷表單欄位是否為普通欄位,如果不是普通欄位,那麼就是檔案欄位了。 *String getName():獲取檔案欄位的檔名稱; *String getString():獲取欄位的內容,如果是檔案欄位,那麼獲取的是檔案內容,當然上傳的檔案必須是文字檔案; *String getFieldName():獲取欄位名稱,例如:<input type="text" name="username"/>,返回的是username; *String getContentType():獲取上傳的檔案的型別,例如:text/plain。 *int getSize():獲取上傳檔案的大小; *boolean isFormField():判斷當前表單欄位是否為普通文字欄位,如果返回false,說明是檔案欄位; *InputStream getInputStream():獲取上傳檔案對應的輸入流; *void write(File):把上傳的檔案儲存到指定檔案中。
3.簡單上傳示例 寫一個簡單的上傳示例: *表單包含一個使用者名稱欄位,已經一個檔案欄位; *Servlet儲存上傳的檔案到uploads目錄,顯示使用者名稱,檔名,檔案大小,檔案型別。
第一步: 完成index.jsp,只需要一個表單。注意表單必須是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 { // 因為要使用response列印,所以設定其編碼 response.setContentType("text/html;charset=utf-8"); // 建立工廠 DiskFileItemFactory dfif = new DiskFileItemFactory(); // 使用工廠建立解析器物件 ServletFileUpload fileUpload = new ServletFileUpload(dfif); try { // 使用解析器物件解析request,得到FileItem列表 List<FileItem> list = fileUpload.parseRequest(request); // 遍歷所有表單項 for(FileItem fileItem : list) { // 如果當前表單項為普通表單項 if(fileItem.isFormField()) { // 獲取當前表單項的欄位名稱 String fieldName = fileItem.getFieldName(); // 如果當前表單項的欄位名為username if(fieldName.equals("username")) { // 列印當前表單項的內容,即使用者在username表單項中輸入的內容 response.getWriter().print("使用者名稱:" + fileItem.getString() + "<br/>"); } } else {//如果當前表單項不是普通表單項,說明就是檔案欄位 String name = fileItem.getName();//獲取上傳檔案的名稱 // 如果上傳的檔名稱為空,即沒有指定上傳檔案 if(name == null || name.isEmpty()) { continue; } // 獲取真實路徑,對應${專案目錄}/uploads,當然,這個目錄必須存在 String savepath = this.getServletContext().getRealPath("/uploads"); // 通過uploads目錄和檔名稱來建立File物件 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); } }
檔案上傳之細節 1.把上傳的檔案放到WEB-INF目錄下 如果沒有把使用者上傳的檔案存放到WEB-INF目錄下,那麼使用者就可以通過瀏覽器直接訪問上傳的檔案,這是非常危險的。 假如說使用者上傳了一個a.jsp檔案,然後使用者在通過瀏覽器去訪問這個a.jsp檔案,那麼就會執行a.jsp中的內容,如果在a.jsp中有如下語句:Runtime.getRuntime().exec("shutdown -s -t 1");,那麼系統立馬關機
通常我們會在WEB-INF目錄下建立一個uploads目錄來存放上傳的檔案,而在Servlet中找到這個目錄需要使用ServletoContext的getRealPath(String)方法,例如在我的upload1專案中有如下語句: 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);
使用不同瀏覽器測試,其中IE6就會返回上傳檔案的完整路徑,不知道IE6怎麼操作,這就給我們帶來了很大的麻煩,就是需要處理一下這個問題。 處理這一問題也很簡單,無論是否為完整路徑,我們都去擷取最後一個“\\”後面的內容就可以了。 String name = file1FileItem.getName(); int lastIndex = name.lastIndexOf("\\");//獲取最後一個“\”的位置 if(lastIndex != -1) {//注意,如果不是完整路徑,那麼就不會有“\”的存在。 name = name.substring(lastIndex + 1);//獲取檔名稱 } response.getWriter().print(name);
3.中文亂碼問題 上傳檔名稱包含中文: 當上傳檔名稱中包含中文: 當上傳的誰的名稱中包含中文時,需要設定編碼,commons-fileupload元件為我們提供了兩種設定編碼的方式: *request.setCharacterEncoding(Strnig):這種方式是我們最為熟悉的一種; *fileUpload.setHeaderEncoding(String):這種方式的優先順序高於前一種。
上傳檔案的檔案內容包含中文: 通常我們會把使用者上傳的檔案儲存到uploads目錄下,但是如果使用者上傳了同名檔案?,這回出現覆蓋現象。處理這一問題的手段是使用UUID生產唯一名稱,然後再使用“_”連線檔案上傳的原始名稱。 例如使用者上傳的檔案是“123.jpg”,在通過處理後,檔名稱為“891b3881395f4175b969256a3f7b6e10_123.jpg”,這種手段不會使檔案丟失副檔名,並且因為UUID的唯一性,上傳的檔案同名,但是在伺服器端是不會出現同名問題的。 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); //獲取第二個表單項,因為第一個表單項是username,第二個才是file表單項 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 + 下劃線 + 原始名稱 //建立file物件,下面會把上傳檔案儲存到這個file指定的路徑 //savepath,即上傳檔案的儲存目錄 //filename,檔名稱 File file = new File(savepath, filename); // 儲存檔案 fileItem.write(file); } catch (Exception e) { throw new ServletException(e); } }
5.一個目錄不能存放過多的檔案(存放目錄打散) 一個目錄下不應該存放過多的檔案,一般一個目錄下存放1000個檔案就是上限了,如果在多,那麼開啟目錄的時候就會出現卡頓現象。 也就是說,我們需要把上傳的檔案放到不同的目錄中。但是也不能為每個上傳的檔案設定一個目錄,這種方式會導致目錄過多。所以應該採用某種演算法進行“打散”操作。 打散的方式有很多,例如使用日期進行打散,每天生成一個目錄,也可以使用檔名的首字母來生成目錄,相同首字母的檔案放到同一目錄下。 日期打散演算法:如果某一天上傳的檔案過多,那麼也會出現一個目錄檔案過多的情況; 首字打散演算法:如果檔名是中文的,因為中文過多,所有會導致目錄過多的現象。
這裡我們使用hash演算法進行打散操作: 1.獲取檔名稱的hashCode、int hCode = name.hashCode(); 2.獲取hCode的第4位,然後轉換成16進位制字元; 3.獲取hCode的5~8位,然後轉換成16進位制字元; 4.使用這兩個16進位制的字元生成目錄鏈。例如第4位字元為“5”
這種演算法的好處是,在uploads目錄下最多生成16個目錄,而每個目錄下最多再生成16個目錄,即256個目錄,所有上傳的檔案都放到這256個目錄下。如果每個目錄上限為1000個檔案,那麼一共可以有256000個檔案。
例如上傳檔名稱為:新建 文字文件.txt,那麼把“新建 文字文件.txt”的雜湊碼獲取到,再獲取雜湊碼的低4位,和5~8位。假如第4位為9,5~8位為1,那麼檔案的儲存路徑為uploads/9/1/。 int hCode = name.hashCode();//獲取檔名的hashCode //獲取hCode的低4位,並轉換成16進位制字串 String dir1 = Integer.toHexString(hCode & 0xF); //獲取hCode的低5~8位,並轉換成16進位制字串 String dir2 = Integer.toHexString(hCode >>> 4 & 0xF); //與檔案儲存目錄連線成完整路徑 savepath = savepath + "/" + dir1 + "/" + dir2; //因為這個路徑可能不存在,所以建立成File物件,再建立目錄鏈,確保目錄在儲存檔案之前已經存在 new File(savepath).mkdirs();
6.上傳的單個檔案的大小限制 限制上傳檔案的大小很簡單,ServletFileUpload類的setFileSizeMax(long)就可以了。引數就是上傳檔案的上限位元組數,例如servletFileUpload.setFileSizeMax(1024*10)表示上限為10kb。 一旦上傳的檔案超出了上限,那麼就會丟擲FileUploadBase.FileSizeLimitExceededException異常。我們可以在Servlet中獲取這個異常,然後向頁面輸出“上傳的檔案超出限制”。 public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); DiskFileItemFactory dfif = new DiskFileItemFactory(); ServletFileUpload fileUpload = new ServletFileUpload(dfif); // 設定上傳的單個檔案的上限為10KB fileUpload.setFileSizeMax(1024 * 10); try { List<FileItem> list = fileUpload.parseRequest(request); //獲取第二個表單項,因為第一個表單項是username,第二個才是file表單項 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位,並轉換成16進位制字串 String dir1 = Integer.toHexString(hCode & 0xF); //獲取hCode的低5~8位,並轉換成16進位制字串 String dir2 = Integer.toHexString(hCode >>> 4 & 0xF); //與檔案儲存目錄連線成完整路徑 savepath = savepath + "/" + dir1 + "/" + dir2; //因為這個路徑可能不存在,所以建立成File物件,再建立目錄鏈,確保目錄在儲存檔案之前已經存在 new File(savepath).mkdirs(); //建立file物件,下面會把上傳檔案儲存到這個file指定的路徑 //savepath,即上傳檔案的儲存目錄 //filename,檔名稱 File file = new File(savepath, filename); // 儲存檔案 fileItem.write(file); } catch (Exception e) { // 判斷丟擲的異常的型別是否為FileUploadBase.FileSizeLimitExceededException // 如果是,說明上傳檔案時超出了限制。 if(e instanceof FileUploadBase.FileSizeLimitExceededException) { // 在request中儲存錯誤資訊 request.setAttribute("msg", "上傳失敗!上傳的檔案超出了10KB!"); // 轉發到index.jsp頁面中!在index.jsp頁面中需要使用${msg}來顯示錯誤資訊 request.getRequestDispatcher("/index.jsp").forward(request, response); return; } throw new ServletException(e); } } 7.上傳檔案的總大小限制 上傳檔案的表單中可能允許上傳多個檔案,例如: 有時我們需要限制一個請求的大小。也就是說這個請求的最大位元組數(所有表單項之和)實現這一功能也很簡單,只需要呼叫ServletFileUpload類的setSizeMax(long)方法即可。 例如fileUpload.setSizeMax(1024*10),顯示整個請求的上限為10kb。當請求大小超出了10kb時,ServletFileUploa類的parseRequest()方法會丟擲FileUploadBase.SizeLimitException異常。
8.快取大小與臨時目錄 一種現象:如果上傳一個藍光電影,先把電影儲存到電影中,然後再通過記憶體copy到伺服器硬碟上,那麼你的記憶體能吃下嗎? 所以fileUpload元件不可能吧檔案都保留到記憶體中,fileUpload會判斷檔案大小是否超出10kb,如果是,那麼就把檔案儲存到硬碟中,如果沒超過,那麼就儲存到記憶體中。 10kb是fileUpload的預設值,我們可以來設定它。 當檔案儲存到硬碟時,fileUpload是吧檔案儲存到系統臨時目錄,當然你也可以去設定臨時目錄。 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); } // 通過檔名稱生成一級、二級目錄 int hCode = filename.hashCode(); String dir1 = Integer.toHexString(hCode & 0xF); String dir2 = Integer.toHexString(hCode >>> 4 & 0xF); savepath = savepath + "/" + dir1 + "/" + dir2; // 建立目錄 new File(savepath).mkdirs(); // 給檔名稱新增uuid字首 String uuid = CommonUtils.uuid(); filename = uuid + "_" + filename; // 建立檔案完成路徑 return new File(savepath, filename); }
檔案下載 1.通過Servlet下載1 被下載的資源必須放到WEB-INF目錄下,(只要使用者不能通過瀏覽器直接訪問就ok),然後通過Servlet完成下載, 在jsp頁面中給出超連結,連線到DownloadServlet,並提供要下載的檔名稱。然後DownloadServlet獲取檔案的真實路徑,然後把檔案寫入到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()); }
上面程式碼有如下問題: *可以下載a.avi,但是在下載框中的檔名稱為DownloadServlet; *不能下載a.jpg和a.txt,而是在頁面中顯示他們。
2.通過Servlet下載2 下面來處理上一例中的問題,讓下載框中可以顯示正確的檔名稱,已經可以下載a.jsp和a.txt檔案。 通過新增content-disposition頭來處理上面問題。當設定了content-disposition頭後,瀏覽器就會彈出下載框。 而且還可以通過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()); 雖然上面的程式碼已經處理txt和jpg等檔案的下載問題,並且也處理了下載框中顯示檔名稱的問題,但是如果下載的檔名稱是中文的,那麼還是不行的。
3.通過Serlvet下載3 下面是處理在下載框中顯示中文的問題 其實這一問題很簡單,只需要通過URL來編碼中文即可。
download.jsp <a href="<c:url value='/DownloadServlet?path=這個殺手不太冷.avi'/>">這個殺手不太冷.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請求中,引數中包含中文需要自己動手來轉換。 // 當然如果你使用了“全域性編碼過濾器”,那麼這裡就不用處理了 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; } // 所有瀏覽器都會使用本地編碼,即中文作業系統使用GBK // 瀏覽器收到這個檔名後,會使用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());
JavaMail *郵件協議 *teinet訪問郵件伺服器 *JavaMail
郵件協議 1.收發郵件 發郵件是從客戶端吧郵件傳送到郵件伺服器,收郵件是吧郵件伺服器的郵件下載到客戶端。 在163、126、QQ、sohu、sina等網站註冊的Email賬戶,其實就是在郵件伺服器中註冊的。這些網站都有自己的郵件伺服器。
2.郵件協議概述 與Http協議相同,收發郵件也是需要有傳輸協議的。 *SMTP:(Simple Mail Transfer Protocol,簡單郵件傳輸協議)發郵件協議; *POP3:(Post Office Protocol Version 3,郵局協議第3版)收郵件協議; *IMAP:(Internet Message Access Protocol,因特網訊息訪問協議)收發郵件協議。
3.理解郵件收發過程 把郵件伺服器理解為郵局,如果你需要寄一封信,那麼你需要把信封放到信筒中,這樣你的信就會“自動”到達郵局,郵局會把信郵寄到另一個地方的郵局中。然後這封信會被送到收信人的郵箱中。最終收信人需要自己經常檢視郵箱中是否有新的信件。 其實每個郵件伺服器都有SMTP伺服器和POP3伺服器構成,其中SMTP伺服器負責發郵件的請求,而POP3負責收郵件的請求。
當然,有時會使用163的賬戶,向126的賬號傳送郵件。這時郵件是傳送到126的郵件伺服器,而對於163的郵件伺服器是不會儲存這封郵件的。
4.郵件伺服器名稱 SMTP伺服器的埠號為25,伺服器名稱為smtp.xxx.xxx。 POP3伺服器的埠號為110,伺服器名稱為pop3.xxx.xxx。 例如: *163:smtp.163.com和pop3.163.com; *126:smtp.126.com和pop3.126.com; *qq:smtp.qq.com和pop3.qq.com; *sohu:smtp.sohu.com和pop3.sohu.com; *sina:smtp.sina.com和pop3.sina.com。
telnet收發郵件 1.BASE64加密 BASE64是一種加密演算法,這總加密方式是可逆的。它的作用是使加密後的文字無法通過肉眼識別。java提供了sun.misc.BASE63Encoder這個類,用來對做Base64的加密和解密。 import org.apache.commons.codec.binary.Base64;
public class Base64Utils { public static String encode(String s) { return encode(s, "utf-8"); } public static String decode(String s) { return decode(s, "utf-8"); } public static String encode(String s, String charset) { try { byte[] bytes = s.getBytes(charset); bytes = Base64.encodeBase64(bytes); return new String(bytes, charset); } catch (Exception e) { throw new RuntimeException(e); } }
public static String decode(String s, String charset) { try { byte[] bytes = s.getBytes(charset); bytes = Base64.decodeBase64(bytes); return new String(bytes, charset); } catch (Exception e) { throw new RuntimeException(e); } } }
2.telnet發郵件 連線163的smtp伺服器:telnet smtp.163.com 25; 連線成功後需要如下步驟才能傳送郵件: 1.與伺服器打招呼:ehlo你的名字 2.發出登入請求:auth-login 3.輸入加密後的郵箱名:([email protected])aXRjYXN0X2N4ZkAxNjMuY29t 4.輸入加密後的郵箱密碼:(123456)aXRjYXN0 5.輸入誰來發送郵件,即from:mail from:<[email protected]> 6.輸入把郵件發給誰,即to:rcpt to:<[email protected]> 7.傳送新增資料請求:data 8.開始輸入資料,資料包含:from、to、subject、以及郵件內容,如果輸入結束後,以一個“.”為一行,表示輸入結束; from:<[email protected]> to:<[email protected]> subject: 我愛上你了
我已經深深的愛上你了。 .
注意:在標題和郵件正文之間要有一個空行。當要退出時,一定要以一個“.”為單行,表示輸入結束。 9.最後一步:quit
3.telnet收郵件 1.telnet收郵件的步驟 pop3無需使用Base64加密。 收郵件連線的伺服器是pop3.xxx.com,pop3協議的預設埠號為110。請注意。這與發郵件完全不同。如果你在163有郵箱賬號,那麼你想使用telnet收郵件,需要連線的伺服器是pop3.163.com。
*連線pop3伺服器:telnet.pop3.163.com 110 *user命令:user 使用者名稱,例如:user [email protected]; *pass命令:pass 密碼,例如:pass 123456 *stat命令:stat命令用來檢視郵箱中郵件的個數,所有郵件所佔的空間; *list命令:list命令用來檢視所有郵件,或指定郵件的狀態,例如:list 1是檢視第一封郵件的大小,list是檢視郵件列表,即列出所有郵件的編號,及大小; *retr命令:檢視指定郵件的內容,例如:retr 1#是檢視第一封郵件的內容; *dele命令:標記某郵件為刪除,單不是馬上刪除,而是在退出時才會真正的刪除; *quit命令:退出。如果在退出之前已經使用了dele命令標記了某些郵件,那麼會在退出時刪除它們。
JavaMail 1.JavaMail概述 JavaMail是由SUN公司提供的專門針對郵件的api,主要的jar包:mail.jar、activation.jar。 在使用MyEclipse建立web專案時,需要小心。如果只是在web專案中使用JavaMail是沒有什麼問題的,釋出到Tomcat上執行一點問題都沒有。 但是在web專案中寫測試那就出問題了。 在MyEclipse中,會自動給web專案匯入javax.mail包中的類,但是不全(其實是隻有介面,而沒有介面的實現類),所以只靠MyEclipse中的類是不能執行JavaMail專案的,但是如果這時你再去自行匯入mail.jar時,就會出現衝突。 處理方案:到下面路徑中找到javaee.jar檔案,把javax.mail刪除。
2.JavaMail中主要類 JavaMail中主要類:javax.mail.Session、javax.mail.internet.MimeMessage、javax.mail.Transport。 Session:表示會話,即客戶端與郵件伺服器之間的會話,想要獲得會話需要給出賬戶和密碼,當然還要給出伺服器名稱。在郵件伺服器中的Session物件,就相當於連線資料庫時的Commection物件。 MimeMessage:表示郵件類,它是Message的子類。它包含郵件的主題(標題)、內容,收件人地址、發件人地址,還可以設定抄送和暗送,甚至還可以設定附件。 Transport:用來發送郵件。它是傳送器。
3.JavaMail之HelloWorld 在使用telnet發郵件時,還需要自己來處理Base64編碼對問題,但是使用Javamail就不必理會這些問題了,都有JavaMail來處理。
第一步:獲取Session Session session = Session。getInstance(Properties prop, Authenticator auth); 其中prop需要制定兩個鍵值,一個是制定伺服器主機名,另一個是指定是否需要認證。 Properties prop = new Properties(); prop.setProperty("mail.host", "smtp.qq.com");//設定伺服器主機名 prop.setProperty("mail.smtp.auth", "true");//設定需要認證
其中Authenticator是一個介面表示認證器,集校驗客戶端的身份。我們需要自己來實現這個介面,實現這個介面需要使用賬戶和密碼。 Authenticator auth = new Authenticator(){ public PasswordAuthentication getPasswordAuthentication(){ return new PasswordAuthentication("1451388723", "123456");//使用者名稱和密碼 } }
通過上面的準備,現在可以獲取Session物件了: Session session = Session.getInstance(prop, auth);
第二步:建立MimeMessage物件 建立MimeMessage需要使用Session物件來建立: MimeMssage msg = new MimeMessage(session); 然後需要設定發信人地址、收信人地址、主題,以及郵件正文。 msg.setFrom(new InternetAddress("[email protected]"));//設定發信人 msg.setRecipients(RecipientType.TO, "[email protected]", "[email protected]");//設定多個收信人 msg.setRecipients(RecipientType.CC, "[email protected]","[email protected]");//設定多個抄送 msg.setRecipients(RecipientType.BCC, "[email protected]");//設定暗送人 msg.setSubject("這是一封測試郵件");//設定主題(標題) msg.setContent("當然是HelloWorld。","text/plain;charset=utf-8");//設定正文
第三步:傳送郵件: Transport.send(msg);//傳送郵件
4.JavaMail傳送帶有附件的郵件(瞭解) 一封郵件可以包含正文、附件N個,所有正文與N個附件都是有郵件的一個部分。 上面的HelloWorld案例中,只是傳送了帶有正文的郵件,所以在呼叫setContent()方法時直接設定了正文,如果想要傳送帶有附件郵件,那麼需要設定郵件的內容為MimeMultiPart。 MimeMulitpart parts = new MimeMulitpart();//多部件物件,可以理解為是部件的集合 msg.setContent(parts);//設定郵件的內容為多部件內容。 然後我們需要把正文、N個附件建立為“主體部件”物件(MimeBodyPart),新增到MimeMuiltPart中即可。 MimeBodyPart part1 = new MimeBodyPart();//建立一個部件 part1.setContent("這是正文部分", "text/html;charset=utf-8");//給部件設定內容 parts.addBodyPart(part1);//把部件新增到部件集中。
下面我們建立一個附件: MimeBodyPart part2 = new MimeBodyPart();//建立一個部件 part2.attachFile("d:\\a.jpg");//設定附件 part2.setFileName("hello.jpg");//設定附件名稱 part2.addBodyPart(part2);//把附件新增到部件集中
注意:如果在設定名稱中,檔名稱中包含了中文的話,那麼需要使用MimeUitlity類來給中文編碼: part2.setFileName(MimeUitlity.encodeText("美女.jpg"));