增加JakartaMailUtil,支持新包名的mail

This commit is contained in:
Looly 2024-07-16 16:54:36 +08:00
parent dd659efb52
commit cfc83563c1
7 changed files with 1149 additions and 0 deletions

View File

@ -7,6 +7,7 @@
### 🐣新特性
* 【core 】 Converter转换规则变更空对象、空值转为Bean时创建默认对象而非nullissue#3649@Github
* 【core 】 UrlQuery增加remove方法
* 【extra 】 增加JakartaMailUtil支持新包名的mail
### 🐞Bug修复
* 【core 】 修复因RFC3986理解有误导致的UrlPath处理冒号转义问题issue#IAAE88@Gitee

View File

@ -142,6 +142,19 @@
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
<version>2.1.2</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.angus</groupId>
<artifactId>jakarta.mail</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
<!-- SSH安全连接所使用的类库 -->
<dependency>

View File

@ -0,0 +1,107 @@
package cn.hutool.extra.mail;
import cn.hutool.core.util.ArrayUtil;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeUtility;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 邮件内部工具类
* @author looly
* @since 3.2.3
*/
public class JakartaInternalMailUtil {
/**
* 将多个字符串邮件地址转为{@link InternetAddress}列表<br>
* 单个字符串地址可以是多个地址合并的字符串
*
* @param addrStrs 地址数组
* @param charset 编码主要用于中文用户名的编码
* @return 地址数组
* @since 4.0.3
*/
public static InternetAddress[] parseAddressFromStrs(String[] addrStrs, Charset charset) {
final List<InternetAddress> resultList = new ArrayList<>(addrStrs.length);
InternetAddress[] addrs;
for (String addrStr : addrStrs) {
addrs = parseAddress(addrStr, charset);
if (ArrayUtil.isNotEmpty(addrs)) {
Collections.addAll(resultList, addrs);
}
}
return resultList.toArray(new InternetAddress[0]);
}
/**
* 解析第一个地址
*
* @param address 地址字符串
* @param charset 编码{@code null}表示使用系统属性定义的编码或系统编码
* @return 地址列表
*/
public static InternetAddress parseFirstAddress(String address, Charset charset) {
final InternetAddress[] internetAddresses = parseAddress(address, charset);
if (ArrayUtil.isEmpty(internetAddresses)) {
try {
return new InternetAddress(address);
} catch (AddressException e) {
throw new MailException(e);
}
}
return internetAddresses[0];
}
/**
* 将一个地址字符串解析为多个地址<br>
* 地址间使用" "","";"分隔
*
* @param address 地址字符串
* @param charset 编码{@code null}表示使用系统属性定义的编码或系统编码
* @return 地址列表
*/
public static InternetAddress[] parseAddress(String address, Charset charset) {
InternetAddress[] addresses;
try {
addresses = InternetAddress.parse(address);
} catch (AddressException e) {
throw new MailException(e);
}
//编码用户名
if (ArrayUtil.isNotEmpty(addresses)) {
final String charsetStr = null == charset ? null : charset.name();
for (InternetAddress internetAddress : addresses) {
try {
internetAddress.setPersonal(internetAddress.getPersonal(), charsetStr);
} catch (UnsupportedEncodingException e) {
throw new MailException(e);
}
}
}
return addresses;
}
/**
* 编码中文字符<br>
* 编码失败返回原字符串
*
* @param text 被编码的文本
* @param charset 编码
* @return 编码后的结果
*/
public static String encodeText(String text, Charset charset) {
try {
return MimeUtility.encodeText(text, charset.name(), null);
} catch (UnsupportedEncodingException e) {
// ignore
}
return text;
}
}

View File

