SpringBoot 邮件服务集成配置全面解析

前言

本文以网易邮箱(及 163 邮箱)为例,展示如何为 SpringBoot 项目集成邮件服务,其他邮箱配置类似,可以自行查看 Spring Email 指南 或是其他官方文档

授权码

首先我们需要获取授权码,用于后续配置,登录邮箱: https://mail.163.com/

点击顶端设置,之后选择 POP3/SMTP/IMAP 选项

POP3/SMTP 服务已开启 – 开启该服务,开启是需要验证手机号发送验证码。

验证完成会返回授权码,该授权码只显示一次,记得保存,否则需要重新发送验证码获取新的授权码

添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

配置文件

Spring Boot 为 JavaMailSender 提供了自动配置以及启动器模块。

spring:
  mail:  
    default-encoding: UTF-8  
    host: smtp.163.com # 网站发送邮件邮箱服务 host    port: 465  
    username: xxx@163.com # 邮箱  
    password: ONSWXXXXXXXX # 授权码 
    protocol: smtp  
    properties:  
      mail:  
        smtp:  
          auth: 'true'  
          socketFactory:  
            class: com.rymcu.forest.util.MailSSLSocketFactory  
#            class: javax.net.ssl.SSLSocketFactory  
            port: 465  
          ssl:  
            enable: true  
          starttls:  
            enable: true  
          stattls:  
            required: true  
          connectiontimeout: 5000  
          timeout: 3000  
          writetimeout: 5000

注意:上面的 password 不是你的邮箱密码,而是我们上一章节得到的授权码

相关参数介绍

  • default-encoding: 默认编码格式,这里设置为 UTF-8。
  • host: SMTP服务器的地址,这里是163邮箱的SMTP服务器地址。
  • port: SMTP服务器的端口,163邮箱的SMTP端口是465。
  • username: 163邮箱账号。
  • password: 我们上面得到的授权码。
  • protocol: 使用的协议,这里是SMTP协议。
  • properties: 额外的属性设置。
    • mail: 邮件相关的属性。
      • smtp: SMTP相关的属性。
        • auth: 是否需要认证,这里设置为true,表示需要认证。
        • socketFactory: Socket工厂相关设置。
          • class: Socket工厂类,表示使用SSL加密。
          • port: Socket工厂使用的端口,这里也是465。
        • ssl: SSL相关设置。
          • enable: 是否启用SSL,这里设置为true,表示启用SSL加密。
        • starttls: STARTTLS相关设置。
          • enable: 是否启用STARTTLS,这里设置为true,表示启用STARTTLS。
        • stattls: STARTTLS相关设置。
          • required: 是否要求STARTTLS,这里设置为true,表示要求STARTTLS。
        • connectiontimeout: 连接超时时间,单位为毫秒,这里设置为5000毫秒(5秒)。
        • timeout: 操作超时时间,单位为毫秒,这里设置为3000毫秒(3秒)。
        • writetimeout: 写超时时间,单位为毫秒,这里设置为5000毫秒(5秒)。

更多详细信息可以查看 Spring 官方文档:36. Sending Email (spring.io)

MailProperties 源码:MailProperties.java at v2.0.3.RELEASE

如果不想要给部分配置信息写在 application.yml 中,也可以直接硬编码在代码里

        Properties props = new Properties();
        // 表示SMTP发送邮件,需要进行身份验证
        props.put("mail.smtp.auth", true);
        props.put("mail.smtp.ssl.enable", true);
        props.put("mail.smtp.host", SERVER_HOST);
        props.put("mail.smtp.port", SERVER_PORT);
        // 如果使用ssl,则去掉使用25端口的配置,进行如下配置
        props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        props.put("mail.smtp.socketFactory.port", SERVER_PORT);
        // 发件人的账号,填写控制台配置的发信地址,比如xxx@xxx.com
        props.put("mail.user", USERNAME);
        // 访问SMTP服务时需要提供的密码(在控制台选择发信地址进行设置)
        props.put("mail.password", PASSWORD);
        // 配置
        mailSender.setJavaMailProperties(props);

