10 分钟实现 Spring Boot 发生邮件功能

共 8837字,需浏览 18分钟

 ·

2022-01-23 13:30

作者:yizhiwazi

https://www.jianshu.com/p/5eb000544dd7

SpringBoot 集成邮件服务竟如此简单,还等什么,快来学习涨姿势吧!!

学习目标

  • 快速掌握邮件业务类的核心逻辑和企业邮件的日常服务。

源码下载:springboot-send-mail:https://github.com/yizhiwazi/springboot-socks

— Hey Man,Don't forget to Star or Fork . —

什么是 SMTP?

SMTP 全称为 Simple Mail Transfer Protocol(简单邮件传输协议),它是一组用于从源地址到目的地址传输邮件的规范,通过它来控制邮件的中转方式。SMTP 认证要求必须提供账号和密码才能登陆服务器,其设计目的在于避免用户受到垃圾邮件的侵扰。

什么是 IMAP?

IMAP 全称为 Internet Message Access Protocol(互联网邮件访问协议),IMAP 允许从邮件服务器上获取邮件的信息、下载邮件等。IMAP 与 POP 类似,都是一种邮件获取协议。

什么是 POP3?

POP3 全称为 Post Office Protocol 3(邮局协议),POP3 支持客户端远程管理服务器端的邮件。POP3 常用于 “离线” 邮件处理,即允许客户端下载服务器邮件,然后服务器上的邮件将会被删除。目前很多 POP3 的邮件服务器只提供下载邮件功能,服务器本身并不删除邮件,这种属于改进版的 POP3 协议。

IMAP 和 POP3 协议有什么不同呢?

两者最大的区别在于,IMAP 允许双向通信,即在客户端的操作会反馈到服务器上,例如在客户端收取邮件、标记已读等操作,服务器会跟着同步这些操作。而对于 POP 协议虽然也允许客户端下载服务器邮件,但是在客户端的操作并不会同步到服务器上面的,例如在客户端收取或标记已读邮件,服务器不会同步这些操作。

什么是 JavaMailSender和 JavaMailSenderImpl

JavaMailSender和 JavaMailSenderImpl 是 Spring 官方提供的集成邮件服务的接口和实现类,以简单高效的设计著称,目前是 Java 后端发送邮件和集成邮件服务的主流工具。

如何通过 JavaMailSenderImpl发送邮件?

非常简单,直接在业务类注入 JavaMailSenderImpl并调用 send方法发送邮件。其中简单邮件可以通过 SimpleMailMessage来发送邮件,而复杂的邮件(例如添加附件)可以借助 MimeMessageHelper来构建 MimeMessage发送邮件。例如:

  1. @Autowired

  2. private JavaMailSenderImpl mailSender;


  3. public void sendMail() throws MessagingException {


  4. SimpleMailMessage simpleMailMessage = new SimpleMailMessage();

  5. simpleMailMessage.setFrom("admin@163.com");

  6. simpleMailMessage.setTo("socks@qq.com");

  7. simpleMailMessage.setSubject("Happy New Year");

  8. simpleMailMessage.setText("新年快乐!");

  9. mailSender.send(simpleMailMessage);



  10. MimeMessage mimeMessage = mailSender.createMimeMessage();

  11. MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage);

  12. messageHelper.setFrom("admin@163.com");

  13. messageHelper.setTo("socks@qq.com");

  14. messageHelper.setSubject("Happy New Year");

  15. messageHelper.setText("新年快乐!");

  16. messageHelper.addInline("doge.gif", new File("xx/xx/doge.gif"));

  17. messageHelper.addAttachment("work.docx", new File("xx/xx/work.docx"));

  18. mailSender.send(mimeMessage);

  19. }

为什么 JavaMailSenderImpl 能够开箱即用 ?

