From 482bd5ac13547f6dce8e73cc73ee298e543222ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=B9=E9=B9=8F=E9=A3=9E?= Date: Wed, 6 May 2026 10:50:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(email):=20=E9=87=8D=E6=9E=84=E9=82=AE?= =?UTF-8?q?=E4=BB=B6=E6=9C=8D=E5=8A=A1=EF=BC=8C=E6=94=AF=E6=8C=81=E5=BC=82?= =?UTF-8?q?=E6=AD=A5=E5=92=8C=E5=A4=9A=E6=94=B6=E4=BB=B6=E4=BA=BA=E5=8F=91?= =?UTF-8?q?=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 改造邮件发送逻辑,支持to、cc、bcc多种接收方式 - 增加邮件配置和Session缓存,提高性能和稳定性 - 添加异步发送邮件接口,提升调用效率 - 实现邮件配置缓存刷新功能,方便配置更新应用 - 优化异常处理,统一邮件发送异常抛出 - 调整编码格式和日志记录,提升邮件兼容性与调试能力 - 删除旧版邮件配置解析方式,简化逻辑实现 --- .../nflg/wms/admin/service/EmailService.java | 206 ------------------ .../admin/service/UserControllerService.java | 1 + .../wms/starter/service/EmailService.java | 194 +++++++++++++++-- 3 files changed, 174 insertions(+), 227 deletions(-) delete mode 100644 nflg-wms-admin/src/main/java/com/nflg/wms/admin/service/EmailService.java diff --git a/nflg-wms-admin/src/main/java/com/nflg/wms/admin/service/EmailService.java b/nflg-wms-admin/src/main/java/com/nflg/wms/admin/service/EmailService.java deleted file mode 100644 index 57d6c679..00000000 --- a/nflg-wms-admin/src/main/java/com/nflg/wms/admin/service/EmailService.java +++ /dev/null @@ -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 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 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; - } -} diff --git a/nflg-wms-admin/src/main/java/com/nflg/wms/admin/service/UserControllerService.java b/nflg-wms-admin/src/main/java/com/nflg/wms/admin/service/UserControllerService.java index 7f4707b7..0c80d0cb 100644 --- a/nflg-wms-admin/src/main/java/com/nflg/wms/admin/service/UserControllerService.java +++ b/nflg-wms-admin/src/main/java/com/nflg/wms/admin/service/UserControllerService.java @@ -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; diff --git a/nflg-wms-starter/src/main/java/com/nflg/wms/starter/service/EmailService.java b/nflg-wms-starter/src/main/java/com/nflg/wms/starter/service/EmailService.java index 0d689897..8c85da59 100644 --- a/nflg-wms-starter/src/main/java/com/nflg/wms/starter/service/EmailService.java +++ b/nflg-wms-starter/src/main/java/com/nflg/wms/starter/service/EmailService.java @@ -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 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 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; } }