完成基础邮件发送功能

This commit is contained in:
AlanScipio
2024-03-01 13:46:33 +08:00
parent f9f895c518
commit 1e35045f67
19 changed files with 815 additions and 70 deletions

View File

@@ -124,6 +124,12 @@
<version>${mybatis.dynamic.version}</version>
</dependency>
<!-- Java邮件标准接口 -->
<dependency>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,124 @@
package com.ruoyi.common.core.mail;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 邮件发送参数
*
* @since 2022/8/17
*/
@Data
@Accessors(chain = true)
public class MailMessage {
/**
* 邮件头信息(标题、收件人、抄送等)
*/
private MailMessageHead headInfo = new MailMessageHead();
/**
* 正文
*/
private String content;
/**
* 正文里的多媒体文件
*/
private Map<String, File> inLineMap;
/**
* 正文是否为html。true代表是
*/
private boolean isHtml = false;
/**
* 附件
*/
private final Map<String, File> attachments = new LinkedHashMap<>();
public void clearHeadInfo() {
headInfo.setFrom(null);
headInfo.setTo(null);
headInfo.setCc(null);
headInfo.setSubject(null);
}
public MailMessage setFrom(String fromAddress) {
headInfo.setFrom(fromAddress);
return this;
}
public MailMessage setTo(String toAddress) {
headInfo.setTo(toAddress);
return this;
}
public MailMessage setCc(String cc) {
headInfo.setCc(cc);
return this;
}
public MailMessage setSubject(String subject) {
headInfo.setSubject(subject);
return this;
}
public String getFrom() {
return headInfo.getFrom();
}
public String getTo() {
return headInfo.getTo();
}
public String getCc() {
return headInfo.getCc();
}
public String getSubject() {
return headInfo.getSubject();
}
public MailMessage addAttachment(String fileKey, File attachment) {
attachments.put(fileKey, attachment);
return this;
}
public MailMessage addAttachment(File attachment) {
attachments.put(attachment.getName(), attachment);
return this;
}
public MailMessage addAttachments(List<File> attachments) {
if (attachments != null) {
for (File attachment : attachments) {
this.attachments.put(attachment.getName(), attachment);
}
}
return this;
}
public void clearAttachments() {
attachments.clear();
}
public File getAttachment(String fileKey) {
return attachments.get(fileKey);
}
public List<File> getAttachmentsList() {
List<File> list = new ArrayList<>();
for (Map.Entry<String, File> entry : attachments.entrySet()) {
list.add(entry.getValue());
}
return list;
}
}

View File

@@ -0,0 +1,35 @@
package com.ruoyi.common.core.mail;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 邮件头信息
*
* @since 2022/8/17
*/
@Data
@Accessors(chain = true)
public class MailMessageHead {
/**
* 发件人
*/
private String from;
/**
* 收件人
*/
private String to;
/**
* 抄送
*/
private String cc;
/**
* 邮件标题(Subject)
*/
private String subject;
}

View File

@@ -0,0 +1,43 @@
package com.ruoyi.common.core.mail;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* 邮箱账号信息(发邮件用)
* @since 2021/6/25
*/
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class MailSendAccount {
/**
* SMTP服务器地址
*/
private String host;
/**
* SMTP服务器端口号
*/
private Integer port;
/**
* 用户名
*/
private String username;
/**
* 密码/授权码
*/
private String password;
/**
* 是否用SSL加密
*/
private boolean sslFlag = false;
}

View File

@@ -0,0 +1,51 @@
package com.ruoyi.common.core.mail;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 邮件发送结果
*
* @since 2022/8/18
*/
@Data
@Accessors(chain = true)
public class MailSendResult {
/**
* 是否成功true代表成功
*/
private boolean success;
/**
* 错误信息
*/
private String errMsg;
/**
* 如果有抛出异常,则此为抛出的异常对象
*/
private Throwable errObj;
public MailSendResult setFailure(Throwable errObj) {
this.success = false;
if (errObj != null) {
this.errMsg = errObj.toString();
this.errObj = errObj;
}
return this;
}
public static MailSendResult success() {
return new MailSendResult().setSuccess(true);
}
public static MailSendResult failure(String errMsg) {
return new MailSendResult().setSuccess(false).setErrMsg(errMsg);
}
public static MailSendResult failure(Throwable errObj) {
return new MailSendResult().setFailure(errObj);
}
}

View File