所谓开箱即用其实就是基于官方内置的自动配置,翻看源码可知晓邮件自动配置类 (MailSenderPropertiesConfiguration) 为上下文提供了邮件服务实例 (JavaMailSenderImpl)。具体源码如下:

  1. @Configuration

  2. @ConditionalOnProperty(prefix = "spring.mail", name = "host")

  3. class MailSenderPropertiesConfiguration {

  4. private final MailProperties properties;

  5. MailSenderPropertiesConfiguration(MailProperties properties) {

  6. this.properties = properties;

  7. }

  8. @Bean

  9. @ConditionalOnMissingBean

  10. public JavaMailSenderImpl mailSender() {

  11. JavaMailSenderImpl sender = new JavaMailSenderImpl();

  12. applyProperties(sender);

  13. return sender;

  14. }

其中 MailProperties是关于邮件服务器的配置信息,具体源码如下:

  1. @ConfigurationProperties(prefix = "spring.mail")

  2. public class MailProperties {

  3. private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

  4. private String host;

  5. private Integer port;

  6. private String username;

  7. private String password;

  8. private String protocol = "smtp";

  9. private Charset defaultEncoding = DEFAULT_CHARSET;

  10. private Map<String, String> properties = new HashMap<>();

  11. }

一、开启邮件服务

登陆网易邮箱 163,在设置中打开并勾选 POP3/SMTP/IMAP服务,然后会得到一个授权码,这个邮箱和授权码将用作登陆认证。关注公众号:Java后端编程 !

二、配置邮件服务

首先咱们通过 Spring Initializr 创建工程 springboot-send-mail,如图所示:

然后在 pom.xml 引入 webthymeleaf 和 spring-boot-starter-mail等相关依赖。例如:

  1. org.springframework.boot

  2. spring-boot-starter-web

  3. org.springframework.boot

  4. spring-boot-starter-thymeleaf

  5. org.springframework.boot

  6. spring-boot-starter-mail

  7. org.webjars

  8. webjars-locator-core

  9. org.webjars

  10. jquery

  11. 3.3.1

  12. org.webjars

  13. bootstrap

  14. 3.3.7

  15. org.springframework.boot

  16. spring-boot-devtools

  17. runtime

  18. org.springframework.boot

  19. spring-boot-starter-test

  20. test

根据前面提到的配置项 (MailProperties)填写相关配置信息,其中 spring.mail.username 表示连接邮件服务器时认证的登陆账号,可以是普通的手机号或者登陆账号,并非一定是邮箱,为了解决这个问题,推荐大家在 spring.mail.properties.from填写邮件发信人即真实邮箱。

然后在 application.yml添加如下配置:

  1. spring:

  2. mail:

  3. host: smtp.163.com #SMTP服务器地址

  4. username: socks #登陆账号

  5. password: 123456 #登陆密码(或授权码)

  6. properties:

  7. from: socks@163.com #邮件发信人(即真实邮箱)

  8. thymeleaf:

  9. cache: false

  10. prefix: classpath:/views/

  11. servlet:

  12. multipart:

  13. max-file-size: 10MB #限制单个文件大小

  14. max-request-size: 50MB #限制请求总量

透过前面的进阶知识,我们知道在发送邮件前,需要先构建 SimpleMailMessage或 MimeMessage 邮件信息类来填写邮件标题、邮件内容等信息,最后提交给 JavaMailSenderImpl发送邮件,这样看起来没什么问题,也能实现既定目标,但在实际使用中会出现大量零散和重复的代码,还不便于保存邮件到数据库。

那么优雅的发送邮件应该是如何的呢?应该屏蔽掉这些构建信息和发送邮件的细节,不管是简单还是复杂邮件,都可以通过统一的 API 来发送邮件。例如: mailService.send(mailVo) 。

例如通过邮件信息类 (MailVo) 来保存发送邮件时的邮件主题、邮件内容等信息 :

  1. package com.hehe.vo;


  2. public class MailVo {

  3. private String id;

  4. private String from;

  5. private String to;

  6. private String subject;

  7. private String text;

  8. private Date sentDate;

  9. private String cc;

  10. private String bcc;

  11. private String status;

  12. private String error;

  13. @JsonIgnore

  14. private MultipartFile[] multipartFiles;


  15. }

三、发送邮件和附件

=========== 接下来正式介绍发送邮件的最核心逻辑 前方高能 =============

除了发送邮件之外,还包括检测邮件和保存邮件等操作,例如:

  • 检测邮件 checkMail(); 首先校验邮件收信人、邮件主题和邮件内容这些必填项,若为空则拒绝发送。

  • 发送邮件 sendMimeMail(); 其次通过 MimeMessageHelper 来解析 MailVo 并构建 MimeMessage 传输邮件。

  • 保存邮件 sendMimeMail(); 最后将邮件保存到数据库,便于统计和追查邮件问题。

本案例邮件业务类 MailService 的具体源码如下:

  1. package com.hehe.service;



  2. @Service

  3. public class MailService {


  4. private Logger logger = LoggerFactory.getLogger(getClass());


  5. @Autowired

  6. private JavaMailSenderImpl mailSender;




  7. public MailVo sendMail(MailVo mailVo) {

  8. try {

  9. checkMail(mailVo);

  10. sendMimeMail(mailVo);

  11. return saveMail(mailVo);

  12. } catch (Exception e) {

  13. logger.error("发送邮件失败:", e);

  14. mailVo.setStatus("fail");

  15. mailVo.setError(e.getMessage());

  16. return mailVo;

  17. }


  18. }



  19. private void checkMail(MailVo mailVo) {

  20. if (StringUtils.isEmpty(mailVo.getTo())) {

  21. throw new RuntimeException("邮件收信人不能为空");

  22. }

  23. if (StringUtils.isEmpty(mailVo.getSubject())) {

  24. throw new RuntimeException("邮件主题不能为空");

  25. }

  26. if (StringUtils.isEmpty(mailVo.getText())) {

  27. throw new RuntimeException("邮件内容不能为空");

  28. }

  29. }



  30. private void sendMimeMail(MailVo mailVo) {

  31. try {

  32. MimeMessageHelper messageHelper = new MimeMessageHelper(mailSender.createMimeMessage(), true);

  33. mailVo.setFrom(getMailSendFrom());

  34. messageHelper.setFrom(mailVo.getFrom());

  35. messageHelper.setTo(mailVo.getTo().split(","));

  36. messageHelper.setSubject(mailVo.getSubject());

  37. messageHelper.setText(mailVo.getText());

  38. if (!StringUtils.isEmpty(mailVo.getCc())) {

  39. messageHelper.setCc(mailVo.getCc().split(","));

  40. }

  41. if (!StringUtils.isEmpty(mailVo.getBcc())) {

  42. messageHelper.setCc(mailVo.getBcc().split(","));

  43. }

  44. if (mailVo.getMultipartFiles() != null) {

  45. for (MultipartFile multipartFile : mailVo.getMultipartFiles()) {

  46. messageHelper.addAttachment(multipartFile.getOriginalFilename(), multipartFile);

  47. }

  48. }

  49. if (StringUtils.isEmpty(mailVo.getSentDate())) {

  50. mailVo.setSentDate(new Date());

  51. messageHelper.setSentDate(mailVo.getSentDate());

  52. }

  53. mailSender.send(messageHelper.getMimeMessage());

  54. mailVo.setStatus("ok");

  55. logger.info("发送邮件成功:{}->{}", mailVo.getFrom(), mailVo.getTo());

  56. } catch (Exception e) {

  57. throw new RuntimeException(e);

  58. }

  59. }



  60. private MailVo saveMail(MailVo mailVo) {


  61. return mailVo;

  62. }



  63. public String getMailSendFrom() {

  64. return mailSender.getJavaMailProperties().getProperty("from");

  65. }

  66. }

搞定了发送邮件最核心的业务逻辑,接下来咱们写一个简单页面用来发送邮件。

首先写好跟页面交互的控制器 MailController,具体源码如下:

  1. @RestController

  2. public class MailController {

  3. @Autowired

  4. private MailService mailService;



  5. @GetMapping("/")

  6. public ModelAndView index() {

  7. ModelAndView mv = new ModelAndView("mail/sendMail");

  8. mv.addObject("from", mailService.getMailSendFrom());

  9. return mv;

  10. }


  11. @PostMapping("/mail/send")

  12. public MailVo sendMail(MailVo mailVo, MultipartFile[] files) {

  13. mailVo.setMultipartFiles(files);

  14. return mailService.sendMail(mailVo);

  15. }

  16. }

然后在 /resources/views/mail目录新建 sendMail.html,具体源码如下:

  1. xmlns:th="http://www.thymeleaf.org">


  2. charset="UTF-8"/>

  3. </span><span style="color: rgb(204, 204, 204);line-height: 20px;font-size: 13px !important;white-space: inherit !important;">发送邮件</span><span style="color: rgb(242, 119, 122);line-height: 20px;font-size: 13px !important;white-space: inherit !important;">

  4. th:href="@{/webjars/bootstrap/css/bootstrap.min.css}" rel="stylesheet" type="text/css"/>



  5. behavior="alternate" onfinish="alert(12)"

  6. onMouseOut="this.start();$('#egg').text('嗯 真听话!');"

  7. onMouseOver="this.stop();$('#egg').text('有本事放开我呀!');">

  8. 祝大家新年快乐!
    src="http://pics.sc.chinaz.com/Files/pic/faces/3709/7.gif" alt="">


  9. 邮件发信人:

  10. ${from}" readonly="readonly">

  • 邮件收信人:

  • >

  • 邮件主题:

  • >

  • 邮件内容:

  • >

  • 邮件附件:

  • >

  • 邮件操作:

  • onclick="sendMail()">发送邮件

  • onclick="clearForm()">清空


  • 四、测试发送邮件

    如果是初学者,建议大家先下载源码,修改配置后运行工程,成功后再自己重新写一遍代码,这样有助于加深记忆。

    启动工程并访问:http://localhost:8080 然后可以看到发送邮件的主界面如下:

    然后填写你的小号邮箱,点击发送邮件,若成功则可以登陆小号邮箱查看邮件和刚才上传的附件。

    至此发送邮件代码全部完成,欢迎大家下载并关注 Github 源码。

    五、常见失败编码

    如果企业定制了邮件服务器,自然会记录邮件日志,根据错误编码存储日志有利于日常维护。

    例如这些由网易邮箱提供的错误编码标识:

    -553
      553 Requested action not taken: NULL sender is not allowed 不允许发件人为空,请使用真实发件人发送;
      553 Requested action not taken: Local user only SMTP 类型的机器只允许发信人是本站用户;
      553 Requested action not taken: no smtp MX only MX 类型的机器不允许发信人是本站用户;
      553 authentication is required SMTP 需要身份验证,请检查客户端设置;

    -554
      554 DT:SPM 发送的邮件内容包含了未被许可的信息,或被系统识别为垃圾邮件。请检查是否有用户发送病毒或者垃圾邮件;
      554 DT:SUM 信封发件人和信头发件人不匹配;
      554 IP is rejected, smtp auth error limit exceed 该 IP 验证失败次数过多,被临时禁止连接。请检查验证信息设置;
      554 HL:IHU 发信 IP 因发送垃圾邮件或存在异常的连接行为,被暂时挂起。请检测发信 IP 在历史上的发信情况和发信程序是否存在异常;
      554 HL:IPB 该 IP 不在网易允许的发送地址列表里;
      554 MI:STC 发件人当天内累计邮件数量超过限制,当天不再接受该发件人的投信。请降低发信频率;
      554 MI:SPB 此用户不在网易允许的发信用户列表里;
      554 IP in blacklist 该 IP 不在网易允许的发送地址列表里。



    PS:如果觉得我的分享不错,欢迎大家随手点赞、在看。


    最近面试BAT,整理一份面试资料Java面试BAT通关手册,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
    获取方式:关注公众号并回复 java 领取,更多内容陆续奉上。
    明天见(。・ω・。)ノ♡
    浏览 23
    点赞
    评论
    收藏
    分享

    手机扫一扫分享

    分享
    举报
    评论
    图片
    表情
    推荐
    点赞
    评论
    收藏
    分享

    手机扫一扫分享

    分享
    举报