feat(email): 重构邮件服务,支持异步和多收件人发送

- 改造邮件发送逻辑,支持to、cc、bcc多种接收方式
- 增加邮件配置和Session缓存,提高性能和稳定性
- 添加异步发送邮件接口,提升调用效率
- 实现邮件配置缓存刷新功能,方便配置更新应用
- 优化异常处理,统一邮件发送异常抛出
- 调整编码格式和日志记录,提升邮件兼容性与调试能力
- 删除旧版邮件配置解析方式,简化逻辑实现
This commit is contained in:
曹鹏飞 2026-05-06 10:50:27 +08:00
parent 6db9c11ec6
commit 482bd5ac13
3 changed files with 174 additions and 227 deletions

View File

@ -1,206 +0,0 @@
package com.nflg.wms.admin.service;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.nflg.wms.common.pojo.dto.EmailConfigDTO;
import com.nflg.wms.common.util.VUtil;
import com.nflg.wms.repository.entity.ParamConfig;
import com.nflg.wms.repository.service.IParamConfigService;
import jakarta.annotation.Resource;
import jakarta.mail.*;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
public class EmailService {
@Resource
private IParamConfigService paramConfigService;
/**
* 邮件配置缓存
*/
private volatile EmailConfigDTO cachedEmailConfig;
/**
* Session缓存
*/
private final ConcurrentHashMap<String, Session> sessionCache = new ConcurrentHashMap<>();
/**
* 发送邮件单个收件人
*
* @param to 收件人邮箱
* @param subject 邮件主题
* @param content 邮件内容HTML格式
*/
public void sendEmail(String to, String subject, String content) {
sendEmail(to, null, null, subject, content);
}
/**
* 发送邮件支持多收件人抄送密送
*
* @param to 收件人邮箱多个用逗号分隔
* @param cc 抄送人邮箱多个用逗号分隔可为null
* @param bcc 密送人邮箱多个用逗号分隔可为null
* @param subject 邮件主题
* @param content 邮件内容HTML格式
*/
public void sendEmail(String to, String cc, String bcc, String subject, String content) {
try {
log.info("准备发送邮件, to:{}, cc:{}, bcc:{}, subject:{}", to, cc, bcc, subject);
EmailConfigDTO emailConfig = getEmailConfig();
Session session = getSession(emailConfig);
MimeMessage message = createMessage(session, emailConfig, to, cc, bcc, subject, content);
Transport.send(message);
log.info("发送邮件完成, to:{}, subject:{}", to, subject);
} catch (Exception e) {
log.error("发送邮件失败, to:{}, subject:{}, error:{}", to, subject, e.getMessage(), e);
throw new RuntimeException("发送邮件失败: " + e.getMessage(), e);
}
}
/**
* 异步发送邮件
*
* @param to 收件人邮箱
* @param subject 邮件主题
* @param content 邮件内容HTML格式
*/
@Async
public void sendEmailAsync(String to, String subject, String content) {
sendEmail(to, subject, content);
}
/**
* 异步发送邮件支持多收件人抄送密送
*
* @param to 收件人邮箱多个用逗号分隔
* @param cc 抄送人邮箱多个用逗号分隔可为null
* @param bcc 密送人邮箱多个用逗号分隔可为null
* @param subject 邮件主题
* @param content 邮件内容HTML格式
*/
@Async
public void sendEmailAsync(String to, String cc, String bcc, String subject, String content) {
sendEmail(to, cc, bcc, subject, content);
}
/**
* 刷新邮件配置缓存
*/
public void refreshConfig() {
cachedEmailConfig = null;
sessionCache.clear();
log.info("邮件配置缓存已刷新");
}
/**
* 获取邮件配置带缓存
*/
private EmailConfigDTO getEmailConfig() {
if (cachedEmailConfig != null) {
return cachedEmailConfig;
}
synchronized (this) {
if (cachedEmailConfig != null) {
return cachedEmailConfig;
}
List<ParamConfig> configs = paramConfigService.lambdaQuery()
.eq(ParamConfig::getGroupName, "EmailSet")
.list();
VUtil.trueThrowBusinessError(CollectionUtil.isEmpty(configs)).throwMessage("未配置邮件参数");
EmailConfigDTO emailConfig = new EmailConfigDTO();
ParamConfig cfg = configs.stream().filter(c -> StrUtil.equals("host", c.getCode())).findFirst().orElse(null);
VUtil.trueThrowBusinessError(Objects.isNull(cfg)).throwMessage("主机名未配置");
emailConfig.setHost(cfg.getValue());
cfg = configs.stream().filter(c -> StrUtil.equals("port", c.getCode())).findFirst().orElse(null);
VUtil.trueThrowBusinessError(Objects.isNull(cfg)).throwMessage("端口号未配置");
emailConfig.setPort(Integer.valueOf(cfg.getValue()));
cfg = configs.stream().filter(c -> StrUtil.equals("username", c.getCode())).findFirst().orElse(null);
VUtil.trueThrowBusinessError(Objects.isNull(cfg)).throwMessage("用户名未配置");
emailConfig.setUsername(cfg.getValue());
cfg = configs.stream().filter(c -> StrUtil.equals("password", c.getCode())).findFirst().orElse(null);
VUtil.trueThrowBusinessError(Objects.isNull(cfg)).throwMessage("密码未配置");
emailConfig.setPassword(cfg.getValue());
cachedEmailConfig = emailConfig;
return emailConfig;
}
}
/**
* 获取Session带缓存
*/
private Session getSession(EmailConfigDTO emailConfig) {
String cacheKey = emailConfig.getHost() + ":" + emailConfig.getPort();
return sessionCache.computeIfAbsent(cacheKey, k -> createSession(emailConfig));
}
/**
* 创建Session
*/
private Session createSession(EmailConfigDTO emailConfig) {
Properties properties = new Properties();
properties.put("mail.smtp.host", emailConfig.getHost());
properties.put("mail.smtp.port", emailConfig.getPort().toString());
properties.put("mail.smtp.auth", "true");
properties.put("mail.smtp.ssl.enable", "true");
properties.put("mail.smtp.connectiontimeout", "5000");
properties.put("mail.smtp.timeout", "5000");
properties.put("mail.smtp.writetimeout", "5000");
return Session.getInstance(properties, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(emailConfig.getUsername(), emailConfig.getPassword());
}
});
}
/**
* 创建邮件消息
*/
private MimeMessage createMessage(Session session, EmailConfigDTO emailConfig,
String to, String cc, String bcc,
String subject, String content) throws MessagingException, UnsupportedEncodingException {
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(emailConfig.getUsername()));
// 设置收件人
if (StrUtil.isNotBlank(to)) {
InternetAddress[] toAddresses = InternetAddress.parse(to);
message.setRecipients(Message.RecipientType.TO, toAddresses);
}
// 设置抄送
if (StrUtil.isNotBlank(cc)) {
InternetAddress[] ccAddresses = InternetAddress.parse(cc);
message.setRecipients(Message.RecipientType.CC, ccAddresses);
}
// 设置密送
if (StrUtil.isNotBlank(bcc)) {
InternetAddress[] bccAddresses = InternetAddress.parse(bcc);
message.setRecipients(Message.RecipientType.BCC, bccAddresses);
}
message.setSubject(subject, "UTF-8");
message.setContent(content, "text/html; charset=UTF-8");
return message;
}
}