还有就是大家可以注意到,上面指定端口为 465,这是因为 SMTP 服务默认的 25 端口阿里云默认是禁用状态,详情请看阿里云官方文档

所以如果本地调试我们不指定 port 是没问题的,但是阿里云线上是无法通过端口 25 发送邮件的,建议直接指定指定 465 端口(使用 SSL),或是 80 端口(不使用 SSL)。虽然通过一定手段可以解封 25 端口,但是比较麻烦,且成功率不高

编写代码

注:代码部分来自仓库:rymcu/forest: forest(森林),同时进行了改动

SSL 相关配置

本章节为需要使用 https 协议的相关配置,没有该需求这个小章节可以先跳过,给后面配置完后再来配置也没有影响,不过应该需要设置 spring.mail.properties.mail.smtp.ssl.enable=false 以此来关闭 ssl

MailSSLSocketFactory

package com.rymcu.forest.util;

import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;

public class MailSSLSocketFactory extends SSLSocketFactory {
    private SSLSocketFactory factory;

    public MailSSLSocketFactory() {
        try {
            SSLContext sslcontext = SSLContext.getInstance("TLS");
            sslcontext.init(null, new TrustManager[]{new MailTrustManager()}, null);
            factory = sslcontext.getSocketFactory();
        } catch (Exception ex) {
            // ignore
        }
    }

    public static SocketFactory getDefault() {
        return new MailSSLSocketFactory();
    }

    @Override
    public Socket createSocket() throws IOException {
        return factory.createSocket();
    }

    @Override
    public Socket createSocket(Socket socket, String s, int i, boolean flag) throws IOException {
        return factory.createSocket(socket, s, i, flag);
    }

    @Override
    public Socket createSocket(InetAddress inaddr, int i, InetAddress inaddr1, int j) throws IOException {
        return factory.createSocket(inaddr, i, inaddr1, j);
    }

    @Override
    public Socket createSocket(InetAddress inaddr, int i) throws IOException {
        return factory.createSocket(inaddr, i);
    }

    @Override
    public Socket createSocket(String s, int i, InetAddress inaddr, int j) throws IOException {
        return factory.createSocket(s, i, inaddr, j);
    }

    @Override
    public Socket createSocket(String s, int i) throws IOException {
        return factory.createSocket(s, i);
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return factory.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return factory.getSupportedCipherSuites();
    }
}

MailTrustManager

package com.rymcu.forest.util;

import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;

public class MailTrustManager implements X509TrustManager {
    public void checkClientTrusted(X509Certificate[] cert, String authType) {
        // everything is trusted
    }

    public void checkServerTrusted(X509Certificate[] cert, String authType) {
        // everything is trusted
    }

    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }
}

发送邮件

JavaMailService 定义邮件相关接口

public interface JavaMailService {
    /**
     * 发送验证码邮件
     *
     * @param email 收件人邮箱
     * @return 执行结果 0:失败1:成功
     * @throws MessagingException
     */
    Integer sendEmailCode(String email) throws MessagingException;

    /**
     * 发送找回密码邮件
     *
     * @param email 收件人邮箱
     * @return 执行结果 0:失败1:成功
     * @throws MessagingException
     */
    Integer sendForgetPasswordEmail(String email) throws MessagingException;

    /**
     * 发送下消息通知
     *
     * @param notification
     * @return
     * @throws MessagingException
     */
    Integer sendNotification(NotificationDTO notification) throws MessagingException;
}

JavaMailServiceImpl 邮件接口实现

@Service
public class JavaMailServiceImpl implements JavaMailService {

