现在在后台服务器中发送邮件已经是一个非常常用的功能了。通常来说虽然HTML并非是一个非常标准的信息格式,但是至少许多邮件客户端都至少支持一部分标记语言。 在这边教程中主要是关于教你如何在Spring Boot 应用中发送邮件以及使用非常简单强大的Thymeleaf模板引擎来制作邮件内容。
文章末尾附上源码,已经开源到Github上,是我公司做项目的时候处理邮件这一块用到的。 基本上覆盖了大部分邮件发送需求。稍微修改了一下,奉献给有需要的人。当你看完文章在看一下这封源码,你会对这一块更加的了解。而且你能掌握常用的邮件发送:
纯文本邮件
内联图片邮件
带附件的邮件
纯文本邮件
添加依赖(Mail starter dependencies)
首先制作并且通过SMTP邮件服务器来发送一个纯文本邮件。
如果你之前有用过Spring Boot的话,那你宁该并不好奇在你建立一个新工程的时候,Spring Boot已经帮你继承了常用的依赖库。 通常你只需要在你的 pom.xml 中添加如下依赖即可:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
邮件服务器属性配置(Properties configuration)
通常情况下,如果所需要的依赖在 class path 中都是可用的话,这时候Spring会自动帮你注册一个默认实现的邮件发送服务 (default mail sender service)。 spring.mail.host 属性已经被自动定义了, 所有我们所需要做的事情就是把这个属性添加到我们应用的 application.properties 配置文件中。
application.properties 在resource文件夹下
Spring Boot 提供的默认邮件发送服务 其实已经非常强大了,我们可以通过简单的配置它的属性就可以了。所谓的属性其实说白了就是配置它的邮件SMTP 服务器:
spring.mail.port=25 # SMTP server port
spring.mail.username= # Login used for authentication
spring.mail.password= # Password for the given login
spring.mail.protocol=smtp
spring.mail.defaultEncoding=UTF-8 # Default message encoding
这里附带一份 gmail 的SMTP服务器配置清单:
spring.mail.host = smtp.gmail.com
spring.mail.username = *****@gmail.com
spring.mail.password = ****
spring.mail.properties.mail.smtp.auth = true
spring.mail.properties.mail.smtp.socketFactory.port = 587
spring.mail.properties.mail.smtp.socketFactory.class = javax.net.ssl.SSLSocketFactory
spring.mail.properties.mail.smtp.socketFactory.fallback = false
邮件发送服务(Mail sending service)
在这里我们使用 Autowired 在注入我们的service, 它主要就是生成邮件的相关信息
@Service
public class MailClient {
private JavaMailSender mailSender;
@Autowired
public MailService(JavaMailSender mailSender) {
this.mailSender = mailSender;
}
public void prepareAndSend(String recipient, String message) {
//TODO implement
}
}
生成邮件内容
下面是一个简单的生成邮件内容的代码。
public void prepareAndSend(String recipient, String message) {
MimeMessagePreparator messagePreparator = mimeMessage -> {
MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage);
messageHelper.setFrom("sample@dolszewski.com");
messageHelper.setTo(recipient);
messageHelper.setSubject("Sample mail subject");
messageHelper.setText(message);
};
try {
mailSender.send(messagePreparator);
} catch (MailException e) {
// runtime exception; compiler will not force you to handle it
}
}
send() 需要被重写以接受不同类型的参数变量:
- SimpleMailMessage: 正如名字所示,这是一个最基本的邮件message的模块,我们可以给它设置常用的属性,它并不能够修改信息的头,只能发送纯文本的文件。
- MimeMessage: 通过这个类我们可以构建出比较复杂的邮件内容
- MimeMessagePreparator: 这是一个接口类,主要目的是提供一个构建模板方法用来构建 MimeMessage 以及当你生成一个实例的时候帮你处理异常信息。官方文档(也是常识:))建议将MimeMessagePreparator作为邮件构建的首选类型。
MimeMessageHelper类是MimeMessage的装饰类,它提供了更多的开发人员友好界面,并为类的许多属性添加了输入验证。你可以不用,但是别人肯定会用,而且你会后悔不用 XD。
send() 会抛出 **MailException ** 异常,这是个运行时异常,也就是通常所说的 RuntimeException。 在消息传递失败的情况下,很可能会重复发送操作,或者至少使用一些更复杂的解决方案处理这种情况,例如:使用相应的堆栈跟踪记录错误消息。
手动测试
通常如果你想邮件功能,你首先需要拥有一个SMTP服务器在你本机的电脑上处理你的请求。 如果你还没用过,下面给你们推荐一些常用的:
- FakeSMTP – A simple server written in Java. Supported by any operating system with Java 1.6 or newer installed.
- smtp4dev – A server with a plain and user friendly interface. For Windows only.
- Papercut – Another simple server designed for Windows.
集成测试
你可能或许会感到好奇应该如果写一个自动化的Test来验证你客户端的功能。 如果你手动测试的话,你需要开启SMTP 服务器然后在运行你的Spring Boot客户端。 在这里给大家推荐一个神器 GreenMail, 因为他跟Junit单元测试高度集成,可以简化我们的测试。
添加依赖
GreenMail 已经在Maven仓库中了,所以我们唯一所需要做的就是将其依赖加入我们的 pom.xml 配置文件中:
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
<version>1.5.0</version>
<scope>test</scope>
</dependency>
SMTP服务器与Test模板
现在呢,说了这么多废话,我们终于可以创建我们的第一个集成测试类了。 它会启动Spring应用程序并同时运行邮件客户端。但是在我们编写实际测试之前呢,我们首先必须要确保SMTP服务器正确运行,同时在测试结束的时候能够正确关闭。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class MailClientTest {
private GreenMail smtpServer;
@Before
public void setUp() throws Exception {
smtpServer = new GreenMail(new ServerSetup(25, null, "smtp"));
smtpServer.start();
}
@After
public void tearDown() throws Exception {
smtpServer.stop();
}
}
创建邮件客户端
首先,我们需要注入我们的邮件service在测试类中。之后,我们才能通过GrennMail来验证是否能够接受到邮件。
@Autowired
private MailClient mailClient;
@Test
public void shouldSendMail() throws Exception {
//given
String recipient = "name@hotmail.com";
String message = "Test message content";
//when
mailClient.prepareAndSend(recipient, message);
//then
assertReceivedMessageContains(message);
}
private void assertReceivedMessageContains(String expected) throws IOException, MessagingException {
MimeMessage[] receivedMessages = smtpServer.getReceivedMessages();
assertEquals(1, receivedMessages.length);
String content = (String) receivedMessages[0].getContent();
assertTrue(content.contains(expected));
}
发送HTML邮件
在这里我们主要说一下如何构建HTML类型的邮件。
Thymeleaf 模板引擎
首先在你的 pom.xml 中添加依赖。Spring引导将使用其默认设置自动准备引擎
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Thymeleaf的默认配置期望所有HTML文件都放在 **resources/templates ** 目录下,以.html扩展名结尾。 让我们创建一个名为mailTemplate.html的简单文件,我们将使用创建的邮件客户端类发送:
除了在生成过程中作为参数传递的消息的占位符,该模板几乎不包含任何内容。这不是废话么-,-
模板处理
创建一个服务类,它主要负责将写入的模板和外部模型组合在一起,这在我们的例子中是一个简单的短信。
@Service
public class MailContentBuilder {
private TemplateEngine templateEngine;
@Autowired
public MailContentBuilder(TemplateEngine templateEngine) {
this.templateEngine = templateEngine;
}
public String build(String message) {
Context context = new Context();
context.setVariable("message", message);
return templateEngine.process("mailTemplate", context);
}
}
注意这里的 context。 这里主要使用的 键值对 的形式,类似map,将模板里面的需要的变量与值对应起来。 比如: <span th:text="${message}"></span>, 这里我们通过context就将message的内容赋值给了span。
TemplateEngine类的实例由Spring Boot Thymeleaf自动配置提供。我们所需要做的就是调用process()方法,该方法接受两个参数,也就是我们使用的模板的名称以及充当模型的容器的上下文对象对象。
将新创建的 MailContentBuilder 注入到MailService类中。我们需要在prepareAndSen() 方法中进行一个小的调整,以利用构建器将生成内容设置为mime消息。我们还使用 setText() 方法的重载变量将 Content-Type 头设置为text / html,而不是默认的 text / plain。
测试
需要更新的最后一件事是我们的测试,更确切地说,是接收到的消息的预期内容。只需对验证逻辑进行一个小的更改,运行测试并检查结果。
@Test
public void shouldSendMail() throws Exception {
//given
String recipient = "name@dolszewski.com";
String message = "Test message content";
//when
mailService.prepareAndSend(recipient, message);
//then
String content = "<span>" + message + "</span>";
assertReceivedMessageContains(content);
}
本文到此基本宣告结束。
再次献上一份我常用的html email模板:
<!DOCTYPE html>
<html lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Siemens Sinnovation</title>
<style>
.button {
background-color: #4CAF50;
border-radius: 12px;
border: none;
color: white;
padding: 10px 25px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 18px;
margin: 4px 2px;
cursor: pointer;
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
.button:hover {
box-shadow: 0 12px 16px 0 rgba(0, 0, 0, 0.24), 0 17px 50px 0 rgba(0, 0, 0, 0.19);
}
</style>
</head>
<body style="margin: 0;padding: 0;">
<table align="center" border="1" cellpadding="0" cellspacing="0" width="600px">
<tr>
<td>
<table align="center" border="0" cellpadding="0" cellspacing="0" width="600"
style="border-collapse: collapse;">
<tr>
<td align="center" style="padding: 40px 0 30px 0;">
<!--![](image/logo.png)-->
![](|cid:${imageResourceName}|)
</td>
</tr>
<tr>
<td bgcolor="#ffffff" style="padding: 20px 30px 20px 30px">
<h4>The following message was created by <span th:text="${owner.getName()}"></span> in the
Siemens DFFA group: </h4>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td><span th:text="${title}">title</span></td>
</tr>
<tr>
<td style="padding: 20px 0 30px 0">
<span th:text="${description}">description</span>
</td>
</tr>
<tr>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<!--<td align="center" style="padding: 5px 0 3px 0">Status:<span-->
<!--th:text="${status}">status</span></td>-->
<!--<td align="center" style="padding: 5px 0 3px 0">Date submitted: <span-->
<!--th:text="${createDate}">createDate</span></td>-->
<!--<td align="center" style="padding: 5px 0 3px 0">Days left to join:<span-->
<!--th:text="${leftTime}">leftTime</span></td>-->
<td align="center" style="padding: 5px 0 3px 0">Status:<span
th:text="${status}"> OPEN FOR JOINING</span></td>
<td align="center" style="padding: 5px 0 3px 0">Date submitted: 28/08/2017 <span
th:text="${createDate}">createDate</span></td>
<td align="center" style="padding: 5px 0 3px 0">Days left to join: 10h<span
th:text="${leftTime}">leftTime</span></td>
</tr>
</table>
</tr>
<tr>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td style="padding: 40px 0px 10px 0px">
Team Member:
</td>
</tr>
<tr th:each="member :${members}">
<td style="padding: 5px 0px 5px 0px"><span
th:text="${member.getName()}+', Email: '+${member.getEmail()}"></span>
</td>
</tr>
</table>
</tr>
<tr>
<td align="center" style="padding: 5px 40px 5px 40px">
<button class="button">View Details</button>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
如果你没看源码的话,我敢打赌你看完这篇文章还是一脸蒙蔽 哈哈。
如果喜欢的话,请star吧 谢谢各位看观老爷
Github 源码