@ -0,0 +1,485 @@
package cn.hutool.extra.mail;
import cn.hutool.core.builder.Builder;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.activation.DataHandler;
import jakarta.activation.DataSource;
import jakarta.activation.FileDataSource;
import jakarta.activation.FileTypeMap;
import jakarta.mail.*;
import jakarta.mail.internet.MimeBodyPart;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeMultipart;
import jakarta.mail.internet.MimeUtility;
import jakarta.mail.util.ByteArrayDataSource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.util.Date;
/**
* 邮件发送客户端
*
* @author looly
* @since 3.2.0
*/
public class JakartaMail implements Builder<MimeMessage> {
private static final long serialVersionUID = 1L;
/**
* 邮箱帐户信息以及一些客户端配置信息
*/
private final MailAccount mailAccount;
/**
* 收件人列表
*/
private String[] tos;
/**
* 抄送人列表carbon copy
*/
private String[] ccs;
/**
* 密送人列表blind carbon copy
*/
private String[] bccs;
/**
* 回复地址(reply-to)
*/
private String[] reply;
/**
* 标题
*/
private String title;
/**
* 内容
*/
private String content;
/**
* 是否为HTML
*/
private boolean isHtml;
/**
* 正文附件和图片的混合部分
*/
private final Multipart multipart = new MimeMultipart();
/**
* 是否使用全局会话默认为false
*/
private boolean useGlobalSession = false;
/**
* debug输出位置可以自定义debug日志
*/
private PrintStream debugOutput;
/**
* 创建邮件客户端
*
* @param mailAccount 邮件帐号
* @return Mail
*/
public static JakartaMail create(MailAccount mailAccount) {
return new JakartaMail(mailAccount);
}
/**
* 创建邮件客户端使用全局邮件帐户
*
* @return Mail
*/
public static JakartaMail create() {
return new JakartaMail();
}
// --------------------------------------------------------------- Constructor start
/**
* 构造使用全局邮件帐户
*/
public JakartaMail() {
this(GlobalMailAccount.INSTANCE.getAccount());
}
/**
* 构造
*
* @param mailAccount 邮件帐户如果为null使用默认配置文件的全局邮件配置
*/
public JakartaMail(MailAccount mailAccount) {
mailAccount = (null != mailAccount) ? mailAccount : GlobalMailAccount.INSTANCE.getAccount();
this.mailAccount = mailAccount.defaultIfEmpty();
}
// --------------------------------------------------------------- Constructor end
// --------------------------------------------------------------- Getters and Setters start
/**
* 设置收件人
*
* @param tos 收件人列表
* @return this
* @see #setTos(String...)
*/
public JakartaMail to(String... tos) {
return setTos(tos);
}
/**
* 设置多个收件人
*
* @param tos 收件人列表
* @return this
*/
public JakartaMail setTos(String... tos) {
this.tos = tos;
return this;
}
/**
* 设置多个抄送人carbon copy
*
* @param ccs 抄送人列表
* @return this
* @since 4.0.3
*/
public JakartaMail setCcs(String... ccs) {
this.ccs = ccs;
return this;
}
/**
* 设置多个密送人blind carbon copy
*
* @param bccs 密送人列表
* @return this
* @since 4.0.3
*/
public JakartaMail setBccs(String... bccs) {
this.bccs = bccs;
return this;
}
/**
* 设置多个回复地址(reply-to)
*
* @param reply 回复地址(reply-to)列表
* @return this
* @since 4.6.0
*/
public JakartaMail setReply(String... reply) {
this.reply = reply;
return this;
}
/**
* 设置标题
*
* @param title 标题
* @return this
*/
public JakartaMail setTitle(String title) {
this.title = title;
return this;
}
/**
* 设置正文<br>
* 正文可以是普通文本也可以是HTML默认普通文本可以通过调用{@link #setHtml(boolean)} 设置是否为HTML
*
* @param content 正文
* @return this
*/
public JakartaMail setContent(String content) {
this.content = content;
return this;
}
/**
* 设置是否是HTML
*
* @param isHtml 是否为HTML
* @return this
*/
public JakartaMail setHtml(boolean isHtml) {
this.isHtml = isHtml;
return this;
}
/**
* 设置正文
*
* @param content 正文内容
* @param isHtml 是否为HTML
* @return this
*/
public JakartaMail setContent(String content, boolean isHtml) {
setContent(content);
return setHtml(isHtml);
}
/**
* 设置文件类型附件文件可以是图片文件此时自动设置cid正文中引用图片默认cid为文件名
*
* @param files 附件文件列表
* @return this
*/
public JakartaMail setFiles(File... files) {
if (ArrayUtil.isEmpty(files)) {
return this;
}
final DataSource[] attachments = new DataSource[files.length];
for (int i = 0; i < files.length; i++) {
attachments[i] = new FileDataSource(files[i]);
}
return setAttachments(attachments);
}
/**
* 增加附件或图片附件使用{@link DataSource} 形式表示可以使用{@link FileDataSource}包装文件表示文件附件
*
* @param attachments 附件列表
* @return this
* @since 4.0.9
*/
public JakartaMail setAttachments(DataSource... attachments) {
if (ArrayUtil.isNotEmpty(attachments)) {
final Charset charset = this.mailAccount.getCharset();
MimeBodyPart bodyPart;
String nameEncoded;
try {
for (DataSource attachment : attachments) {
bodyPart = new MimeBodyPart();
bodyPart.setDataHandler(new DataHandler(attachment));
nameEncoded = attachment.getName();
if (this.mailAccount.isEncodefilename()) {
nameEncoded = InternalMailUtil.encodeText(nameEncoded, charset);
}
// 普通附件文件名
bodyPart.setFileName(nameEncoded);
if (StrUtil.startWith(attachment.getContentType(), "image/")) {
// 图片附件用于正文中引用图片
bodyPart.setContentID(nameEncoded);
}
this.multipart.addBodyPart(bodyPart);
}
} catch (MessagingException e) {
throw new MailException(e);
}
}
return this;
}
/**
* 增加图片图片的键对应到邮件模板中的占位字符串图片类型默认为"image/jpeg"
*
* @param cid 图片与占位符占位符格式为cid:${cid}
* @param imageStream 图片文件
* @return this
* @since 4.6.3
*/
public JakartaMail addImage(String cid, InputStream imageStream) {
return addImage(cid, imageStream, null);
}
/**
* 增加图片图片的键对应到邮件模板中的占位字符串
*
* @param cid 图片与占位符占位符格式为cid:${cid}
* @param imageStream 图片流不关闭
* @param contentType 图片类型null赋值默认的"image/jpeg"
* @return this
* @since 4.6.3
*/
public JakartaMail addImage(String cid, InputStream imageStream, String contentType) {
ByteArrayDataSource imgSource;
try {
imgSource = new ByteArrayDataSource(imageStream, ObjectUtil.defaultIfNull(contentType, "image/jpeg"));
} catch (IOException e) {
throw new IORuntimeException(e);
}
imgSource.setName(cid);
return setAttachments(imgSource);
}
/**
* 增加图片图片的键对应到邮件模板中的占位字符串
*
* @param cid 图片与占位符占位符格式为cid:${cid}
* @param imageFile 图片文件
* @return this
* @since 4.6.3
*/
public JakartaMail addImage(String cid, File imageFile) {
InputStream in = null;
try {
in = FileUtil.getInputStream(imageFile);
return addImage(cid, in, FileTypeMap.getDefaultFileTypeMap().getContentType(imageFile));
} finally {
IoUtil.close(in);
}
}
/**
* 设置字符集编码
*
* @param charset 字符集编码
* @return this
* @see MailAccount#setCharset(Charset)
*/
public JakartaMail setCharset(Charset charset) {
this.mailAccount.setCharset(charset);
return this;
}
/**
* 设置是否使用全局会话默认为true
*
* @param isUseGlobalSession 是否使用全局会话默认为true
* @return this
* @since 4.0.2
*/
public JakartaMail setUseGlobalSession(boolean isUseGlobalSession) {
this.useGlobalSession = isUseGlobalSession;
return this;
}
/**
* 设置debug输出位置可以自定义debug日志
*
* @param debugOutput debug输出位置
* @return this
* @since 5.5.6
*/
public JakartaMail setDebugOutput(PrintStream debugOutput) {
this.debugOutput = debugOutput;
return this;
}
// --------------------------------------------------------------- Getters and Setters end
@Override
public MimeMessage build() {
try {
return buildMsg();
} catch (MessagingException e) {
throw new MailException(e);
}
}
/**
* 发送
*
* @return message-id
* @throws MailException 邮件发送异常
*/
public String send() throws MailException {
try {
return doSend();
} catch (MessagingException e) {
if (e instanceof SendFailedException) {
// 当地址无效时显示更加详细的无效地址信息
final Address[] invalidAddresses = ((SendFailedException) e).getInvalidAddresses();
final String msg = StrUtil.format("Invalid Addresses: {}", ArrayUtil.toString(invalidAddresses));
throw new MailException(msg, e);
}
throw new MailException(e);
}
}
// --------------------------------------------------------------- Private method start
/**
* 执行发送
*
* @return message-id
* @throws MessagingException 发送异常
*/
private String doSend() throws MessagingException {
final MimeMessage mimeMessage = buildMsg();
Transport.send(mimeMessage);
return mimeMessage.getMessageID();
}
/**
* 构建消息
*
* @return {@link MimeMessage}消息
* @throws MessagingException 消息异常
*/
private MimeMessage buildMsg() throws MessagingException {
final Charset charset = this.mailAccount.getCharset();
final MimeMessage msg = new MimeMessage(getSession());
// 发件人
final String from = this.mailAccount.getFrom();
if (StrUtil.isEmpty(from)) {
// 用户未提供发送方则从Session中自动获取
msg.setFrom();
} else {
msg.setFrom(JakartaInternalMailUtil.parseFirstAddress(from, charset));
}
// 标题
msg.setSubject(this.title, (null == charset) ? null : charset.name());
// 发送时间
msg.setSentDate(new Date());
// 内容和附件
msg.setContent(buildContent(charset));
// 收件人
msg.setRecipients(MimeMessage.RecipientType.TO, JakartaInternalMailUtil.parseAddressFromStrs(this.tos, charset));
// 抄送人
if (ArrayUtil.isNotEmpty(this.ccs)) {
msg.setRecipients(MimeMessage.RecipientType.CC, JakartaInternalMailUtil.parseAddressFromStrs(this.ccs, charset));
}
// 密送人
if (ArrayUtil.isNotEmpty(this.bccs)) {
msg.setRecipients(MimeMessage.RecipientType.BCC, JakartaInternalMailUtil.parseAddressFromStrs(this.bccs, charset));
}
// 回复地址(reply-to)
if (ArrayUtil.isNotEmpty(this.reply)) {
msg.setReplyTo(JakartaInternalMailUtil.parseAddressFromStrs(this.reply, charset));
}
return msg;
}
/**
* 构建邮件信息主体
*
* @param charset 编码{@code null}则使用{@link MimeUtility#getDefaultJavaCharset()}
* @return 邮件信息主体
* @throws MessagingException 消息异常
*/
private Multipart buildContent(Charset charset) throws MessagingException {
final String charsetStr = null != charset ? charset.name() : MimeUtility.getDefaultJavaCharset();
// 正文
final MimeBodyPart body = new MimeBodyPart();
body.setContent(content, StrUtil.format("text/{}; charset={}", isHtml ? "html" : "plain", charsetStr));
this.multipart.addBodyPart(body);
return this.multipart;
}
/**
* 获取默认邮件会话<br>
* 如果为全局单例的会话则全局只允许一个邮件帐号否则每次发送邮件会新建一个新的会话
*
* @return 邮件会话 {@link Session}
*/
private Session getSession() {
final Session session = JakartaMailUtil.getSession(this.mailAccount, this.useGlobalSession);
if (null != this.debugOutput) {
session.setDebugOut(debugOutput);
}
return session;
}
// --------------------------------------------------------------- Private method end
}

