当前位置: 首页 > news >正文

SpringBoot使用Hutool邮件工具MailUtil实现电子邮件发送功能(以网易邮箱为例)

文章目录

  • 前言
  • 一、开启网易邮箱客户端POP/SMTP/IMAP协议
  • 二、引入依赖包
  • 三、邮件工具类配置及使用
    • 1.邮件服务器配置文件内容
    • 2.邮件服务器配置文件位置
    • 3.邮件服务器配置类
    • 4.邮件服务器工具类
  • 四、邮件发送测试
  • 五、踩坑合集与反思
    • 1.遇到邮件正文文本格式问题
    • 2.遇到邮件正文文本累加重复问题
  • 六、应用服务器开通到smtp邮件服务器的网络策略
    • 1.在应用服务器解析域名smtp.163.com
    • 2.填写网络策略申请单
    • 3.备注说明
  • 总结


前言

我们在使用SpringBoot或SpringCloud框架开发系统时,经常会遇到因业务需求,需要给用户发送审批或提醒消息,这些消息又分为站内消息、短信消息、邮件消息等等,刚好最近遇到个邮件发送相关的缺陷,就顺带聊一聊java语言开发的系统如何申请一个网易邮箱用作系统邮箱给用户发送变更消息。


一、开启网易邮箱客户端POP/SMTP/IMAP协议

请参考以下官方帮助文档步骤操作开启客户端协议,获取授权码:
https://help.mail.163.com/faqDetail.do?code=d7a5dc8471cd0c0e8b4b8f4f8e49998b374173cfe9171305fa1ce630d7f67ac2a5feb28b66796d3b

二、引入依赖包

springBoot项目pom文件引入如下依赖:

 <!-- hutool -->
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.20</version>
</dependency><dependency><groupId>com.sun.mail</groupId><artifactId>javax.mail</artifactId><version>1.6.2</version>
</dependency>

三、邮件工具类配置及使用

1.邮件服务器配置文件内容

mail.setting文件的配置如下(示例):

# 邮件服务器的SMTP地址,可选,默认为smtp.<发件人邮箱后缀>
host = smtp.163.com
# 邮件服务器的SMTP端口,可选,默认25
port = 25
# 发件人(必须正确,否则发送失败)
from = test001@163.com
# 用户名,默认为发件人邮箱前缀
user = test001
# 密码(注意,某些邮箱需要为SMTP服务单独设置授权码,详情查看相关帮助)
pass = AMSSTWSEOHJRCFGK

注意:邮件服务器必须支持并打开SMTP协议,样例中提供的是我专门为测试邮件功能注册的smtp.163.com邮箱。

2.邮件服务器配置文件位置

截图如下(示例):
在这里插入图片描述

3.邮件服务器配置类

EmailConfig配置类如下:

