目錄
從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的電子郵箱爲:[email protected],通過郵件客戶端軟件寫好一封郵件,交到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郵箱
[email protected]
# 注意這裏不是郵箱密碼,而是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("[email protected]","測試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("[email protected]","這是一封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("[email protected]","這是一封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("[email protected]","這是一封圖片郵件",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("[email protected]","這是一封HTML模板郵件",emailContent);
}
}
測試結果,收到了電子郵件:
總結
在生產環境,一般郵件服務會單獨部署,並通過HTTP或MQ等方式暴露出來。