    /**
     * Java邮件发送器
     */
    @Resource
    private JavaMailSenderImpl mailSender;
    @Resource
    private RedisService redisService;
    @Resource
    private UserService userService;
    /**
     * thymeleaf模板引擎
     */
    @Resource
    private TemplateEngine templateEngine;

    @Value("${spring.mail.host}")
    private String SERVER_HOST;
    @Value("${spring.mail.port}")
    private String SERVER_PORT;
    @Value("${spring.mail.username}")
    private String USERNAME;
    @Value("${spring.mail.password}")
    private String PASSWORD;
    @Value("${resource.domain}")
    private String BASE_URL;

    @Override
    public Integer sendEmailCode(String email) throws MessagingException {
        return sendCode(email, 0);
    }

    @Override
    public Integer sendForgetPasswordEmail(String email) throws MessagingException {
        return sendCode(email, 1);
    }

    @Override
    public Integer sendNotification(NotificationDTO notification) throws MessagingException {
        User user = userService.findById(String.valueOf(notification.getIdUser()));
        if (NotificationConstant.Comment.equals(notification.getDataType())) {
            String url = notification.getDataUrl();
            String thymeleafTemplatePath = "mail/commentNotification";
            Map<String, Object> thymeleafTemplateVariable = new HashMap<String, Object>(4);
            thymeleafTemplateVariable.put("user", notification.getAuthor().getUserNickname());
            thymeleafTemplateVariable.put("articleTitle", notification.getDataTitle());
            thymeleafTemplateVariable.put("content", notification.getDataSummary());
            thymeleafTemplateVariable.put("url", url);

            sendTemplateEmail(USERNAME,
                    new String[]{user.getEmail()},
                    new String[]{},
                    "【RYMCU】 消息通知",
                    thymeleafTemplatePath,
                    thymeleafTemplateVariable);
            return 1;
        }
        return 0;
    }

    private Integer sendCode(String to, Integer type) throws MessagingException {
        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
        simpleMailMessage.setFrom(USERNAME);
        simpleMailMessage.setTo(to);
        if (type == 0) {
            Integer code = Utils.genCode();
            redisService.set(to, code, 5 * 60);
            simpleMailMessage.setSubject("新用户注册邮箱验证");
            simpleMailMessage.setText("【RYMCU】您的校验码是 " + code + ",有效时间 5 分钟,请不要泄露验证码给其他人。如非本人操作,请忽略!");
            mailSender.send(simpleMailMessage);
            return 1;
        } else if (type == 1) {
            String code = Utils.entryptPassword(to);
            String url = BASE_URL + "/forget-password?code=" + code;
            redisService.set(code, to, 15 * 60);

            String thymeleafTemplatePath = "mail/forgetPasswordTemplate";
            Map<String, Object> thymeleafTemplateVariable = new HashMap<String, Object>(1);
            thymeleafTemplateVariable.put("url", url);

            sendTemplateEmail(USERNAME,
                    new String[]{to},
                    new String[]{},
                    "【RYMCU】 找回密码",
                    thymeleafTemplatePath,
                    thymeleafTemplateVariable);
            return 1;
        }
        return 0;
    }

    /**
     * 发送thymeleaf模板邮件
     *
     * @param deliver                   发送人邮箱名 如: returntmp@163.com
     * @param receivers                 收件人,可多个收件人 如:11111@qq.com,2222@163.com
     * @param carbonCopys               抄送人,可多个抄送人 如:33333@sohu.com
     * @param subject                   邮件主题 如:您收到一封高大上的邮件,请查收。
     * @param thymeleafTemplatePath     邮件模板 如:mail\mailTemplate.html。
     * @param thymeleafTemplateVariable 邮件模板变量集
     */
    public void sendTemplateEmail(String deliver, String[] receivers, String[] carbonCopys, String subject, String thymeleafTemplatePath,
                                  Map<String, Object> thymeleafTemplateVariable) throws MessagingException {
        String text = null;
        if (thymeleafTemplateVariable != null && thymeleafTemplateVariable.size() > 0) {
            Context context = new Context();
            thymeleafTemplateVariable.forEach((key, value) -> context.setVariable(key, value));
            text = templateEngine.process(thymeleafTemplatePath, context);
        }
        sendMimeMail(deliver, receivers, carbonCopys, subject, text, true, null);
    }

