十分钟实现发送邮件服务



发送邮件应该是网站的必备拓展功能之一,注册验证、忘记密码或者是给用户发送营销信息。

一、邮件协议

在收发邮件的过程中,需要遵守相关的协议,其中主要有:

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

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

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

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

二、初始化配置

本文仅以 QQ邮箱和 163邮箱为例。

正常我们会用 JavaMail相关 api来写发送邮件的相关代码,但现在 Spring Boot提供了一套更简易使用的封装。


    org.springframework.boot
    spring-boot-starter-test

    org.springframework.boot
    spring-boot-starter-mail

    org.springframework.boot
    spring-boot-starter-thymeleaf

    org.projectlombok
    lombok
    1.8.4

spring-boot-starter-mail 的配置由 MailProperties 配置类提供。

针对不同的邮箱的配置略有不同,以下是 QQ邮箱和 163邮箱的配置。

server:
  port: 8081
#spring:
 mail:
   # QQ 邮箱 https://service.mail.qq.com/cgi-bin/help?subtype=1&&no=1001256&&id=28
   host: smtp.qq.com
   # 邮箱账号
   username: van93@qq.com
   # 邮箱授权码(不是密码)
   password: password
   default-encoding: UTF-8
   properties:
     mail:
       smtp:
         auth: true
         starttls:
           enable: true
           required: true
spring:
  mail:
    # 163 邮箱 http://help.mail.163.com/faqDetail.do?code=d7a5dc8471cd0c0e8b4b8f4f8e49998b374173cfe9171305fa1ce630d7f67ac2cda80145a1742516
    host: smtp.163.com
    # 邮箱账号
    username: 17098705205@163.com
    # 邮箱授权码(不是密码)
    password: password
    default-encoding: UTF-8
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
            required: true

来保存发送邮件时的邮件主题、邮件内容等信息

@Data
public class Mail {
    /**
     * 邮件id
     */
    private String id;
    /**
     * 邮件发送人
     */
    private String sender;
    /**
     * 邮件接收人 (多个邮箱则用逗号","隔开)
     */
    private String receiver;
    /**
     * 邮件主题
     */
    private String subject;
    /**
     * 邮件内容
     */
    private String text;
    /**
     * 附件/文件地址
     */
    private String filePath;
    /**
     * 附件/文件名称
     */
    private String fileName;
    /**
     * 是否有附件(默认没有)
     */
    private Boolean isTemplate = false;
    /**
     * 模版名称
     */
    private String emailTemplateName;
    /**
     * 模版内容
     */
    private Context emailTemplateContext;

}

三、发送邮件的实现

校验邮件收信人、邮件主题和邮件内容这些必填项

private void checkMail(Mail mail) {
    if (StringUtils.isEmpty(mail.getReceiver())) {
        throw new RuntimeException("邮件收信人不能为空");
    }
    if (StringUtils.isEmpty(mail.getSubject())) {
        throw new RuntimeException("邮件主题不能为空");
    }
    if (StringUtils.isEmpty(mail.getText()) && null == mail.getEmailTemplateContext()) {
        throw new RuntimeException("邮件内容不能为空");
    }
}

发送结束后将邮件保存到数据库,便于统计和追查邮件问题。

private Mail saveMail(Mail mail) {
    // todo 发送成功/失败将邮件信息同步到数据库
    return mail;
}
  • 发送纯文本邮件
public void sendSimpleMail(Mail mail){
    checkMail(mail);
    SimpleMailMessage mailMessage = new SimpleMailMessage();
    mailMessage.setFrom(sender);
    mailMessage.setTo(mail.getReceiver());
    mailMessage.setSubject(mail.getSubject());
    mailMessage.setText(mail.getText());
    mailSender.send(mailMessage);
    saveMail(mail);
}
  • 发送邮件并携带附件
public void sendAttachmentsMail(Mail mail) throws MessagingException {
    checkMail(mail);
    MimeMessage mimeMessage = mailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
    helper.setFrom(sender);
    helper.setTo(mail.getReceiver());
    helper.setSubject(mail.getSubject());
    helper.setText(mail.getText());
    File file = new File(mail.getFilePath());
    helper.addAttachment(file.getName(), file);
    mailSender.send(mimeMessage);
    saveMail(mail);
}
  • 发送模版邮件
public void sendTemplateMail(Mail mail) throws MessagingException {
    checkMail(mail);
    // templateEngine 替换掉动态参数,生产出最后的html
    String emailContent = templateEngine.process(mail.getEmailTemplateName(), mail.getEmailTemplateContext());

    MimeMessage mimeMessage = mailSender.createMimeMessage();

    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
    helper.setFrom(sender);
    helper.setTo(mail.getReceiver());
    helper.setSubject(mail.getSubject());
    helper.setText(emailContent, true);
    mailSender.send(mimeMessage);
    saveMail(mail);
}

四、测试及优化

