作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO
联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬
对于消息提醒,目前的软件开发越来越倾向于发送短信或使用IM,但邮件依旧有着不可代替的作用,比如正式的面试邀请及offer发放等。
使用邮件发送验证码时,可能并不需要过多样式设计,可以直接在Java代码中拼接HTML:
@Test
public void testSimpleMail() {
String receiver = "523839773@qq.com";
String code = "123456";
MimeMessage message = javaMailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(sender, "知乎");
helper.setTo(receiver);
helper.setSubject("测试验证码发送");
// 接收调用方传入的验证码
String VALIDATE_CODE_TEMPLATE = "<!DOCTYPE html>\n" +
"<html lang=\"en\">\n" +
" <head>\n" +
" <meta charset=\"UTF-8\" />\n" +
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n" +
" <title>验证您的知乎邮箱账户</title>\n" +
" </head>\n" +
" <body>\n" +
" <h2 style=\"text-align: center;\">验证您的知乎邮箱账户</h2>\n" +
" <p style=\"text-indent: 2em;\">hi~</p>\n" +
" <p style=\"text-indent: 2em;\">\n" +
" 您已选择 $EMAIL$ 作为你的邮箱账户,为验证此电子邮箱属于你,请在你的邮箱验证界面输入下方6位验证码\n" +
" </p>\n" +
" <h1 style=\"color: #1890ff; text-align: center;\">\n" +
" $CODE$\n" +
" </h1>\n" +
" <p style=\"text-indent: 2em;\">此验证码将于2小时后失效,请您尽快完成验证。</p>\n" +
" </body>\n" +
"</html>";
String text = VALIDATE_CODE_TEMPLATE.replace("$EMAIL$", receiver).replace("$CODE$", code);
int i = 1 / 0;
helper.setText(text, true);
// 发送填充好的整个html页面
javaMailSender.send(message);
log.info("验证码发送成功");
} catch (Exception e) {
log.info("给邮箱{}发送验证码时发生错误", receiver);
e.printStackTrace();
}
}
虽然我说是Java代码里直接拼接HTML,但你可千万别真的直接在代码里拼...先让前端同事写好静态页面,然后拷贝整个HTML复制粘贴就好了:
最终效果:
但相信大家也看到过类似下面这种邮件通知:
是的,你会感觉邮件里好像内嵌了一个页面,而且这个页面上的链接是可以点击跳转的。
要实现这种效果,本质上还是发送一段text文本,只不过text文本中的HTML样式比之前验证码的HTML更加丰富了。而且个别情况下,我们还需要根据用户的性别分别显示♂、♀,也就是需要按条件显示。
此时可以结合SpringBoot Mail + Thymeleaf,以Thymeleaf为页面模板,把需要替换的数据交给模板引擎渲染成静态页面,最后作为邮件的text发送即可。
有些人可能觉得前后端分离的时代,根本不需要Thymeleaf这种模板技术。其实模板技术未必要用来写前端页面,打打辅助可能会有意想不到的收获。
如果发送失败,请百度,需要设置一下QQ邮箱。
<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>
spring.mail.host=smtp.xxx.cn
# 邮箱用户名
spring.mail.username=xxx@yyy.cn
# 邮箱密码(注意:qq邮箱应该使用独立密码,去qq邮箱设置里面获取)
spring.mail.password=xxxx
# 编码格式
spring.mail.default-encoding=UTF-8
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.port=465
spring.mail.properties.mail.smtp.socketFactory.port = 465
spring.mail.properties.mail.smtp.socketFactory.class = javax.net.ssl.SSLSocketFactory
spring.mail.properties.mail.smtp.socketFactory.fallback = false
# java mail发邮件是附件名过长默认会被截断,附件名显示【tcmime.29121.xxx.xxx.bin】,主动设为false可正常显示附件名
spring.mail.properties.mail.mime.splitlongparameters=false
/**
* @author MX
*/
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class SpringMailTest {
@Value("${spring.mail.username}")
private String sender;
@Autowired
private JavaMailSender javaMailSender;
@Autowired
private TemplateEngine templateEngine;
@Test
public void sendResumeNotify() {
MimeMessage message = javaMailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(sender, "知乎");
helper.setTo("523839773@qq.com");
helper.setSubject("简历投递提醒");
// 定义模板数据
Context context = new Context();
HashMap<String, Object> paramMap = new HashMap<>(16);
paramMap.put("candidate", "bravo1988");
paramMap.put("sex", 1);
paramMap.put("publisher", "马云");
paramMap.put("jobName", "Java高级开发工程师(saas方向)");
paramMap.put("icon", "https://pic4.zhimg.com/v2-1907eb21be63d35b077e6ed3cbcbfe13_xll.jpg");
paramMap.put("school", "清华大学");
paramMap.put("major", "计算机科学与技术");
paramMap.put("education", "本科");
paramMap.put("status", "已离职");
paramMap.put("expectJob", "软件工程师");
paramMap.put("expectCity", "杭州");
paramMap.put("salaryBegin", 10);
paramMap.put("salaryEnd", 12);
paramMap.put("appendixUrl", "https://wise-job.oss-cn-zhangjiakou.aliyuncs.com/wiseJob/1601035929262.pdf");
paramMap.put("appendixName", "bravo1988_Java.pdf");
context.setVariables(paramMap);
// 获取thymeleaf模板,填充数据
String emailContent = templateEngine.process("resume/notifyHR", context);
helper.setText(emailContent, true);
// 发送填充好的整个html页面
javaMailSender.send(message);
log.info("简历投递提醒发送成功。");
} catch (Exception e) {
log.error("简历投递提醒发送失败!", e);
}
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="resume-wrap">
<div class="resume-header">
<!-- <img class="logo-title" src="https://wise-job.oss-cn-zhangjiakou.aliyuncs.com/wiseJob/1601028532357_430*289.jpg">-->
<span style="margin-right: 10px;">知乎</span>
<div class="header-title">你好</div>
</div>
<div class="resume-body">
<p>Hi, <span th:text="${publisher}">马云</span></p>
<div class="body_content">你在知乎上发布的<span class="resume_position" th:text="${jobName}">销售经理-杭州</span>职位,有候选人感兴趣,并通过邮箱给你发了一封简历,请点击邮箱附件查看其附件简历。</div>
<div class="position-card">
<div class="avatar">
<img th:if="${sex eq 1}" src="https://wise-job.oss-cn-zhangjiakou.aliyuncs.com/wiseJob/1600923429345_60*60.png" alt="" class="avatar-sex-icon">
<img th:if="${sex eq 0}" src="https://wise-job.oss-cn-zhangjiakou.aliyuncs.com/wiseJob/1600923451797_60*60.png" alt="" class="avatar-sex-icon">
<img th:src="${icon}" alt="">
</div>
<div class="card-info">
<div class="info-name"><span th:text="${candidate}">陈先生</span></div>
<div class="info-pos">
<span th:text="${school}">上海交通大学</span>
<span th:text="${major}">软件工程</span>
<span th:text="${education}">学士</span>
<span th:text="${salaryBegin}">6</span>K-<span th:text="${salaryEnd}">8</span>K / 月</span>
</div>
<div class="info-duty info-bottom">
<img src="https://wise-job.oss-cn-zhangjiakou.aliyuncs.com/wiseJob/1600923405293_23*23.png" alt="职位图标">
<span th:text="${expectJob}">Java开发工程师</span>
</div>
<div class="info-address info-bottom">
<img src="https://wise-job.oss-cn-zhangjiakou.aliyuncs.com/wiseJob/1600923377292_23*23.png" alt="地理位置">
<span th:text="${expectCity}">杭州</span>
</div>
</div>
</div>
<div style="text-align: center;">
<a class="resume-bottom" th:href="${appendixUrl}" target="_blank">查看<span th:text="${appendixName}"></span>附件简历</a>
</div>
</div>
</div>
</body>
</html>
<style>
.resume-wrap {
width: 621px;
margin: 0 auto;
background: #FFFFFF;
border-radius: 4px;
box-shadow: 0px 0px 18px 0px rgba(21, 21, 22, 0.17);
}
.logo-title {
height: 25px;
margin-right: 14px;
}
.header-title {
padding-left: 14px;
border-left: 1px solid #fff;
}
.resume-header {
display: flex;
align-items: center;
padding: 0 22px;
font-size: 20px;
color: #fff;
height: 75px;
border-radius: 4px 4px 0px 0px;
background: linear-gradient(251deg, #5FAEF9 0%, #3985FD 100%);
}
.resume-body {
padding: 50px;
color: #131415;
}
.resume_position {
color:#5FAEF9;
}
.body_content {
line-height: 25px;
}
.position-card {
display: flex;
margin-top: 36px;
border-radius: 4px;
border: 1px solid #DBE0E6;
padding: 30px;
}
.avatar {
position: relative;
width: 60px;
height: 60px;
border-radius: 50%;
}
.avatar img {
width: 100%;
height: 100%;
border-radius: 50%;
border: 1px solid #DBE0E6;
}
.avatar .avatar-sex-icon {
position: absolute;
width: 16px;
height: 16px;
bottom: 0;
right: 0;
}
.card-info {
margin-left: 15px;
}
.info-name {
font-size: 18px;
font-weight: 600;
color: #3C3E43;
line-height: 25px;
}
.info-pos {
margin-top: 10px;
margin-bottom: 16px;
color: #3C3E43;
font-size: 14px;
}
.info-pos span {
padding: 0 10px;
border-right: 1px solid #DBE0E6;
}
.info-pos span:first-child {
padding-left: 0;
}
.info-pos span:nth-child(n+4) {
padding-right: 0;
border-right: none;
}
.info-pos span:nth-child(n+5) {
padding: 0;
}
.info-bottom {
font-size: 14px;
font-weight: 400;
color: #7F8289;
line-height: 20px;
margin-bottom: 10px;
}
.info-bottom img{
width: 16px;
height: 16px;
vertical-align: middle;
margin-left: 2px;
}
.resume-bottom {
margin: 46px auto 0;
color: #fff;
padding:0 20px;
/*width: 230px;*/
height: 44px;
line-height: 44px;
/*background: #F9791B;*/
border-radius: 4px;
text-align: center;
cursor: pointer;
background: #5FAEF9;
}
a {
display: inline-block;
margin: 10px;
color: #F9791B;
}
.resume-awrap {
height: 30px;
}
</style>
稍微提一下模板路径的问题:Thymeleaf默认目录是templates,默认模板后缀是.html,所以可以都省略。
效果展示:
这样式,感觉还蛮好看的。(^..^)
作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO
进群,大家一起学习,一起进步,一起对抗互联网寒冬