    /**
     * 发送的邮件(支持带附件/html类型的邮件)
     *
     * @param deliver             发送人邮箱名 如: returntmp@163.com
     * @param receivers           收件人,可多个收件人 如:11111@qq.com,2222@163.com
     * @param carbonCopys         抄送人,可多个抄送人 如:3333@sohu.com
     * @param subject             邮件主题 如:您收到一封高大上的邮件,请查收。
     * @param text                邮件内容 如:测试邮件逗你玩的。 <html><body><img
     *                            src=\"cid:attchmentFileName\"></body></html>
     * @param attachmentFilePaths 附件文件路径 如:
     *                            需要注意的是addInline函数中资源名称attchmentFileName需要与正文中cid:attchmentFileName对应起来
     * @throws Exception 邮件发送过程中的异常信息
     */
    private void sendMimeMail(String deliver, String[] receivers, String[] carbonCopys, String subject, String text,
                              boolean isHtml, String[] attachmentFilePaths) throws MessagingException {
        StopWatch stopWatch = new StopWatch();

        stopWatch.start();
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        helper.setFrom(deliver);
        helper.setTo(receivers);
        helper.setCc(carbonCopys);
        helper.setSubject(subject);
        helper.setText(text, isHtml);
        // 添加邮件附件
        if (attachmentFilePaths != null && attachmentFilePaths.length > 0) {
            for (String attachmentFilePath : attachmentFilePaths) {
                File file = new File(attachmentFilePath);
                if (file.exists()) {
                    String attachmentFile = attachmentFilePath
                            .substring(attachmentFilePath.lastIndexOf(File.separator));
                    long size = file.length();
                    if (size > 1024 * 1024) {
                        String msg = String.format("邮件单个附件大小不允许超过1MB,[%s]文件大小[%s]。", attachmentFilePath,
                                file.length());
                        throw new RuntimeException(msg);
                    } else {
                        FileSystemResource fileSystemResource = new FileSystemResource(file);
                        helper.addInline(attachmentFile, fileSystemResource);
                    }
                }
            }
        }
        mailSender.send(mimeMessage);
        stopWatch.stop();

    }

}

JavaMailServiceTest 单元测试

/**
 * javaMail测试
 */
class JavaMailServiceTest extends BaseServiceTest {

    private static final String REALITY_EMAIL = "xxxx@qq.com";
    @Autowired
    private JavaMailService javaMailService;

    @Test
    void sendEmailCode() throws MessagingException {
        assertEquals(1, javaMailService.sendEmailCode(REALITY_EMAIL));
    }

    @Test
    void sendForgetPasswordEmail() throws MessagingException {
        assertEquals(1, javaMailService.sendForgetPasswordEmail(REALITY_EMAIL));
    }

    @Test
    void sendNotification() throws MessagingException {
        assertEquals(0, javaMailService.sendNotification(new NotificationDTO()));

    }
}

最后我们测试上面发送验证码函数:sendEmailCode (上面的 xxxx@qq.com 替换为自己的收件人邮箱 )

最终发送成功

image.png

参考链接

  • SpringBoot集成163邮件发送详细配置,从163邮箱开始配置
  • SpringBoot 之发送邮件 | SPRING TUTORIAL (dunwu.github.io)
  • SpringBoot系列(十四)集成邮件发送服务及邮件发送的几种方式
  • 阿里云服务器不能发邮件禁用25端口的三种解决方法
  • Spring Boot配置ssl发送Email_mail.smtp.ssl.enable
  • Spring Boot 发送邮件全解析