@RunWith(SpringRunner.class)
@SpringBootTest
public class MailServiceTest {

    @Resource
    MailService mailService;

    /**
     * 发送纯文本邮件
     */
    @Test
    public void sendSimpleMail() {
        Mail mail = new Mail();
//        mail.setReceiver("17098705205@163.com");
        mail.setReceiver("van93@qq.com");
        mail.setSubject("测试简单邮件");
        mail.setText("测试简单内容");
        mailService.sendSimpleMail(mail);
    }

    /**
     * 发送邮件并携带附件
     */
    @Test
    public void sendAttachmentsMail() throws MessagingException {
        Mail mail = new Mail();
//        mail.setReceiver("17098705205@163.com");
        mail.setReceiver("van93@qq.com");
        mail.setSubject("测试附件邮件");
        mail.setText("附件邮件内容");
        mail.setFilePath("file/dusty_blog.jpg");
        mailService.sendAttachmentsMail(mail);
    }

    /**
     * 测试模版邮件邮件
     */
    @Test
    public void sendTemplateMail() throws MessagingException {
        Mail mail = new Mail();
//        mail.setReceiver("17098705205@163.com");
        mail.setReceiver("van93@qq.com");
        mail.setSubject("测试模版邮件邮件");
        //创建模版正文
        Context context = new Context();
        // 设置模版需要更换的参数
        context.setVariable("verifyCode", "6666");
        mail.setEmailTemplateContext(context);
        // 模版名称(模版位置位于templates目录下)
        mail.setEmailTemplateName("emailTemplate");
        mailService.sendTemplateMail(mail);
    }

}

因为平时发送邮件还有抄送/密送等需求,这里,封装一个实体和工具类,便于直接调用邮件服务。

  • 邮件信息类
@Data
public class MailDomain {
    /**
     * 邮件id
     */
    private String id;
    /**
     * 邮件发送人
     */
    private String sender;
    /**
     * 邮件接收人 (多个邮箱则用逗号","隔开)
     */
    private String receiver;
    /**
     * 邮件主题
     */
    private String subject;
    /**
     * 邮件内容
     */
    private String text;

    /**
     * 抄送(多个邮箱则用逗号","隔开)
     */
    private String cc;
    /**
     * 密送(多个邮箱则用逗号","隔开)
     */
    private String bcc;
    /**
     * 附件/文件地址
     */
    private String filePath;
    /**
     * 附件/文件名称
     */
    private String fileName;
    /**
     * 是否有附件(默认没有)
     */
    private Boolean isTemplate = false;
    /**
     * 模版名称
     */
    private String emailTemplateName;
    /**
     * 模版内容
     */
    private Context emailTemplateContext;
    /**
     * 发送时间(可指定未来发送时间)
     */
    private Date sentDate;
}
  • 邮件工具类
@Component
public class EmailUtil {

    @Resource
    private JavaMailSender mailSender;

    @Resource
    TemplateEngine templateEngine;

    @Value("${spring.mail.username}")
    private String sender;

    /**
     * 构建复杂邮件信息类
     * @param mail
     * @throws MessagingException
     */
    public void sendMail(MailDomain mail) throws MessagingException {

        //true表示支持复杂类型
        MimeMessageHelper messageHelper = new MimeMessageHelper(mailSender.createMimeMessage(), true);
        //邮件发信人从配置项读取
        mail.setSender(sender);
        //邮件发信人
        messageHelper.setFrom(mail.getSender());
        //邮件收信人
        messageHelper.setTo(mail.getReceiver().split(","));
        //邮件主题
        messageHelper.setSubject(mail.getSubject());
        //邮件内容
        if (mail.getIsTemplate()) {
            // templateEngine 替换掉动态参数,生产出最后的html
            String emailContent = templateEngine.process(mail.getEmailTemplateName(), mail.getEmailTemplateContext());
            messageHelper.setText(emailContent, true);
        }else {
            messageHelper.setText(mail.getText());
        }
        //抄送
        if (!StringUtils.isEmpty(mail.getCc())) {
            messageHelper.setCc(mail.getCc().split(","));
        }
        //密送
        if (!StringUtils.isEmpty(mail.getBcc())) {
            messageHelper.setCc(mail.getBcc().split(","));
        }
        //添加邮件附件
        if (mail.getFilePath() != null) {
            File file = new File(mail.getFilePath());
            messageHelper.addAttachment(file.getName(), file);
        }
        //发送时间
        if (StringUtils.isEmpty(mail.getSentDate())) {
            messageHelper.setSentDate(mail.getSentDate());
        }
        //正式发送邮件
        mailSender.send(messageHelper.getMimeMessage());
    }

    /**
     * 检测邮件信息类
     * @param mail
     */
    private void checkMail(MailDomain mail) {
        if (StringUtils.isEmpty(mail.getReceiver())) {
            throw new RuntimeException("邮件收信人不能为空");
        }
        if (StringUtils.isEmpty(mail.getSubject())) {
            throw new RuntimeException("邮件主题不能为空");
        }
        if (StringUtils.isEmpty(mail.getText()) && null == mail.getEmailTemplateContext()) {
            throw new RuntimeException("邮件内容不能为空");
        }
    }