package com.dg.dp.metadata.config;import cn.hutool.core.io.IORuntimeException;
import cn.hutool.extra.mail.Mail;
import cn.hutool.extra.mail.MailAccount;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.io.File;@Slf4j
@Configuration
@ConditionalOnProperty(prefix = "email", name = "enabled", havingValue = "true")
public class EmailConfig {@Beanpublic MailAccount mailAccount(){String mailSettingPath = System.getProperty("user.dir").concat(File.separator).concat("config").concat(File.separator).concat("mail.setting");File file = new File(mailSettingPath);if (file.exists()){log.info("loading mail.setting from {}......",mailSettingPath);return new MailAccount(mailSettingPath);}else {for (String mailSettingInnerPath : MailAccount.MAIL_SETTING_PATHS) {try {return new MailAccount(mailSettingInnerPath);} catch (IORuntimeException ignore) {//ignore}}return null;}}@Beanpublic Mail mail(MailAccount mailAccount) {Mail mail = new Mail(mailAccount.defaultIfEmpty());mail.setUseGlobalSession(true);mail.setHtml(false);return mail;}}

4.邮件服务器工具类

MailUtils工具类如下:

package com.dg.dp.metadata.utils;import cn.hutool.extra.mail.Mail;
import cn.hutool.extra.mail.MailAccount;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.lang.reflect.Field;
import java.util.Arrays;/*** 邮件工具类,依赖 hutool,配置在/src/main/resources/mail.setting*/
@Component
public class MailUtils {@Autowired(required = false)private Mail mail;@Autowired(required = false)private MailAccount mailAccount;public static final String SPLIT = ";";/*** 发送邮件,支持群发* @param tos 接收人邮件,以;分隔* @param subject 邮件标题* @param content 邮件内容*/public void sendMail(String tos, String subject, String content) {Mail mail = new Mail(mailAccount.defaultIfEmpty());mail.setUseGlobalSession(true);mail.setHtml(false);//手动清空Mail对象的状态,避免Mail对象的状态(如正文内容)会保留下来,导致内容累加。mail.setTos();mail.setContent(null);mail.setTitle(null);mail.setTos(Arrays.asList(tos.split(SPLIT)).toArray(new String[0]));mail.setContent(content);mail.setTitle(subject);mail.send();}
}

注:hutool邮箱工具类官方文档参考地址:https://plus.hutool.cn/docs/#/extra/%E9%82%AE%E4%BB%B6%E5%B7%A5%E5%85%B7-MailUtil

四、邮件发送测试

写个测试接口,收件邮箱写自己的QQ邮箱:

@RestController
@RequestMapping("/data/api")
@Slf4j
public class MetadataController{@Autowiredprivate MailUtils mailUtils;private static final String subject = "数据变更通知";private static final String content = "您好!\r\n" +"\u3000\u3000您被授权使用的数据已发生变更。为确保您业务的连续性和数据的准确性,请及时查阅变更内容。";@ApiOperation(value = "发送邮件")@PostMapping("/_test")public Result<?> togglePublish() {mailUtils.sendMail("123456@qq.com", subject, content);return R.success("发送成功");}
}

收到的邮箱内容如下:
在这里插入图片描述

五、踩坑合集与反思

1.遇到邮件正文文本格式问题

如出现邮件正文内容格式不正确,比如:未换行、首行只缩进一个字符等格式问题,很可能是邮件发送时为文本格式,多个空格会被默认当做一个或者忽略掉了,可以搜一下解决方案,可以将发送文本格式设置成html,我这里是采用全角空格的Unicode编码格式,使其在发送的时候能够被识别为两个空格,换行用:\r\n 。

2.遇到邮件正文文本累加重复问题

测试过程中出现了每发送一次,邮件正文内容就累加重复一次的问题,查找了好久,一直以为是在设置Content内容mail.setContent();时设置了多次导致的,后来排查发现原因是:

通过 Spring 的 @Bean 注入了一个配置好的 Mail 实例,那么每次调用 sendMail 方法时,应该确保不会因为单例模式导致的内容累加问题。问题的根本原因可能是 Mail 对象的状态没有在每次发送邮件时被正确重置。
问题分析:
单例模式问题:Mail 对象是单例的,每次调用 sendMail 方法时,Mail 对象的状态(如正文内容)会保留下来。如果在发送邮件时没有重置这些状态,就会导致内容累加。
Hutool 的 Mail 类行为:Hutool 的 Mail 类在发送邮件时会保留之前的设置,包括正文内容。因此,每次发送邮件时,需要手动清空或重置这些状态。

解决方案:
在每次发送邮件之前,手动清空 Mail 对象的状态。Hutool 的 Mail 类提供了 reset 方法,可以用来清空邮件内容和其他设置。由于我这里hutool的版本比较低,不支持reset 方法清空mail对象状态,最终采用了set null值的方式,每次调用发送邮件方法时手动置空邮件内容及设置,再次设置,成功解决了这个问题。

文本重复累加问题邮件截图:
在这里插入图片描述
测试过程中的邮件工具类MailUtils可以通过反射,可以访问 Mail 类的私有字段,从而获取其内部状态,通过查看设置前后邮件Mail 类的私有字段内容,辅助定位问题:

package com.dg.dp.metadata.utils;import cn.hutool.extra.mail.Mail;
import cn.hutool.extra.mail.MailAccount;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.lang.reflect.Field;
import java.util.Arrays;@Component
public class MailUtils {@Autowired(required = false)private Mail mail;@Autowired(required = false)private MailAccount mailAccount;public static final String SPLIT = ";";/*** 发送邮件,支持群发* @param tos 接收人邮件,以;分隔* @param subject 邮件标题* @param content 邮件内容*/public void sendMail(String tos, String subject, String content) {Mail mail = new Mail(mailAccount.defaultIfEmpty());mail.setUseGlobalSession(true);mail.setHtml(false);System.out.println("前正文: " + content);//获取内部状态字段值printMailStatus(mail);//手动清空Mail对象的状态,避免Mail对象的状态(如正文内容)会保留下来,导致内容累加。mail.setTos();mail.setContent(null);mail.setTitle(null);mail.setTos(Arrays.asList(tos.split(SPLIT)).toArray(new String[0]));mail.setContent(content);mail.setTitle(subject);System.out.println("后正文: " + content);//获取内部状态字段值printMailStatus(mail);mail.send();}//通过反射,可以访问 Mail 类的私有字段,从而获取其内部状态。private void printMailStatus(Mail mail) {try {// 获取 Mail 类的所有字段Field[] fields = Mail.class.getDeclaredFields();for (Field field : fields) {// 设置私有字段可访问field.setAccessible(true);// 获取字段值Object value = field.get(mail);System.out.println(field.getName() + ": " + value);}} catch (IllegalAccessException e) {e.printStackTrace();}}
}

日志输出如下:
在这里插入图片描述

六、应用服务器开通到smtp邮件服务器的网络策略

1.在应用服务器解析域名smtp.163.com

当服务被部署到应用服务器时,大概率会因为网络策略没开通的问题导致连接邮件服务器超时,导致服务不可以。这时需要先开通应用服务器开通到网易邮件服务器的网络策略。查资料发现网易邮件服务器的网址是在一些规定的网段内动态变化的,也就是说你的邮件服务请求可能被类似于ELB的负载均衡器转发到不同的机器处理,所以开通策略的时候不能开通单个ip,最好是开通对应网段。
我的方法是:1.解析域名获取当前处理的服务器ip,多试几次,把对应ip的当前网段32个地址都开通。
服务器解析域名:
在这里插入图片描述

在这里插入图片描述

2.填写网络策略申请单

在这里插入图片描述

3.备注说明

1)目标地址:117.135.207.0/27 表示放行从 117.135.207.0 到 117.135.207.31 共 32 个地址。
2)smtphz.qiye.163.com 为网易企业邮箱服务器地址,我这里同时开通了策略。
根据域名查询网易企业邮箱服务器地址,官方网站:
https://qiye.163.com/help/client-profile.html
3)开通后,可以用:telnet smtp.163.com 25 命令来测试是否开通相关策略。
4)默认使用:25、465、 587端口。


总结

要善用kimi、deepseek等工具为自己打开排查问题的思路,但是要注意甄别,AI说的不一定是对的。有什么问题,欢迎大家评论区交流!

http://www.dtcms.com/a/318159.html

相关文章:

  • 51c自动驾驶~合集13
  • 【自动驾驶】《Sparse4Dv3 Advancing End-to-End 3D Detection and Tracking》论文阅读笔记
  • ATS系统推荐:2025年HR选型指南
  • JDK17新特性全解析
  • Numpy科学计算与数据分析:Numpy入门之数组操作与科学计算基础
  • Numpy科学计算与数据分析专题
  • webrtc弱网-OveruseFrameDetector源码分析与算法原理
  • 实现EtherNet/IP网络与Modbus TCP网络之间数据互通
  • 数据爬虫工具【八爪鱼】循环爬取内嵌链接流程
  • webpack
  • PHP官方及第三方下载地址全指南(2025最新版)
  • C++ 运算符重载:避免隐式类型转换的艺术
  • 小杰python高级(one day)——线性代数
  • 後端開發技術教學(二) 條件指令、循環結構、定義函數
  • Linux 学习 之 killer 问题
  • 企业后端系统常用数据源类型有哪些?
  • 8.pcl 点云特征
  • 服务器巡检项目
  • 大模型显存占用分析:以Qwen2.5-7B-Instruct为例,深度剖析推理、LoRA与全量微调
  • 友思特方案 | 如何提高3D成像设备的部署和设计优势
  • Python应用指南:获取风闻评论数据并解读其背后的情感倾向(二)
  • Linux环境下部署SSM聚合项目
  • 微信小程序初次运行项目失败
  • 引入消息队列带来的主要问题
  • 家政小程序系统开发:打造一站式家政服务平台
  • CSS Flexbox 的一个“坑”
  • 【动态规划 | 01背包】动态规划经典:01背包问题详解
  • 解析 div 禁止换行与滚动条组合-CSS运用
  • 模电知识点总结
  • 30ssh远程连接与远程执行命令