本文由博客一文多发平台 OpenWrite 发布!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/482032.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[MAUI]集成高德地图组件至.NET MAUI Blazor项目

文章目录 前期准备&#xff1a;注册高德开发者并创建 key登录控制台创建 key获取 key 和密钥 创建项目创建JS API Loader配置权限创建定义创建模型创建地图组件创建交互逻辑 项目地址 地图组件在手机App中常用地理相关业务&#xff0c;如查看线下门店&#xff0c;设置导航&…

Linux进程的管理和进程的状态

进程的基本概念&#xff1a; 程序的一个执行实例 &#xff0c;正在执行的程序等等 ——— 课本概念 担当分配系统资源的实体&#xff0c;例如cpu时间&#xff0c;内存 -----内核的观点 一、进程的管理 processbar 存储在磁盘中的可执行文件 可执行文件在启动/运行的同时&…

2024阿里云域名优惠口令大全_你要的【优惠口令】都在这!

2024年阿里云域名优惠口令&#xff0c;com域名续费优惠口令“com批量注册更享优惠”&#xff0c;cn域名续费优惠口令“cn注册多个价格更优”&#xff0c;cn域名注册优惠口令“互联网上的中国标识”&#xff0c;阿里云优惠口令是域名专属的优惠码&#xff0c;可用于域名注册、续…

解决修改数据后,前端页面不显示问题

如图&#xff0c;修改数据后&#xff0c;在前端页面不显示的问题&#xff0c;可能是因为缓存问题 解决方案 以为Edge浏览器为例 打开设置左边栏点击隐私&#xff0c;搜索和服务选择清除 Internet Explorer 的浏览数据点击删除&#xff0c;重新启动前端界面即可。

2024年阿里云服务器优惠价格表_一张表清晰明了

2024年腾讯云服务器优惠价格表&#xff0c;一张表整理阿里云服务器最新报价&#xff0c;阿里云服务器网整理云服务器ECS和轻量应用服务器详细CPU内存、公网带宽和系统盘详细配置报价单&#xff0c;大家也可以直接移步到阿里云CLUB中心查看 aliyun.club 当前最新的云服务器优惠券…

python--循环(作业)

作业一&#xff1a; 判断一个数是否为质数&#xff08;素数&#xff09; flag True prime int(input("请输入一个整数&#xff1a;")) for num in range(2, prime):if prime % num 0:flag Falsebreak if flag:print("它是质数") else:print("它…

01.绝对路径和相对路径(Linux基本概念)

基础认知&#xff1a; 电脑的目录结构是一颗多叉树。不管是Linux还是windows&#xff0c;目录结构都是一样的。所以我们在查找某个目录或者文件的时候&#xff0c;本质就是在多叉树结点的查找。多叉树示例图如下&#xff1a; ​​​​​​​ ​​​​​​​ ​​…

指尖论文怎么用 #经验分享#学习方法

指尖论文是一款优秀的论文写作、查重降重工具&#xff0c;被广泛认可为高效、可靠、方便的辅助工具。那么&#xff0c;如何正确地使用指尖论文呢&#xff1f; 首先&#xff0c;用户需要注册一个指尖论文的账号&#xff0c;并登录到平台上。注册过程非常简单&#xff0c;只需要输…

总结: HQL语句

总结: HQL语句 Part1 数据库的操作Part2 数据表的操作1. 创建普通表2. 内外部表3. 内外部表转换 Part1 数据库的操作 查看数据库: show databases; 创建数据库: create database if not exists 数据库名 使用数据库: use 数据库名; 查看数据库详细信息: desc database 数据库名…

java数据结构与算法基础-----字符串------正则表达式的练习案例---持续补充中

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 正则表达式基础&#xff1a;https://blog.csdn.net/grd_java/article/det…

springboot297毕业生实习与就业管理系统的设计与实现