@@ -0,0 +1,87 @@
package com.ruoyi.common.core.mail;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import java.io.File;
import java.util.List;
/**
* 邮件发送工具
* date 2021/8/18
*/
public interface MailSender {
String ADDRESS_SPLIT = ";";
//======================================= ↓↓↓ 快速构建 ↓↓↓ =======================================
/**
* 构建发送工具
*
* @param senderAccount 发送账号
* @param enableDebug 是否开启debug信息输出
*/
static MailSender build(MailSendAccount senderAccount, int timeout, boolean enableDebug) {
return new SpringMailSender(senderAccount, timeout, enableDebug);
}
static MailSender build(MailSendAccount senderAccount, boolean enableDebug) {
return new SpringMailSender(senderAccount, 20000, enableDebug);
}
static MailSender build(MailSendAccount senderAccount) {
return new SpringMailSender(senderAccount);
}
//======================================= ↓↓↓ 主要API ↓↓↓ =======================================
/**
* 发送MIME邮件
*
* @param form 发送参数
* @return 发送结果
*/
MailSendResult sendMail(MailMessage form);
default MailSendResult sendMail(String from, String to, String cc, String subject, String text, List<File> attachments) {
MailMessage message = new MailMessage()
.setFrom(from)
.setTo(to)
.setCc(cc)
.setSubject(subject)
.setContent(text)
.addAttachments(attachments);
return sendMail(message);
}
default MailSendResult sendMail(String to, String cc, String subject, String text, List<File> attachments) {
return sendMail(null, to, cc, subject, text, attachments);
}
default MailSendResult sendMail(String to, String subject, String text, List<File> attachments) {
return sendMail(null, to, null, subject, text, attachments);
}
default MailSendResult sendMail(String to, String cc, String subject, String text) {
return sendMail(null, to, cc, subject, text, null);
}
default MailSendResult sendMail(String to, String subject, String text) {
return sendMail(null, to, null, subject, text, null);
}
//======================================= ↓↓↓ 其他API ↓↓↓ =======================================
MailSendAccount getSenderAccount();
JavaMailSenderImpl getExecutor();
void resetSenderAccount(MailSendAccount senderAccount);
default void resetSenderAccount(String host, int port, String username, String password) {
resetSenderAccount(new MailSendAccount(host, port, username, password, (password != null && !password.isEmpty())));
}
boolean available();
}

View File

