简单邮件发送功能完成

This commit is contained in:
AlanScipio
2024-03-04 16:44:58 +08:00
parent 932b4da710
commit 24f1f4c3e7
15 changed files with 529 additions and 264 deletions

View File

@@ -4,11 +4,14 @@ import com.ruoyi.common.core.mail.MailSendAccount;
import com.ruoyi.common.core.mail.MailSender;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.security.utils.SysConfigUtils;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.File;
/**
* 邮件配置
*
@@ -28,6 +31,12 @@ public class MailConfig {
@Value("${spring.mail.password:#{null}}")
private String password;
@Getter
private File attachmentsSavedDir;
private MailSender mailSender;
@Bean
public MailSender mailSender() {
MailSendAccount account = new MailSendAccount();
@@ -38,11 +47,27 @@ public class MailConfig {
account.setUsername(username);
account.setPassword(password);
getFromCache(account);
mailSender = MailSender.build(account, true);
log.info("Mail configuration has been initialized. smtpHost: [{}], smtpPort: [{}], username: [{}]", account.getHost(), account.getPort(), account.getUsername());
return mailSender;
}
/**
* 从缓存中获取邮件配置
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
private void getFromCache(MailSendAccount account) {
if (account == null) {
throw new IllegalArgumentException("MailSendAccount is null");
}
// 加载数据库中的邮件配置会缓存到redis中并覆盖前面的配置
String hostCache = SysConfigUtils.getConfigCache("sys.mail.smtpHost");
String portCache = SysConfigUtils.getConfigCache("sys.mail.smtpPort");
String usernameCache = SysConfigUtils.getConfigCache("sys.mail.username");
String passwordCache = SysConfigUtils.getConfigCache("sys.mail.password");
String attachmentsSavedPath = SysConfigUtils.getConfigCache("sys.mail.attachmentsSavedPath");
if (StringUtils.isNotBlank(hostCache) && StringUtils.isNotBlank(portCache)) {
account.setHost(hostCache);
account.setPort(Integer.parseInt(portCache));
@@ -52,13 +77,27 @@ public class MailConfig {
if (StringUtils.isNotBlank(passwordCache)) {
account.setPassword(passwordCache);
}
if (StringUtils.isNotBlank(attachmentsSavedPath)) {
attachmentsSavedDir = new File(attachmentsSavedPath);
if (!attachmentsSavedDir.exists()) {
log.info("Mail attachments saved directory does not exist. Create a new one: [{}]", attachmentsSavedDir.getAbsolutePath());
attachmentsSavedDir.mkdirs();
}
}
} else {
log.warn("Mail configuration from database table 'sys_config' is empty. Use the configuration from application.yaml instead.");
}
account.setSslFlag(account.getPassword() != null && !account.getPassword().isEmpty());
log.info("Mail configuration has been initialized. smtpHost: [{}], smtpPort: [{}], username: [{}]", account.getHost(), account.getPort(), account.getUsername());
return MailSender.build(account, true);
}
/**
* 刷新缓存里的邮件配置
*/
public void refreshCache() {
MailSendAccount account = mailSender.getSenderAccount();
getFromCache(account);
mailSender.resetSenderAccount(account);
}
}

View File

@@ -1,6 +1,5 @@
package com.ruoyi.system.controller;
import com.ruoyi.common.core.mail.MailMessage;
import com.ruoyi.common.core.mail.MailSendResult;
import com.ruoyi.common.core.utils.poi.ExcelUtil;
import com.ruoyi.common.core.web.controller.BaseController;
@@ -10,6 +9,7 @@ import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.security.annotation.RequiresPermissions;
import com.ruoyi.system.domain.SysMailLog;
import com.ruoyi.system.domain.vo.MailVo;
import com.ruoyi.system.service.ISysMailLogService;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
@@ -61,21 +61,6 @@ public class SysMailLogController extends BaseController {
return success(sysMailLogService.selectSysMailLogByMailLogId(mailLogId));
}
/**
* 临时邮件发送
*/
@RequiresPermissions("system:mailLog:send")
@Log(title = "临时邮件发送", businessType = BusinessType.OTHER)
@PostMapping("/sendTemporality")
public AjaxResult sendTemporality(@RequestBody MailMessage message) {
MailSendResult result = sysMailLogService.sendTempMail(message);
if (result.isSuccess()) {
return success();
} else {
return error(result.getErrMsg());
}
}
/**
* 删除邮件发送日志
*/
@@ -85,4 +70,28 @@ public class SysMailLogController extends BaseController {
public AjaxResult remove(@PathVariable Long[] mailLogIds) {
return toAjax(sysMailLogService.deleteSysMailLogByMailLogIds(mailLogIds));
}
/**
* 临时邮件发送
*/
@RequiresPermissions("system:mailLog:send")
@Log(title = "临时邮件发送", businessType = BusinessType.OTHER)
@PostMapping("/sendTemporality")
public AjaxResult sendTemporality(MailVo mailVo) {
MailSendResult result = sysMailLogService.sendSimpleMail(mailVo);
if (result.isSuccess()) {
return success();
} else {
return error(result.getErrMsg());
}
}
/**
* 获取邮件发送者信息
*/
@RequiresPermissions("system:mailLog:query")
@GetMapping(value = "/getMailSenderInfo")
public AjaxResult getMailSenderInfo() {
return success(sysMailLogService.getMailSenderInfo());
}
}