View File

@ -24,6 +24,7 @@ import com.nflg.wms.common.pojo.vo.UserVO;
import com.nflg.wms.common.util.*;
import com.nflg.wms.repository.entity.*;
import com.nflg.wms.repository.service.*;
import com.nflg.wms.starter.service.EmailService;
import com.nflg.wms.starter.service.FileUploadService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;

View File

@ -1,54 +1,206 @@
package com.nflg.wms.starter.service;
import cn.hutool.json.JSONUtil;
import com.nflg.wms.common.pojo.dto.EmailConfig;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.nflg.wms.common.pojo.dto.EmailConfigDTO;
import com.nflg.wms.common.util.VUtil;
import com.nflg.wms.repository.entity.ParamConfig;
import com.nflg.wms.repository.service.IParamConfigService;
import jakarta.annotation.Resource;
import jakarta.mail.*;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
@Component
@Slf4j
@Component
public class EmailService {
@Resource
private IParamConfigService paramConfigService;
public void sendEmail(String to,String subject, String content) throws MessagingException {
log.info("准备发送邮件,to:{},subject:{},content:{}",to,subject,content);
ParamConfig config = paramConfigService.lambdaQuery().eq(ParamConfig::getCode, "EmailSet").one();
VUtil.trueThrowBusinessError(Objects.isNull(config)).throwMessage("未配置邮件参数");
EmailConfig emailConfig= JSONUtil.toBean(config.getValue(), EmailConfig.class);
VUtil.trueThrowBusinessError(Objects.isNull(emailConfig)).throwMessage("邮件参数解析失败");
/**
* 邮件配置缓存
*/
private volatile EmailConfigDTO cachedEmailConfig;
/**
* Session缓存
*/
private final ConcurrentHashMap<String, Session> sessionCache = new ConcurrentHashMap<>();
/**
* 发送邮件单个收件人
*
* @param to 收件人邮箱
* @param subject 邮件主题
* @param content 邮件内容HTML格式
*/
public void sendEmail(String to, String subject, String content) {
sendEmail(to, null, null, subject, content);
}
/**
* 发送邮件支持多收件人抄送密送
*
* @param to 收件人邮箱多个用逗号分隔
* @param cc 抄送人邮箱多个用逗号分隔可为null
* @param bcc 密送人邮箱多个用逗号分隔可为null
* @param subject 邮件主题
* @param content 邮件内容HTML格式
*/
public void sendEmail(String to, String cc, String bcc, String subject, String content) {
try {
log.info("准备发送邮件, to:{}, cc:{}, bcc:{}, subject:{}", to, cc, bcc, subject);
EmailConfigDTO emailConfig = getEmailConfig();
Session session = getSession(emailConfig);
MimeMessage message = createMessage(session, emailConfig, to, cc, bcc, subject, content);
Transport.send(message);
log.info("发送邮件完成, to:{}, subject:{}", to, subject);
} catch (Exception e) {
log.error("发送邮件失败, to:{}, subject:{}, error:{}", to, subject, e.getMessage(), e);
throw new RuntimeException("发送邮件失败: " + e.getMessage(), e);
}
}
/**
* 异步发送邮件
*
* @param to 收件人邮箱
* @param subject 邮件主题
* @param content 邮件内容HTML格式
*/
@Async
public void sendEmailAsync(String to, String subject, String content) {
sendEmail(to, subject, content);
}
/**
* 异步发送邮件支持多收件人抄送密送
*
* @param to 收件人邮箱多个用逗号分隔
* @param cc 抄送人邮箱多个用逗号分隔可为null
* @param bcc 密送人邮箱多个用逗号分隔可为null
* @param subject 邮件主题
* @param content 邮件内容HTML格式
*/
@Async
public void sendEmailAsync(String to, String cc, String bcc, String subject, String content) {
sendEmail(to, cc, bcc, subject, content);
}
/**
* 刷新邮件配置缓存
*/
public void refreshConfig() {
cachedEmailConfig = null;
sessionCache.clear();
log.info("邮件配置缓存已刷新");
}
/**
* 获取邮件配置带缓存
*/
private EmailConfigDTO getEmailConfig() {
if (cachedEmailConfig != null) {
return cachedEmailConfig;
}
synchronized (this) {
if (cachedEmailConfig != null) {
return cachedEmailConfig;
}
List<ParamConfig> configs = paramConfigService.lambdaQuery()
.eq(ParamConfig::getGroupName, "EmailSet")
.list();
VUtil.trueThrowBusinessError(CollectionUtil.isEmpty(configs)).throwMessage("未配置邮件参数");
EmailConfigDTO emailConfig = new EmailConfigDTO();
ParamConfig cfg = configs.stream().filter(c -> StrUtil.equals("host", c.getCode())).findFirst().orElse(null);
VUtil.trueThrowBusinessError(Objects.isNull(cfg)).throwMessage("主机名未配置");
emailConfig.setHost(cfg.getValue());
cfg = configs.stream().filter(c -> StrUtil.equals("port", c.getCode())).findFirst().orElse(null);
VUtil.trueThrowBusinessError(Objects.isNull(cfg)).throwMessage("端口号未配置");
emailConfig.setPort(Integer.valueOf(cfg.getValue()));
cfg = configs.stream().filter(c -> StrUtil.equals("username", c.getCode())).findFirst().orElse(null);
VUtil.trueThrowBusinessError(Objects.isNull(cfg)).throwMessage("用户名未配置");
emailConfig.setUsername(cfg.getValue());
cfg = configs.stream().filter(c -> StrUtil.equals("password", c.getCode())).findFirst().orElse(null);
VUtil.trueThrowBusinessError(Objects.isNull(cfg)).throwMessage("密码未配置");
emailConfig.setPassword(cfg.getValue());
cachedEmailConfig = emailConfig;
return emailConfig;
}
}
/**
* 获取Session带缓存
*/
private Session getSession(EmailConfigDTO emailConfig) {
String cacheKey = emailConfig.getHost() + ":" + emailConfig.getPort();
return sessionCache.computeIfAbsent(cacheKey, k -> createSession(emailConfig));
}
/**
* 创建Session
*/
private Session createSession(EmailConfigDTO emailConfig) {
Properties properties = new Properties();
properties.put("mail.smtp.host", emailConfig.getHost());
properties.put("mail.smtp.port", emailConfig.getPort().toString());
properties.put("mail.smtp.auth", "true");
properties.put("mail.smtp.ssl.enable", "true");
// 设置超时时间单位毫秒
properties.put("mail.smtp.connectiontimeout", "5000"); // 连接超时
properties.put("mail.smtp.timeout", "5000"); // 读取超时
properties.put("mail.smtp.writetimeout", "5000"); // 写入超时
Session session = Session.getInstance(properties, new Authenticator() {
properties.put("mail.smtp.connectiontimeout", "5000");
properties.put("mail.smtp.timeout", "5000");
properties.put("mail.smtp.writetimeout", "5000");
return Session.getInstance(properties, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(emailConfig.getUsername(), emailConfig.getPassword());
}
});
}
/**
* 创建邮件消息
*/
private MimeMessage createMessage(Session session, EmailConfigDTO emailConfig,
String to, String cc, String bcc,
String subject, String content) throws MessagingException, UnsupportedEncodingException {
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(emailConfig.getUsername()));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
message.setSubject(subject);
// 设置收件人
if (StrUtil.isNotBlank(to)) {
InternetAddress[] toAddresses = InternetAddress.parse(to);
message.setRecipients(Message.RecipientType.TO, toAddresses);
}
// 设置抄送
if (StrUtil.isNotBlank(cc)) {
InternetAddress[] ccAddresses = InternetAddress.parse(cc);
message.setRecipients(Message.RecipientType.CC, ccAddresses);
}
// 设置密送
if (StrUtil.isNotBlank(bcc)) {
InternetAddress[] bccAddresses = InternetAddress.parse(bcc);
message.setRecipients(Message.RecipientType.BCC, bccAddresses);
}
message.setSubject(subject, "UTF-8");
message.setContent(content, "text/html; charset=UTF-8");
Transport.send(message);
log.info("发送邮件完成,to:{},subject:{},content:{}",to,subject,content);
return message;
}
}