@@ -0,0 +1,184 @@
package com.ruoyi.common.core.mail;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import java.io.File;
import java.util.Map;
import java.util.Properties;
/**
* 邮件发送器
*
* @since 2021/8/18
*/
public class SpringMailSender implements MailSender {
//发送执行者
private final JavaMailSenderImpl executor;
private MailSendAccount senderAccount;
public SpringMailSender(MailSendAccount senderAccount) {
this(senderAccount, 20000, false);
}
public SpringMailSender(MailSendAccount senderAccount, int timeout, boolean enableDebug) {
this.senderAccount = senderAccount;
//创建发送执行者
executor = buildExecutor(senderAccount, timeout, enableDebug);
}
/**
* 创建发送执行者
*
* @param account 发送账号
* @return 发送执行者
*/
private JavaMailSenderImpl buildExecutor(MailSendAccount account, int timeout, boolean enableDebug) {
//邮箱设定
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
Properties mailProperties = new Properties();
//邮件发送时输出debug信息
mailProperties.setProperty("mail.debug", enableDebug ? "true" : "false");
//发送服务器需要身份验证
mailProperties.setProperty("mail.smtp.auth", "true");
//发送邮件协议名称 这里使用的是smtp协议
mailProperties.setProperty("mail.transport.protocol", "smtp");
//默认开启starttls
mailProperties.setProperty("mail.smtp.starttls.enable", "true");
//超时设置
mailProperties.setProperty("mail.smtp.connectiontimeout", timeout + "");//与邮件服务器建立连接的超时
mailProperties.setProperty("mail.smtp.writetimeout", timeout + "");//邮件发送时间限制
if (account.isSslFlag()) {
//开启ssl
System.out.println("****** enable ssl for mail send ******");
mailProperties.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
mailProperties.setProperty("mail.smtp.socketFactory.port", account.getPort().toString());
mailProperties.setProperty("mail.smtp.ssl.enable", "true");
} else {
System.out.println("****** disable ssl for mail send ******");
javaMailSender.setPort(account.getPort());
}
javaMailSender.setJavaMailProperties(mailProperties);
javaMailSender.setHost(account.getHost());
javaMailSender.setUsername(account.getUsername());
javaMailSender.setPassword(account.getPassword());
javaMailSender.setDefaultEncoding("UTF-8");
System.setProperty("mail.mime.splitlongparameters", "false"); //注意不截断base64编码后的长附件名
return javaMailSender;
}
/**
* 创建邮件信息
*
* @param executor 发送执行者
* @param form 发送参数
* @return 邮件信息
* @throws MessagingException 邮件信息创建失败
*/
private MimeMessage createMimeMessage(JavaMailSenderImpl executor, MailMessage form) throws MessagingException {
MailMessageHead headInfo = form.getHeadInfo();
Map<String, File> inLineMap = form.getInLineMap();
Map<String, File> attachments = form.getAttachments();
MimeMessage mimeMessage = executor.createMimeMessage();
//创建发送MIME邮件的工具类
MimeMessageHelper messageHelper = getMimeMessageHelper(form, mimeMessage, headInfo);
//设置正文多媒体信息
if (inLineMap != null) {
for (Map.Entry<String, File> inLine : inLineMap.entrySet()) {
messageHelper.addInline(inLine.getKey(), inLine.getValue());
}
}
//添加附件
if (attachments != null) {
for (Map.Entry<String, File> entry : attachments.entrySet()) {
String fileName = entry.getKey();
File file = entry.getValue();
messageHelper.addAttachment(fileName, file);
}
}
return mimeMessage;
}
private MimeMessageHelper getMimeMessageHelper(MailMessage form, MimeMessage mimeMessage, MailMessageHead headInfo) throws MessagingException {
MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
//发件人
if (form.getFrom() == null || form.getFrom().isEmpty()) {
form.setFrom(senderAccount.getUsername());
} else {
form.setFrom(form.getFrom());
}
//收件人 这里的参数可以是多个收件人,用英文分号分割
messageHelper.setTo(headInfo.getTo().split(ADDRESS_SPLIT));
//抄送 这里的参数可以是多个抄送人,用英文分号分割
if (headInfo.getCc() != null && !headInfo.getCc().isEmpty()) {
messageHelper.setCc(headInfo.getCc().split(ADDRESS_SPLIT));
}
//邮件主题
messageHelper.setSubject(headInfo.getSubject());
//邮件正文
if (form.getContent() != null && !form.getContent().isEmpty()) {
messageHelper.setText(form.getContent(), form.isHtml());
}
return messageHelper;
}
/**
* 发送邮件
*
* @param form 发送参数
* @return 发送结果
*/
@Override
public MailSendResult sendMail(MailMessage form) {
try {
MimeMessage mimeMessage = createMimeMessage(executor, form);
executor.send(mimeMessage);
return MailSendResult.success();
} catch (Exception e) {
return MailSendResult.failure(e);
}
}
@Override
public MailSendAccount getSenderAccount() {
return senderAccount;
}
@Override
public JavaMailSenderImpl getExecutor() {
return executor;
}
@Override
public void resetSenderAccount(MailSendAccount senderAccount) {
checkAccount(senderAccount);
this.senderAccount = senderAccount;
}
private void checkAccount(MailSendAccount account) {
if (account == null) {
throw new IllegalArgumentException("account is null");
}
if (account.getHost() == null || account.getHost().isEmpty()) {
throw new IllegalArgumentException("host is blank");
}
if (account.getPort() == null) {
throw new IllegalArgumentException("port is null");
}
if (account.getPort() <= 0 || account.getPort() > 65535) {
throw new IllegalArgumentException("port is invalid (0-65535)");
}
}
@Override
public boolean available() {
return (senderAccount.getHost() != null && !senderAccount.getHost().isEmpty() ) && senderAccount.getPort() != null;
}
}

View File