View File

@@ -1,9 +1,10 @@
package com.ruoyi.system.domain;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.core.annotation.Excel;
import com.ruoyi.common.core.web.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.io.Serial;
@@ -13,10 +14,22 @@ import java.io.Serial;
* @author ryas
* created on 2024-03-01
*/
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Data
public class SysMailLog extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
//==================== ↓↓↓↓↓↓ 非表字段 ↓↓↓↓↓↓ ====================
/**
* 创建者用户名
*/
private String createByName;
//==================== ↓↓↓↓↓↓ 表字段 ↓↓↓↓↓↓ ====================
/**
* 日志主键
*/
@@ -70,93 +83,4 @@ public class SysMailLog extends BaseEntity {
@Excel(name = "消耗时间(ms)")
private Long costTime;
public void setMailLogId(Long mailLogId) {
this.mailLogId = mailLogId;
}
public Long getMailLogId() {
return mailLogId;
}
public void setStatus(Long status) {
this.status = status;
}
public Long getStatus() {
return status;
}
public void setBusinessType(String businessType) {
this.businessType = businessType;
}
public String getBusinessType() {
return businessType;
}
public void setFrom(String from) {
this.from = from;
}
public String getFrom() {
return from;
}
public void setTo(String to) {
this.to = to;
}
public String getTo() {
return to;
}
public void setCc(String cc) {
this.cc = cc;
}
public String getCc() {
return cc;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getSubject() {
return subject;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setCostTime(Long costTime) {
this.costTime = costTime;
}
public Long getCostTime() {
return costTime;
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("mailLogId", getMailLogId())
.append("status", getStatus())
.append("businessType", getBusinessType())
.append("from", getFrom())
.append("to", getTo())
.append("cc", getCc())
.append("subject", getSubject())
.append("msg", getMsg())
.append("createTime", getCreateTime())
.append("createBy", getCreateBy())
.append("costTime", getCostTime())
.append("remark", getRemark())
.toString();
}
}

View File

@@ -0,0 +1,48 @@
package com.ruoyi.system.domain.vo;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
/**
* @author Alan Scipio
* created on 2024/3/4
*/
@Data
public class MailVo {
/**
* 业务类型
*/
private String businessType;
/**
* 发送人
*/
private String from;
/**
* 接收人
*/
private String to;
/**
* 抄送人
*/
private String cc;
/**
* 邮件主题
*/
private String subject;
/**
* 邮件内容
*/
private String content;
/**
* 附件
*/
private MultipartFile[] attachments;
}

View File

@@ -1,8 +1,9 @@
package com.ruoyi.system.service;
import com.ruoyi.common.core.mail.MailMessage;
import com.ruoyi.common.core.mail.MailSendAccount;
import com.ruoyi.common.core.mail.MailSendResult;
import com.ruoyi.system.domain.SysMailLog;
import com.ruoyi.system.domain.vo.MailVo;
import java.util.List;
@@ -62,10 +63,15 @@ public interface ISysMailLogService {
int deleteSysMailLogByMailLogId(Long mailLogId);
/**
* 发送临时邮件
* 发送邮件 - 简易版
*
* @param message 邮件内容
* @param mailVo 邮件内容
* @return 结果
*/
MailSendResult sendTempMail(MailMessage message);
MailSendResult sendSimpleMail(MailVo mailVo);
/**
* 获取邮件发送账户信息
*/
MailSendAccount getMailSenderInfo();
}

View File

@@ -6,6 +6,7 @@ import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.system.config.MailConfig;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper;
import com.ruoyi.system.service.ISysConfigService;
@@ -29,6 +30,9 @@ public class SysConfigServiceImpl implements ISysConfigService {
@Resource
private RedisService redisService;
@Resource
private MailConfig mailConfig;
/**
* 项目启动时,初始化参数到缓存
*/
@@ -167,6 +171,7 @@ public class SysConfigServiceImpl implements ISysConfigService {
public void resetConfigCache() {
clearConfigCache();
loadingConfigCache();
mailConfig.refreshCache();
}
/**

View File

@@ -1,17 +1,29 @@
package com.ruoyi.system.service.impl;
import com.ruoyi.common.core.mail.MailMessage;
import com.ruoyi.common.core.mail.MailSendAccount;
import com.ruoyi.common.core.mail.MailSendResult;
import com.ruoyi.common.core.mail.MailSender;
import com.ruoyi.common.core.utils.DateUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.uuid.snowflake.SnowFlakeIdGenerator;
import com.ruoyi.system.config.MailConfig;
import com.ruoyi.system.domain.SysMailLog;
import com.ruoyi.system.domain.vo.MailVo;
import com.ruoyi.system.mapper.SysMailLogMapper;
import com.ruoyi.system.service.ISysMailLogService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mail.MailSendException;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 邮件发送日志Service业务层处理
@@ -19,6 +31,7 @@ import java.util.List;
* @author ryas
* created on 2024-03-01
*/
@Slf4j
@Service
public class SysMailLogServiceImpl implements ISysMailLogService {
@@ -28,6 +41,9 @@ public class SysMailLogServiceImpl implements ISysMailLogService {
@Resource
private MailSender mailSender;
@Resource
private MailConfig mailConfig;
/**
* 查询邮件发送日志
*
@@ -62,6 +78,9 @@ public class SysMailLogServiceImpl implements ISysMailLogService {
if (sysMailLog.getMailLogId() == null) {
sysMailLog.setMailLogId(SnowFlakeIdGenerator.nextIdLong());
}
if (StringUtils.isNotBlank(sysMailLog.getMsg()) && sysMailLog.getMsg().length() > 500) {
sysMailLog.setMsg(sysMailLog.getMsg().substring(0, 500));
}
return sysMailLogMapper.insertSysMailLog(sysMailLog);
}
@@ -73,6 +92,9 @@ public class SysMailLogServiceImpl implements ISysMailLogService {
*/
@Override
public int updateSysMailLog(SysMailLog sysMailLog) {
if (StringUtils.isNotBlank(sysMailLog.getMsg()) && sysMailLog.getMsg().length() > 500) {
sysMailLog.setMsg(sysMailLog.getMsg().substring(0, 500));
}
return sysMailLogMapper.updateSysMailLog(sysMailLog);
}
@@ -99,14 +121,146 @@ public class SysMailLogServiceImpl implements ISysMailLogService {
}
/**
* 发送临时邮件
* 发送邮件 - 简易版
*
* @param message 邮件内容
* @param mailVo 邮件内容
* @return 结果
*/
@Override
public MailSendResult sendTempMail(MailMessage message) {
//TODO 待完成
return null;
public MailSendResult sendSimpleMail(MailVo mailVo) {
// 参数校验
if (StringUtils.isBlank(mailVo.getTo())) {
return MailSendResult.failure("收件人不能为空");
}
if (StringUtils.isBlank(mailVo.getSubject())) {
return MailSendResult.failure("邮件主题不能为空");
}
long startTime = System.currentTimeMillis();
// 保存附件
List<File> attachments;
try {
attachments = saveAttachments(mailVo.getAttachments());
} catch (Exception e) {
log.error("Failed to save mail attachments", e);
return MailSendResult.failure("Failed to save attachments! " + e);
}
// 发送邮件
MailMessage mailMessage = new MailMessage()
.setFrom(mailVo.getFrom())
.setTo(mailVo.getTo())
.setCc(mailVo.getCc())
.setSubject(mailVo.getSubject())
.setContent(mailVo.getContent())
.addAttachments(attachments);
MailSendResult result = mailSender.sendMail(mailMessage);
handleMailSendError(result);
// 记录邮件发送日志
addMailLog(mailVo, result, startTime);
return result;
}
/**
* 记录邮件发送日志
*
* @param mailVo 邮件内容
* @param result 发送结果
* @param startTime 发送开始时间
*/
private void addMailLog(MailVo mailVo, MailSendResult result, Long startTime) {
SysMailLog mailLog = new SysMailLog();
mailLog.setBusinessType(mailVo.getBusinessType());
mailLog.setFrom(mailVo.getFrom());
mailLog.setTo(mailVo.getTo());
mailLog.setCc(mailVo.getCc());
mailLog.setSubject(mailVo.getSubject());
mailLog.setCostTime(System.currentTimeMillis() - startTime);
if (result.isSuccess()) {
mailLog.setStatus(1L);
mailLog.setMsg("Send successfully");
} else {
mailLog.setStatus(2L);
mailLog.setMsg(result.getErrMsg());
}
int affectedRows = insertSysMailLog(mailLog);
if (affectedRows == 0) {
log.error("Failed to record mail log: {}", mailLog);
} else {
log.info("Mail send successfully, mailLogId: {}", mailLog.getMailLogId());
}
}
/**
* 保存附件
* <p>
* 路径:配置的根路径+年份+月份+文件名,同名文件会覆盖
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
private List<File> saveAttachments(MultipartFile[] attachments) throws IOException {
List<File> files = new ArrayList<>();
if (attachments != null) {
File rootDir = mailConfig.getAttachmentsSavedDir();
LocalDate nowDate = LocalDate.now();
File savedDir = new File(rootDir, nowDate.getYear() + File.separator + nowDate.getMonthValue());
for (MultipartFile attachment : attachments) {
try {
File file = new File(savedDir, Objects.requireNonNull(attachment.getOriginalFilename()));
if (!savedDir.exists()) {
savedDir.mkdirs();
}
if (file.exists()) {
// 删除已存在的同名附件
log.warn("Mail attachment file already exists, delete it: [{}]", file.getAbsolutePath());
file.delete();
}
attachment.transferTo(file);
log.info("Mail attachment saved to server locally: [{}]", file.getAbsolutePath());
files.add(file);
} catch (IOException e) {
// 删除已保存的附件
for (File f : files) {
f.delete();
}
throw e;
}
}
}
return files;
}
/**
* 进一步处理邮件发送异常
*/
private void handleMailSendError(MailSendResult result) {
Throwable e = result.getErrObj();
if (e == null) {
return;
}
log.error("Failed to send mail", e);
String msg = e.getMessage();
if (e instanceof MailSendException) {
if (msg != null && msg.toLowerCase().startsWith("mail server connection failed")) {
result.setErrMsg("邮件服务器连接失败! 请检查邮件服务器地址和端口是否正确");
} else if (msg != null && msg.toLowerCase().startsWith("mail server authentication failed")) {
result.setErrMsg("邮件服务器认证失败! 请检查配置的用户名和密码是否正确");
} else {
result.setErrMsg("邮件发送失败: " + msg);
}
} else {
result.setErrMsg("邮件发送失败,未知异常: " + msg);
}
}
/**
* 获取邮件发送账户信息
*/
@Override
public MailSendAccount getMailSenderInfo() {
MailSendAccount result = new MailSendAccount();
MailSendAccount senderAccount = mailSender.getSenderAccount();
result.setHost(senderAccount.getHost());
result.setPort(senderAccount.getPort());
result.setUsername(senderAccount.getUsername());
result.setSslFlag(senderAccount.isSslFlag());
return result;
}
}

View File

@@ -15,6 +15,7 @@
<result property="msg" column="msg"/>
<result property="createTime" column="create_time"/>
<result property="createBy" column="create_by"/>
<result property="createByName" column="create_by_name"/>
<result property="costTime" column="cost_time"/>
<result property="remark" column="remark"/>
</resultMap>
@@ -36,13 +37,28 @@
</sql>
<select id="selectSysMailLogList" parameterType="com.ruoyi.system.domain.SysMailLog" resultMap="SysMailLogResult">
<include refid="selectSysMailLogVo"/>
select
t.mail_log_id,
t.status,
t.business_type,
t.from,
t.to,
t.cc,
t.subject,
t.msg,
t.create_time,
t.create_by,
createUser.user_name as create_by_name,
t.cost_time,
t.remark
from sys_mail_log t
left join sys_user createUser on t.create_by = createUser.user_id
<where>
<if test="status != null ">and sys_mail_log.status = #{status}</if>
<if test="to != null and to != ''">and sys_mail_log.to = #{to}</if>
<if test="subject != null and subject != ''">and sys_mail_log.subject = #{subject}</if>
<if test="status != null ">and t.status = #{status}</if>
<if test="to != null and to != ''">and t.to = #{to}</if>
<if test="subject != null and subject != ''">and t.subject = #{subject}</if>
<if test="params.beginCreateTime != null and params.beginCreateTime != '' and params.endCreateTime != null and params.endCreateTime != ''">
and create_time between #{params.beginCreateTime} and #{params.endCreateTime}
and t.create_time between #{params.beginCreateTime} and #{params.endCreateTime}
</if>
</where>
</select>