目錄
從1969年10月世界上的第一封電子郵件發出,到2019年,已經過去將近半個世紀了。雖然即時通訊和視訊會議,甚至全息投影都變得日益普及,但電子郵件依然有著廣泛的使用場景和不可撼動的歷史地位。
SpringBoot擁有強大的生態鏈,幾乎可以連線所有主流的開源庫。
下面我們就從電子郵件傳送的歷史再到原理,然後如何自己配置郵件伺服器併傳送郵件,一步步講解。
本文實現原始碼可以在這裡找到: SpringBoot傳送電子郵件原始碼
電子郵件與Java傳送郵件的歷史
- 1969年10月,世界上的第一封電子郵件
1969年10月世界上的第一封電子郵件是由電腦科學家Leonard K.教授發給他的同事的一條簡短訊息。第一條網上資訊就是‘LO’,意思是‘你好!’。
- 1987年9月14日中國的第一封電子郵件
在此之後,1987年9月14日中國的第一封電子郵件,這封郵件是由德國維爾納·措恩與中國的王運豐在北京計算機應用技術研究所,發往德國一個大學的,郵件內容頗具深意,“Across the Great Wall we can reach every corner in the world.(越過長城,走向世界)”,這是中國通過北京與德國大學之間的網路連線,向全球科學網發出的第一封電子郵件。
- 30年代發展歷程
接下來中國的電子郵件進入了30年的發展期,雖然在1987年就有了電子郵件,但是,真正的郵件興起,應該在90年代到2000年之間,因為在1987的時候中國網速特別慢,真正能接觸到網際網路的使用者是非常少的,到了90年代中期,網際網路瀏覽器的誕生,使得全民上網人數激增,電子郵件被廣泛使用,此時,中國的部分學生在研究中使用到電子郵件,真正普及的時間是在2000年左右。
- Java傳送郵件
Java在發明之初,就開始支援傳送郵件,通過java mail包方式去操作郵件傳送的內容和協議,但是,這種傳送方式稍微比較複雜,需要配置各種引數、協議、內容,之後產生了Spring框架。
- Spring傳送郵件
Spring在java mail的基礎上進行了一些封裝,使傳送郵件的過程的複雜大大減少。
- SpringBoot傳送郵件
SpringBoot Mail在Spring Mail的基礎上,再次進行一次封裝,使得傳送郵件的便利度上,更為簡單。
電子郵件原理
電子郵件伺服器
使用者要在Internet上提供電子郵件功能,必須有專門的電子郵件伺服器。這些郵件伺服器就類似於現實生活中的郵局,它主要負責接收使用者投遞過來的郵件,並把郵件投遞到郵件接收者的電子郵箱中。
郵件伺服器就好像是網際網路世界的郵局。按照功能劃分,郵件伺服器可以劃分為兩種型別:
- SMTP郵件伺服器:使用者替使用者傳送郵件和接收外面傳送給本地使用者的郵件。
- POP3/IMAP郵件伺服器:使用者幫助使用者讀取SMTP郵件伺服器接收進來的郵件。
電子郵箱
電子郵箱也稱為E-mail地址,使用者可以通過E-mail地址來標識自己傳送的電子郵件,也可以通過這個地址接收別人發來的電子郵件。電子郵箱需要到郵件伺服器進行申請,也就是說,電子郵箱其實就是使用者在郵件伺服器上申請的賬戶。郵件伺服器會把接收到的郵件儲存到為該賬戶所分配的郵箱空間中,使用者通過使用者名稱密碼登入到郵件伺服器查收該地址已經收到的郵件。一般來講,郵件伺服器為使用者分配的郵箱空間是有限的。
郵件客戶端
我們可以直接在網站上進行郵件收發,也可以使用常見的FoxMail、Outlook等郵件客戶端軟體接受郵件。郵件客戶端軟體通常集郵件撰寫、傳送和收發功能於一體,主要用於幫助使用者將郵件傳送給SMTP郵件伺服器和從POP3/IMAP郵件伺服器讀取使用者的電子郵件。
郵件傳輸協議
電子郵件需要在郵件客戶端和郵件伺服器之間,以及兩個郵件伺服器之間進行郵件傳遞,那就必須要遵守一定的規則,這個規則就是郵件傳輸協議。下面我們分別簡單介紹幾種協議:
- SMTP協議:全稱為 Simple Mail Transfer Protocol,簡單郵件傳輸協議。它定義了郵件客戶端軟體和SMTP郵件伺服器之間,以及兩臺SMTP郵件伺服器之間的通訊規則。
- POP3協議:全稱為 Post Office Protocol,郵局協議。它定義了郵件客戶端軟體和POP3郵件伺服器的通訊規則。
- IMAP協議:全稱為 Internet Message Access Protocol,Internet訊息訪問協議,它是對POP3協議的一種擴充套件,也是定義了郵件客戶端軟體和IMAP郵件伺服器的通訊規則。
郵件格式
要想各種郵件處理程式能識別我們所寫的電子郵件,能從我們所書寫的電子郵件中分析和提取出發件人、收件人、郵件主題和郵件內容以及附件等資訊,那麼我們所寫的電子郵件必須要遵循一定的格式要求,而這種郵件內容的基本格式和具體細節分別是由 RFC822 文件和 MIME 協議定義的。
- RFC822 文件中定義的檔案格式包括兩個部分:郵件頭和郵件體。
- MIME協議(Multipurpose Internet Mail Extensions )用於定義複雜郵件體的格式,它可以表達多段平行的文字內容和非文字的郵件內容,例如,在郵件體中內嵌的影象資料和郵件附件等。另外,MIME協議的資料格式也可以避免郵件內容在傳輸過程中發生資訊丟失。MIME協議不是對RFC822郵件格式的升級和替代,而是基於RFC822郵件格式的擴充套件應用。一言以蔽之,RFC822定義了郵件內容的格式和郵件頭欄位的詳細細節,MIME協議則是定義瞭如何在郵件體部分表達出的豐富多樣的資料內容。
電子郵件傳送和接收流程
圖示的六個步驟分別進行如下的說明:
①使用者A的電子郵箱為:xx@qq.com,通過郵件客戶端軟體寫好一封郵件,交到QQ的郵件伺服器,這一步使用的協議是SMTP,對應圖示的①;
②QQ郵箱會根據使用者A傳送的郵件進行解析,也就是根據收件地址判斷是否是自己管轄的賬戶,如果收件地址也是QQ郵箱,那麼會直接存放到自己的儲存空間。這裡我們假設收件地址不是QQ郵箱,而是163郵箱,那麼QQ郵箱就會將郵件轉發到163郵箱伺服器,轉發使用的協議也是SMTP,對應圖示的②;
③163郵箱伺服器接收到QQ郵箱轉發過來的郵件,也會判斷收件地址是否是自己,發現是自己的賬戶,那麼就會將QQ郵箱轉發過來的郵件存放到自己的內部儲存空間,對應圖示的③;
④使用者A將郵件傳送了之後,就會通知使用者B去指定的郵箱收取郵件。使用者B會通過郵件客戶端軟體先向163郵箱伺服器請求,要求收取自己的郵件,對應圖示的④;
⑤163郵箱伺服器收到使用者B的請求後,會從自己的儲存空間中取出B未收取的郵件,對應圖示⑤;
⑥163郵箱伺服器取出使用者B未收取的郵件後,將郵件發給使用者B,對應圖示的⑥;最後三步使用者B收取郵件的過程,使用的協議是POP3;
電子郵件的使用場景
在系統中電子郵件的使用場景:
-
註冊驗證
-
營銷推送
-
觸發機制
-
監控報警
電子郵件是業務和安全的最後一道防線 —— 當系統無法自動處理的時候,通過郵件提醒相關支援人員。
SpringBoot實現傳送電子郵件
準備賬號
註冊發件郵箱並設定客戶端授權碼,這裡以163免費郵箱為例:
構建專案並配置
搭建完專案以後,進行下面的兩步配置。
application.properties配置引數:
# 郵箱配置
spring.mail.host=smtp.163.com
# 你的163郵箱
spring.mail.username=ispringboot@163.com
# 注意這裡不是郵箱密碼,而是SMTP授權密碼
spring.mail.password=isb001
spring.mail.port=25
spring.mail.protocol=smtp
spring.mail.default-encoding=UTF-8
複製程式碼
pom.xml依賴spring-boot-starter-mail模組:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
複製程式碼
實現服務端程式碼
MailService.java:
package org.ijiangtao.tech.spring.boot.mail.imail.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;
@Service
public class MailService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Value("${spring.mail.username}")
private String from;
@Autowired
private JavaMailSender mailSender;
/**
* 簡單文字郵件
* @param to 接收者郵件
* @param subject 郵件主題
* @param contnet 郵件內容
*/
public void sendSimpleMail(String to, String subject, String contnet){
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(subject);
message.setText(contnet);
message.setFrom(from);
mailSender.send(message);
}
/**
* HTML 文字郵件
* @param to 接收者郵件
* @param subject 郵件主題
* @param contnet HTML內容
* @throws MessagingException
*/
public void sendHtmlMail(String to, String subject, String contnet) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(contnet, true);
helper.setFrom(from);
mailSender.send(message);
}
/**
* 附件郵件
* @param to 接收者郵件
* @param subject 郵件主題
* @param contnet HTML內容
* @param filePath 附件路徑
* @throws MessagingException
*/
public void sendAttachmentsMail(String to, String subject, String contnet,
String filePath) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(contnet, true);
helper.setFrom(from);
FileSystemResource file = new FileSystemResource(new File(filePath));
String fileName = file.getFilename();
helper.addAttachment(fileName, file);
mailSender.send(message);
}
/**
* 圖片郵件
* @param to 接收者郵件
* @param subject 郵件主題
* @param contnet HTML內容
* @param rscPath 圖片路徑
* @param rscId 圖片ID
* @throws MessagingException
*/
public void sendInlinkResourceMail(String to, String subject, String contnet,
String rscPath, String rscId) {
logger.info("傳送靜態郵件開始: {},{},{},{},{}", to, subject, contnet, rscPath, rscId);
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = null;
try {
helper = new MimeMessageHelper(message, true);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(contnet, true);
helper.setFrom(from);
FileSystemResource res = new FileSystemResource(new File(rscPath));
helper.addInline(rscId, res);
mailSender.send(message);
logger.info("傳送靜態郵件成功!");
} catch (MessagingException e) {
logger.info("傳送靜態郵件失敗: ", e);
}
}
}
複製程式碼
新建郵件模板
我們使用thymeleaf作為模板引擎。
emailTeplate.html:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"/>
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
<title>註冊-測試郵件模板</title>
</head>
<body>
你好,感謝你的註冊,這是一封驗證郵件,請點選下面的連線完成註冊,感謝您的支援。
<a href="#" th:href="@{https://github.com/{id}(id=${id})}">啟用賬戶</a>
</body>
</html>
複製程式碼
測試傳送郵件
測試傳送郵件,使用單元測試MailServiceTest.java:
package org.ijiangtao.tech.spring.boot.mail.imail.service;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import javax.annotation.Resource;
import javax.mail.MessagingException;
@RunWith(SpringRunner.class)
@SpringBootTest
public class MailServiceTest {
@Autowired
private MailService mailService;
@Resource
private TemplateEngine templateEngine;
@Test
public void sendSimpleMail() {
mailService.sendSimpleMail("ispringboot@163.com","測試spring boot imail-主題","測試spring boot imail - 內容");
}
@Test
public void sendHtmlMail() throws MessagingException {
String content = "<html>\n" +
"<body>\n" +
"<h3>hello world</h3>\n" +
"<h1>html</h1>\n" +
"<body>\n" +
"</html>\n";
mailService.sendHtmlMail("ispringboot@163.com","這是一封HTML郵件",content);
}
@Test
public void sendAttachmentsMail() throws MessagingException {
String filePath = "/ijiangtao/軟體開發前景.docx";
String content = "<html>\n" +
"<body>\n" +
"<h3>hello world</h3>\n" +
"<h1>html</h1>\n" +
"<h1>附件傳輸</h1>\n" +
"<body>\n" +
"</html>\n";
mailService.sendAttachmentsMail("ispringboot@163.com","這是一封HTML郵件",content, filePath);
}
@Test
public void sendInlinkResourceMail() throws MessagingException {
//TODO 改為本地圖片目錄
String imgPath = "/ijiangtao/img/blob/dd9899b4cf95cbf074ddc4607007046c022564cb/blog/animal/dog/dog-at-work-with-computer-2.jpg?raw=true";
String rscId = "admxj001";
String content = "<html>" +
"<body>" +
"<h3>hello world</h3>" +
"<h1>html</h1>" +
"<h1>圖片郵件</h1>" +
"<img src='cid:"+rscId+"'></img>" +
"<body>" +
"</html>";
mailService.sendInlinkResourceMail("ispringboot@163.com","這是一封圖片郵件",content, imgPath, rscId);
}
@Test
public void testTemplateMailTest() throws MessagingException {
Context context = new Context();
context.setVariable("id","ispringboot");
String emailContent = templateEngine.process("emailTeplate", context);
mailService.sendHtmlMail("ispringboot@163.com","這是一封HTML模板郵件",emailContent);
}
}
複製程式碼
測試結果,收到了電子郵件:
總結
在生產環境,一般郵件服務會單獨部署,並通過HTTP或MQ等方式暴露出來。