@@ -38,7 +38,7 @@ public final class SpringUtils implements BeanFactoryPostProcessor {
* 获取类型为requiredType的对象
*/
public static <T> T getBean(Class<T> clz) throws BeansException {
T result = (T) beanFactory.getBean(clz);
T result = beanFactory.getBean(clz);
return result;
}

View File

@@ -1,7 +1,5 @@
package com.ruoyi.common.security.utils;
import java.util.Collection;
import java.util.List;
import com.alibaba.fastjson2.JSONArray;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.utils.SpringUtils;
@@ -9,35 +7,47 @@ import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.system.api.domain.SysDictData;
import java.util.Collection;
import java.util.List;
/**
* 字典工具类
*
*
* @author ruoyi
*/
public class DictUtils
{
public class DictUtils {
private DictUtils() {
throw new IllegalStateException("Utility class cannot be instantiated");
}
private static RedisService redisService;
/**
* 设置字典缓存
*
* @param key 参数键
* @param dictDatas 字典数据列表
*
* @param key 参数键
* @param dictDataList 字典数据列表
*/
public static void setDictCache(String key, List<SysDictData> dictDatas)
{
SpringUtils.getBean(RedisService.class).setCacheObject(getCacheKey(key), dictDatas);
public static void setDictCache(String key, List<SysDictData> dictDataList) {
if (redisService == null) {
redisService = SpringUtils.getBean(RedisService.class);
}
redisService.setCacheObject(getCacheKey(key), dictDataList);
}
/**
* 获取字典缓存
*
*
* @param key 参数键
* @return dictDatas 字典数据列表
* @return dictDataList 字典数据列表
*/
public static List<SysDictData> getDictCache(String key)
{
JSONArray arrayCache = SpringUtils.getBean(RedisService.class).getCacheObject(getCacheKey(key));
if (StringUtils.isNotNull(arrayCache))
{
public static List<SysDictData> getDictCache(String key) {
if (redisService == null) {
redisService = SpringUtils.getBean(RedisService.class);
}
JSONArray arrayCache = redisService.getCacheObject(getCacheKey(key));
if (StringUtils.isNotNull(arrayCache)) {
return arrayCache.toList(SysDictData.class);
}
return null;
@@ -45,31 +55,34 @@ public class DictUtils
/**
* 删除指定字典缓存
*
*
* @param key 字典键
*/
public static void removeDictCache(String key)
{
SpringUtils.getBean(RedisService.class).deleteObject(getCacheKey(key));
public static void removeDictCache(String key) {
if (redisService == null) {
redisService = SpringUtils.getBean(RedisService.class);
}
redisService.deleteObject(getCacheKey(key));
}
/**
* 清空字典缓存
*/
public static void clearDictCache()
{
Collection<String> keys = SpringUtils.getBean(RedisService.class).keys(CacheConstants.SYS_DICT_KEY + "*");
SpringUtils.getBean(RedisService.class).deleteObject(keys);
public static void clearDictCache() {
if (redisService == null) {
redisService = SpringUtils.getBean(RedisService.class);
}
Collection<String> keys = redisService.keys(CacheConstants.SYS_DICT_KEY + "*");
redisService.deleteObject(keys);
}
/**
* 设置cache key
*
*
* @param configKey 参数键
* @return 缓存键key
*/
public static String getCacheKey(String configKey)
{
public static String getCacheKey(String configKey) {
return CacheConstants.SYS_DICT_KEY + configKey;
}
}

View File

@@ -0,0 +1,81 @@
package com.ruoyi.common.security.utils;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.utils.SpringUtils;
import com.ruoyi.common.redis.service.RedisService;
import java.util.Collection;
/**
* 系统参数工具类 (sys_config)
*
* @author Alan Scipio
* created on 2024/3/1
*/
public class SysConfigUtils {
private SysConfigUtils() {
throw new IllegalStateException("Utility class cannot be instantiated");
}
private static RedisService redisService;
/**
* 设置参数缓存
*
* @param configKey 参数键
* @param configValue 参数值
*/
public static void setConfigCache(String configKey, String configValue) {
if (redisService == null) {
redisService = SpringUtils.getBean(RedisService.class);
}
redisService.setCacheObject(getCacheKey(configKey), configValue);
}
/**
* 获取参数缓存
*
* @param configKey 参数键
* @return configValue 参数值
*/
public static String getConfigCache(String configKey) {
if (redisService == null) {
redisService = SpringUtils.getBean(RedisService.class);
}
return redisService.getCacheObject(getCacheKey(configKey));
}
/**
* 删除指定参数缓存
*
* @param key 字典键
*/
public static void removeConfigCache(String key) {
if (redisService == null) {
redisService = SpringUtils.getBean(RedisService.class);
}
redisService.deleteObject(getCacheKey(key));
}
/**
* 清空所有参数缓存
*/
public static void clearConfigCaches() {
if (redisService == null) {
redisService = SpringUtils.getBean(RedisService.class);
}
Collection<String> keys = redisService.keys(CacheConstants.SYS_CONFIG_KEY + "*");
redisService.deleteObject(keys);
}
/**
* 设置cache key
*
* @param configKey 参数键
* @return 缓存键key
*/
public static String getCacheKey(String configKey) {
return CacheConstants.SYS_CONFIG_KEY + configKey;
}
}