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

发布于:2025-08-07 ⋅ 阅读:(18) ⋅ 点赞:(0)


前言

我们在使用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 {
    @Bean
    public 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;
        }
    }

    @Bean
    public 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{
	@Autowired
	private 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说的不一定是对的。有什么问题,欢迎大家评论区交流!


网站公告

今日签到

点亮在社区的每一天
去签到