View File

@ -0,0 +1,441 @@
package cn.hutool.extra.mail;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.mail.Authenticator;
import jakarta.mail.Session;
import java.io.File;
import java.io.InputStream;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* 邮件工具类基于jakarta.mail封装
*
* @author looly
* @since 5.8.30
*/
public class JakartaMailUtil {
/**
* 使用配置文件中设置的账户发送文本邮件发送给单个或多个收件人<br>
* 多个收件人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendText(String to, String subject, String content, File... files) {
return send(to, subject, content, false, files);
}
/**
* 使用配置文件中设置的账户发送HTML邮件发送给单个或多个收件人<br>
* 多个收件人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendHtml(String to, String subject, String content, File... files) {
return send(to, subject, content, true, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送单个或多个收件人<br>
* 多个收件人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
*/
public static String send(String to, String subject, String content, boolean isHtml, File... files) {
return send(splitAddress(to), subject, content, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送单个或多个收件人<br>
* 多个收件人抄送人密送人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人可以使用逗号,分隔也可以通过分号;分隔
* @param cc 抄送人可以使用逗号,分隔也可以通过分号;分隔
* @param bcc 密送人可以使用逗号,分隔也可以通过分号;分隔
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(String to, String cc, String bcc, String subject, String content, boolean isHtml, File... files) {
return send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送文本邮件发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param files 附件列表
* @return message-id
*/
public static String sendText(Collection<String> tos, String subject, String content, File... files) {
return send(tos, subject, content, false, files);
}
/**
* 使用配置文件中设置的账户发送HTML邮件发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendHtml(Collection<String> tos, String subject, String content, File... files) {
return send(tos, subject, content, true, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
*/
public static String send(Collection<String> tos, String subject, String content, boolean isHtml, File... files) {
return send(tos, null, null, subject, content, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送给多人
*
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, boolean isHtml, File... files) {
return send(GlobalMailAccount.INSTANCE.getAccount(), true, tos, ccs, bccs, subject, content, null, isHtml, files);
}
// ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount
/**
* 发送邮件给多人
*
* @param mailAccount 邮件认证对象
* @param to 收件人多个收件人逗号或者分号隔开
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String send(MailAccount mailAccount, String to, String subject, String content, boolean isHtml, File... files) {
return send(mailAccount, splitAddress(to), subject, content, isHtml, files);
}
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
*/
public static String send(MailAccount mailAccount, Collection<String> tos, String subject, String content, boolean isHtml, File... files) {
return send(mailAccount, tos, null, null, subject, content, isHtml, files);
}
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(MailAccount mailAccount, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, boolean isHtml, File... files) {
return send(mailAccount, false, tos, ccs, bccs, subject, content, null, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送HTML邮件发送给单个或多个收件人<br>
* 多个收件人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendHtml(String to, String subject, String content, Map<String, InputStream> imageMap, File... files) {
return send(to, subject, content, imageMap, true, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送单个或多个收件人<br>
* 多个收件人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
*/
public static String send(String to, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(splitAddress(to), subject, content, imageMap, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送单个或多个收件人<br>
* 多个收件人抄送人密送人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人可以使用逗号,分隔也可以通过分号;分隔
* @param cc 抄送人可以使用逗号,分隔也可以通过分号;分隔
* @param bcc 密送人可以使用逗号,分隔也可以通过分号;分隔
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(String to, String cc, String bcc, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, imageMap, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送HTML邮件发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendHtml(Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, File... files) {
return send(tos, subject, content, imageMap, true, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
*/
public static String send(Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(tos, null, null, subject, content, imageMap, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送给多人
*
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(GlobalMailAccount.INSTANCE.getAccount(), true, tos, ccs, bccs, subject, content, imageMap, isHtml, files);
}
// ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount
/**
* 发送邮件给多人
*
* @param mailAccount 邮件认证对象
* @param to 收件人多个收件人逗号或者分号隔开
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String send(MailAccount mailAccount, String to, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(mailAccount, splitAddress(to), subject, content, imageMap, isHtml, files);
}
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 4.6.3
*/
public static String send(MailAccount mailAccount, Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(mailAccount, tos, null, null, subject, content, imageMap, isHtml, files);
}
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 4.6.3
*/
public static String send(MailAccount mailAccount, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, Map<String, InputStream> imageMap,
boolean isHtml, File... files) {
return send(mailAccount, false, tos, ccs, bccs, subject, content, imageMap, isHtml, files);
}
/**
* 根据配置文件获取邮件客户端会话
*
* @param mailAccount 邮件账户配置
* @param isSingleton 是否单例全局共享会话
* @return {@link Session}
* @since 5.5.7
*/
public static Session getSession(MailAccount mailAccount, boolean isSingleton) {
Authenticator authenticator = null;
if (mailAccount.isAuth()) {
authenticator = new JakartaUserPassAuthenticator(mailAccount.getUser(), mailAccount.getPass());
}
return isSingleton ? Session.getDefaultInstance(mailAccount.getSmtpProps(), authenticator) //
: Session.getInstance(mailAccount.getSmtpProps(), authenticator);
}
// ------------------------------------------------------------------------------------------------------------------------ Private method start
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param useGlobalSession 是否全局共享Session
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:${cid}
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 4.6.3
*/
private static String send(MailAccount mailAccount, boolean useGlobalSession, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content,
Map<String, InputStream> imageMap, boolean isHtml, File... files) {
final Mail mail = Mail.create(mailAccount).setUseGlobalSession(useGlobalSession);
// 可选抄送人
if (CollUtil.isNotEmpty(ccs)) {
mail.setCcs(ccs.toArray(new String[0]));
}
// 可选密送人
if (CollUtil.isNotEmpty(bccs)) {
mail.setBccs(bccs.toArray(new String[0]));
}
mail.setTos(tos.toArray(new String[0]));
mail.setTitle(subject);
mail.setContent(content);
mail.setHtml(isHtml);
mail.setFiles(files);
// 图片
if (MapUtil.isNotEmpty(imageMap)) {
for (Entry<String, InputStream> entry : imageMap.entrySet()) {
mail.addImage(entry.getKey(), entry.getValue());
// 关闭流
IoUtil.close(entry.getValue());
}
}
return mail.send();
}
/**
* 将多个联系人转为列表分隔符为逗号或者分号
*
* @param addresses 多个联系人如果为空返回null
* @return 联系人列表
*/
private static List<String> splitAddress(String addresses) {
if (StrUtil.isBlank(addresses)) {
return null;
}
List<String> result;
if (StrUtil.contains(addresses, CharUtil.COMMA)) {
result = StrUtil.splitTrim(addresses, CharUtil.COMMA);
} else if (StrUtil.contains(addresses, ';')) {
result = StrUtil.splitTrim(addresses, ';');
} else {
result = CollUtil.newArrayList(addresses);
}
return result;
}
// ------------------------------------------------------------------------------------------------------------------------ Private method end
}

View File

@ -0,0 +1,33 @@
package cn.hutool.extra.mail;
import jakarta.mail.Authenticator;
import jakarta.mail.PasswordAuthentication;
/**
* jakarta用户名密码验证器
*
* @author looly
* @since 5.8.30
*/
public class JakartaUserPassAuthenticator extends Authenticator {
private final String user;
private final String pass;
/**
* 构造
*
* @param user 用户名
* @param pass 密码
*/
public JakartaUserPassAuthenticator(String user, String pass) {
this.user = user;
this.pass = pass;
}
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(this.user, this.pass);
}
}

View File

@ -0,0 +1,69 @@
package cn.hutool.extra.mail;
import cn.hutool.core.io.FileUtil;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* 邮件发送测试
* @author looly
*
*/
public class JakartaMailTest {
@Test
@Ignore
public void sendWithFileTest() {
JakartaMailUtil.send("hutool@foxmail.com", "测试", "<h1>邮件来自Hutool测试</h1>", true, FileUtil.file("d:/测试附件文本.txt"));
}
@Test
@Ignore
public void sendWithLongNameFileTest() {
//附件名长度大于60时的测试
JakartaMailUtil.send("hutool@foxmail.com", "测试", "<h1>邮件来自Hutool测试</h1>", true, FileUtil.file("d:/6-LongLong一阶段平台建设周报2018.3.12-3.16.xlsx"));
}
@Test
@Ignore
public void sendWithImageTest() {
Map<String, InputStream> map = new HashMap<>();
map.put("testImage", FileUtil.getInputStream("f:/test/me.png"));
JakartaMailUtil.sendHtml("hutool@foxmail.com", "测试", "<h1>邮件来自Hutool测试</h1><img src=\"cid:testImage\" />", map);
}
@Test
@Ignore
public void sendHtmlTest() {
JakartaMailUtil.send("hutool@foxmail.com", "测试", "<h1>邮件来自Hutool测试</h1>", true);
}
@Test
@Ignore
public void sendByAccountTest() {
MailAccount account = new MailAccount();
account.setHost("smtp.yeah.net");
account.setPort(465);
account.setSslEnable(true);
account.setFrom("hutool@yeah.net");
account.setUser("hutool");
account.setPass("q1w2e3");
JakartaMailUtil.send(account, "hutool@foxmail.com", "测试", "<h1>邮件来自Hutool测试</h1>", true);
}
@Test
public void mailAccountTest() {
MailAccount account = new MailAccount();
account.setFrom("hutool@yeah.net");
account.setDebug(true);
account.defaultIfEmpty();
Properties props = account.getSmtpProps();
Assert.assertEquals("true", props.getProperty("mail.debug"));
}
}