毕业生实习与就业管理系统 摘 要 使用旧方法对毕业生实习与就业管理系统的信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在毕业生实习与就业管理系统的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数…

升级 HarmonyOS 4 版本,腕上智慧更进一步

HUAWEI WATCH GT 3 系列升级 HarmonyOS 4 新版本后&#xff0c;手表体验更进一步&#xff0c;快来看看有哪些变化吧~

Linux Sftp和Scp

scp 和 sftp 区别 1 scp 能将远程文件复制到另一个远程机&#xff0c;sftp 不能。sftp为 SSH的其中一部分&#xff0c;是一种传输档案至 Blogger 伺服器的安全方式 2.scp 没有删除/创建远程目录功能&#xff0c;sftp 有。scp 在需要进行验证时会要求你输入密码或口令。 3. FT…

docker 的八大技术架构(图解)

docker 的八大技术架构 单机架构 概念&#xff1a; 应用服务和数据库服务公用一台服务器 出现背景&#xff1a; 出现在互联网早期&#xff0c;访问量比较小&#xff0c;单机足以满足需求 架构优缺点&#xff1a; 优点&#xff1a;部署简单&#xff0c;成本低 缺点&#xff1…

ChatGPT不再只是聊天工具!揭秘10种令你大开眼界的新玩法!

随着生活步伐的加速&#xff0c;大家都在寻求效率和便利。在此背景下&#xff0c;人工智能成了许多人的备受关注和热用的技术。如今&#xff0c;自然语言处理模型ChatGPT逐渐在助力众多人士提升工作效率和生活品质。还不知道如何使用ChatGPT的话&#xff0c;不妨读下这篇介绍。…

阅读笔记(ICIP2023)Rectangular-Output Image Stitching

“矩形输出”图像拼接 Zhou, H., Zhu, Y., Lv, X., Liu, Q., & Zhang, S. (2023, October). Rectangular-Output Image Stitching. In 2023 IEEE International Conference on Image Processing (ICIP) (pp. 2800-2804). IEEE. 0. 摘要 图像拼接的目的是将两幅视场重叠的…

代码随想录day28(2)二叉树:删除二叉搜索树中的节点(leetcode450)

题目要求&#xff1a;给定一个二叉搜索树的根节点 root 和一个值 key&#xff0c;删除二叉搜索树中的 key 对应的节点&#xff0c;并保证二叉搜索树的性质不变。返回二叉搜索树&#xff08;有可能被更新&#xff09;的根节点的引用。 思路&#xff1a;首先要删除二叉搜索树中的…

CycleGAN-Turbo:CycleGAN结合扩散模型,一步图像到图像转换方法

CycleGAN-Turbo&#xff1a;CycleGAN结合扩散模型&#xff0c;一步图像到图像转换方法 提出背景子解法1&#xff1a;直接对条件信息进行编码子解法2&#xff1a;整合三个独立模块子解法3&#xff1a;保留高频细节 相关工作例子&#xff1a;日转夜图像转换现有方法我们的方法&am…

SRS-110VDC-4Z-10A静态中间继电器 35MM卡轨安装 JOSEF约瑟

系列型号&#xff1a; SRS-24VDC-2Z-8A静态中间继电器&#xff1b;SRS-24VDC-2Z-10A静态中间继电器&#xff1b; SRS-24VDC-2Z-16A静态中间继电器&#xff1b;SRS-24VAC-2Z-8A静态中间继电器&#xff1b; SRS-24VAC-2Z-10A 静态中间继电器&#xff1b;SRS-24VAC-2Z-16A静态中…

echarts睡眠分期

效果 echarts核心配置 option {tooltip: {trigger: axis // 触发方式为axis&#xff0c;表示数据项图形触发&#xff0c;此时坐标轴上的刻度也会显示提示信息。},xAxis: {show: false,type: category,data: [2024-02-02 12:00:01,2024-02-02 12:00:02,2024-02-02 12:00:03,20…