    /**
     * 将邮件保存到数据库
     * @param mail
     * @return
     */
    private MailDomain saveMail(MailDomain mail) {
        // todo 发送成功/失败将邮件信息同步到数据库
        return mail;
    }
}

五、 总结及延伸

很多时候邮件发送并不是我们主业务必须关注的结果,比如通知类、提醒类的业务可以允许延时或者失败。这个时候可以采用异步的方式来发送邮件,加快主交易执行速度,在实际项目中可以采用 MQ发送邮件相关参数,监听到消息队列之后启动发送邮件。

因为各种原因,总会有邮件发送失败的情况,比如:邮件发送过于频繁、网络异常等。在出现这种情况的时候,我们一般会考虑重新重试发送邮件,会分为以下几个步骤来实现:

邮件端口问题和附件大小问题。

Original: https://www.cnblogs.com/VanFan/p/12592681.html
Author: 风尘博客
Title: 十分钟实现发送邮件服务

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/573566/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Java SE 3、封装

    提供一个公共的(public)set方法,用于对属性判断并赋值 public void setXxx(类型 参数名){ //Xxx表示某个属性 ​ //加入数据验证的业务逻辑 ​ …

    Java 2023年6月7日
    046
  • 国庆节,零代码帮你搞定假期美食菜单

    当国庆假期遇上美食 每一口都唇齿留香 特色美食太多,不知道吃什么? AppCube带你一分钟搞定假期美食 来一场舌尖上的旅行 零代码,让假期生活有滋有味 国庆小长假,三五好友结伴出…

    Java 2023年6月15日
    026
  • 生产计划体系完整解决方案(1)-复杂大规模问题的分阶段规划

    背景 在过往参与的一些项目支持工作,以及平台发布后各位小伙伴使用过程中,经常遇到这样的问题:你这个引擎性能怎么样?可以处理多大数据量的排程?我有数万个任务,这个引擎多长时间可以排产…

    Java 2023年6月16日
    036
  • ZXing二维码解析&精度提高一瞥

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    Java 2022年10月12日
    0179
  • SpringCloud : 多个 @FeignClient 注解 value 设置为同一个应用的解决方案

    Feign 版本10.1.0 Spring 版本 5.1.5.RELEASE SpringBoot 版本 2.1.5.RELEASE SpringCloud 版本 2.1.1.RE…

    Java 2023年5月30日
    031
  • JAVA利用enum结合testng做数据驱动示例

    数据驱动是做自动化测试中很重要的一部分,数据源的方案也是百花八门了,比如利用外部文件,直接在@DataProvider中写死等等,我们今天介绍一下利用enum来做数据源,先来看一下…

    Java 2023年5月29日
    029
  • mqtt实现跨平台跨应用通讯

    介绍 最近物联网应用一直很火,也打算做一些这方面的尝试,就边学边做在家花了2天时间做了一个简单demo,功能很简单,使用emq x 作为mqtt broker,用python写了一…

    Java 2023年5月30日
    053
  • markdown语法学习

    markdown语法学习 标题: 一共有六级标题一级标题:#号+空格+标题名称 或 ctrl+1 二级标题:##号+空格+标题名称或ctrl+2 以此类推…&#8230…

    Java 2023年6月7日
    037
  • 浅尝Spring注解开发_Bean生命周期及执行过程

    Spring注解开发_Bean生命周期及执行过程 浅尝Spring注解开发,基于Spring 4.3.12包含Bean生命周期、自定义初始化方法、Debug BeanPostPro…

    Java 2023年6月5日
    036
  • 通用树形结构的迭代与组合模式实现方案

    日常开发过程过程中。树形结构运用的非常频繁。 例如:公司组织结构、各种分类结构、分组结构等等。 SET FOREIGN_KEY_CHECKS = 0; CREATE TABLE I…

    Java 2023年6月9日
    031
  • Arthas之实例操作

    Arthas之实例操作 1. 静态类属性操作 获取public静态属性 ognl -c 7cd84586 ‘@com.system.framework.ArtahsDemoClas…

    Java 2023年6月13日
    027
  • Nginx 同一个服务器设置二级域名

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    Java 2022年9月23日
    0143
  • 家教小程序的设计与实现

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    Java 2022年10月16日
    0158
  • Java高并发教程:Java NIO简介

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    Java 2022年9月21日
    0165
  • 自动装配

    Bean自动装配 自动装配是Spring满足bean依赖一种方式 Spring会在上下文中自动寻找,并自动给bean装配属性 在Spring中有三种装配的方式 在xml中显示的配置…

    Java 2023年6月14日
    023
  • @RequestBody使用说明

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    Java 2022年11月12日
    0143
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球