邮箱模块:完善发送 send 逻辑

pull/2/head
YunaiV 2023-01-25 21:32:32 +08:00
parent ea39dcc5c8
commit afced9d420
13 changed files with 275 additions and 166 deletions

View File

@ -145,9 +145,12 @@ public interface ErrorCodeConstants {
ErrorCode MAIL_ACCOUNT_NOT_EXISTS = new ErrorCode(1002023000, "邮箱账号不存在");
ErrorCode MAIL_ACCOUNT_RELATE_TEMPLATE_EXISTS = new ErrorCode(1002023001, "无法删除,该邮箱账号还有邮件模板");
// ========== 邮模版 1002024000 ==========
// ========== 邮模版 1002024000 ==========
ErrorCode MAIL_TEMPLATE_NOT_EXISTS = new ErrorCode(1002024000, "邮件模版不存在");
ErrorCode MAIL_TEMPLATE_CODE_EXISTS = new ErrorCode(1002024001, "邮件模版 code({}) 已存在");
ErrorCode MAIL_SEND_TEMPLATE_PARAM_MISS = new ErrorCode(1002021003, "模板参数({})缺失"); // TODO 优化
// ========== 邮件发送 1002025000 ==========
ErrorCode MAIL_SEND_TEMPLATE_PARAM_MISS = new ErrorCode(1002025000, "模板参数({})缺失");
ErrorCode MAIL_SEND_MAIL_NOT_EXISTS = new ErrorCode(1002025000, "邮箱不存在");
}

View File

@ -17,9 +17,4 @@ public interface MailAccountMapper extends BaseMapperX<MailAccountDO> {
.likeIfPresent(MailAccountDO::getUsername , pageReqVO.getUsername()));
}
default MailAccountDO selectOneByFrom(String from){
return selectOne(new QueryWrapperX<MailAccountDO>()
.eqIfPresent("from_address" , from));
}
}

View File

@ -22,6 +22,14 @@ public interface MailAccountService {
*/
void initLocalCache();
/**
*
*
* @param id
* @return
*/
MailAccountDO getMailAccountFromCache(Long id);
/**
*
*

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.system.service.mail.impl;
package cn.iocoder.yudao.module.system.service.mail;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountCreateReqVO;
@ -8,8 +8,6 @@ import cn.iocoder.yudao.module.system.convert.mail.MailAccountConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
import cn.iocoder.yudao.module.system.dal.mysql.mail.MailAccountMapper;
import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
import cn.iocoder.yudao.module.system.service.mail.MailAccountService;
import cn.iocoder.yudao.module.system.service.mail.MailTemplateService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@ -21,8 +19,7 @@ import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_ACCOUNT_NOT_EXISTS;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_ACCOUNT_RELATE_TEMPLATE_EXISTS;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
/**
* Service
@ -63,6 +60,11 @@ public class MailAccountServiceImpl implements MailAccountService {
mailAccountCache = convertMap(accounts, MailAccountDO::getId);
}
@Override
public MailAccountDO getMailAccountFromCache(Long id) {
return mailAccountCache.get(id);
}
@Override
public Long createMailAccount(MailAccountCreateReqVO createReqVO) {
// 插入

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.system.service.mail.impl;
package cn.iocoder.yudao.module.system.service.mail;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogExportReqVO;
@ -28,7 +28,6 @@ import java.util.Objects;
@Validated
public class MailLogServiceImpl implements MailLogService {
// TODO @wangjingyiprivate然后使用 @Resource DONE
@Resource
private MailLogMapper mailLogMapper;

View File

@ -1,19 +1,11 @@
package cn.iocoder.yudao.module.system.service.mail;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.send.MailReqVO;
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplateCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplatePageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplateUpdateReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
/**
*
* Service
*
* @author wangjingyi
* @since 2022-03-21
@ -21,17 +13,41 @@ import java.util.Map;
public interface MailSendService {
/**
*
*
*
* @param mail
* @param userId
* @param templateCode
* @param from
* @param content
* @param templateParams
* @param to
* @param templateParams
* @return
*/
Long sendSingleMailToAdmin(String mail, Long userId,
String templateCode, Map<String, Object> templateParams);
/**
* APP
*
* @param mail
* @param userId
* @param templateCode
* @param templateParams
* @return
*/
Long sendSingleMailToMember(String mail, Long userId,
String templateCode, Map<String, Object> templateParams);
/**
*
*
* @param mail
* @param userId
* @param userType
* @param templateCode
* @param templateParams
* @return
*/
void sendMail(Long userId, Integer userType, String templateCode, String from,String to, String content, Map<String, Object> templateParams);
Long sendSingleMail(String mail, Long userId, Integer userType,
String templateCode, Map<String, Object> templateParams);
/**
*
@ -40,4 +56,5 @@ public interface MailSendService {
* @param message
*/
void doSendMail(MailSendMessage message);
}

View File

@ -0,0 +1,172 @@
package cn.iocoder.yudao.module.system.service.mail;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.mail.MailAccount;
import cn.hutool.extra.mail.MailUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.module.system.convert.mail.MailAccountConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.mail.MailAccountMapper;
import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
import cn.iocoder.yudao.module.system.service.mail.MailLogService;
import cn.iocoder.yudao.module.system.service.mail.MailSendService;
import cn.iocoder.yudao.module.system.service.mail.MailTemplateService;
import cn.iocoder.yudao.module.system.service.member.MemberService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
/**
*
*
* @author wangjingyi
* @since 2022-03-21
*/
@Service
@Validated
@Slf4j
public class MailSendServiceImpl implements MailSendService {
@Resource
private AdminUserService adminUserService;
@Resource
private MemberService memberService;
@Resource
private MailAccountService mailAccountService;
@Resource
private MailTemplateService mailTemplateService;
@Resource
private MailLogService mailLogService;
@Resource
private MailProducer mailProducer;
@Override
public Long sendSingleMailToAdmin(String mail, Long userId,
String templateCode, Map<String, Object> templateParams) {
// 如果 mail 为空,则加载用户编号对应的邮箱
if (StrUtil.isEmpty(mail)) {
AdminUserDO user = adminUserService.getUser(userId);
if (user != null) {
mail = user.getEmail();
}
}
// 执行发送
return sendSingleMail(mail, userId, UserTypeEnum.ADMIN.getValue(), templateCode, templateParams);
}
@Override
public Long sendSingleMailToMember(String mail, Long userId,
String templateCode, Map<String, Object> templateParams) {
// 如果 mail 为空,则加载用户编号对应的邮箱
if (StrUtil.isEmpty(mail)) {
mail = memberService.getMemberUserEmail(userId);
}
// 执行发送
return sendSingleMail(mail, userId, UserTypeEnum.MEMBER.getValue(), templateCode, templateParams);
}
@Override
public Long sendSingleMail(String mail, Long userId, Integer userType,
String templateCode, Map<String, Object> templateParams) {
// 校验邮箱模版是否合法
MailTemplateDO template = checkMailTemplateValid(templateCode);
// 校验邮箱账号是否合法
MailAccountDO account = checkMailAccountValid(template.getAccountId());
// 校验邮箱是否存在
mail = checkMail(mail);
// 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志
List<KeyValue<String, Object>> newTemplateParams = buildTemplateParams(template, templateParams);
// 创建发送日志。如果模板被禁用,则不发送短信,只记录日志
Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus());
String content = mailTemplateService.formatMailTemplateContent(template.getContent(), templateParams);
Long sendLogId = mailLogService.createMailLog(userId, userType, mail,
account, template, content, templateParams, isSend); // TODO 芋艿:待测试
// 发送 MQ 消息,异步执行发送短信
if (isSend) {
mailProducer.sendMailSendMessage(sendLogId, account, template, content, newTemplateParams, mail); // TODO 芋艿:待测试
}
return sendLogId;
}
@Override
public void doSendMail(MailSendMessage message) {
// TODO @wangjingyi直接使用 hutool 发送,不要封装 mail client 哈,因为短信的客户端都是比较统一的 DONE
//装载账号信息
MailAccount account = MailAccountConvert.INSTANCE.convertAccount(message);
//发送邮件
try {
String messageId = MailUtil.send(account,message.getTo(),message.getTitle(),message.getContent(),false,null);
mailLogService.updateMailSendResult(message.getLogId() , messageId);
} catch (Exception e){
mailLogService.updateFailMailSendResult(message.getLogId() , e.getMessage());
}
}
@VisibleForTesting
public MailTemplateDO checkMailTemplateValid(String templateCode) {
// 获得邮件模板。考虑到效率,从缓存中获取
MailTemplateDO template = mailTemplateService.getMailTemplateByCodeFromCache(templateCode);
// 邮件模板不存在
if (template == null) {
throw exception(MAIL_TEMPLATE_NOT_EXISTS);
}
return template;
}
@VisibleForTesting
public MailAccountDO checkMailAccountValid(Long accountId) {
// 获得邮箱账号。考虑到效率,从缓存中获取
MailAccountDO account = mailAccountService.getMailAccountFromCache(accountId);
// 邮箱账号不存在
if (account == null) {
throw exception(MAIL_ACCOUNT_NOT_EXISTS);
}
return account;
}
@VisibleForTesting
public String checkMail(String mail) {
if (StrUtil.isEmpty(mail)) {
throw exception(MAIL_SEND_MAIL_NOT_EXISTS);
}
return mail;
}
/**
* KeyValue
*
* @param template
* @param templateParams
* @return
*/
@VisibleForTesting
public List<KeyValue<String, Object>> buildTemplateParams(MailTemplateDO template, Map<String, Object> templateParams) {
return template.getParams().stream().map(key -> {
Object value = templateParams.get(key);
if (value == null) {
throw exception(MAIL_SEND_TEMPLATE_PARAM_MISS, key);
}
return new KeyValue<>(key, value);
}).collect(Collectors.toList());
}
}

View File

@ -83,7 +83,7 @@ public interface MailTemplateService {
* @param params
* @return
*/
String formatMailTemplateContent(String content, Map<String, String> params);
String formatMailTemplateContent(String content, Map<String, Object> params);
/**
*

View File

@ -1,12 +1,9 @@
package cn.iocoder.yudao.module.system.service.mail.impl;
package cn.iocoder.yudao.module.system.service.mail;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplateCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplatePageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplateUpdateReqVO;
@ -14,7 +11,6 @@ import cn.iocoder.yudao.module.system.convert.mail.MailTemplateConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
import cn.iocoder.yudao.module.system.dal.mysql.mail.MailTemplateMapper;
import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
import cn.iocoder.yudao.module.system.service.mail.MailTemplateService;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -23,12 +19,12 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
/**
@ -42,6 +38,11 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
@Slf4j
public class MailTemplateServiceImpl implements MailTemplateService {
/**
* {}
*/
private static final Pattern PATTERN_PARAMS = Pattern.compile("\\{(.*?)}");
@Resource
private MailTemplateMapper mailTemplateMapper;
@ -73,7 +74,8 @@ public class MailTemplateServiceImpl implements MailTemplateService {
validateCodeUnique(null, createReqVO.getCode());
// 插入
MailTemplateDO template = MailTemplateConvert.INSTANCE.convert(createReqVO);
MailTemplateDO template = MailTemplateConvert.INSTANCE.convert(createReqVO)
.setParams(parseTemplateContentParams(createReqVO.getContent()));
mailTemplateMapper.insert(template);
// 发送刷新消息
mailProducer.sendMailTemplateRefreshMessage();
@ -88,7 +90,8 @@ public class MailTemplateServiceImpl implements MailTemplateService {
validateCodeUnique(updateReqVO.getId(),updateReqVO.getCode());
// 更新
MailTemplateDO updateObj = MailTemplateConvert.INSTANCE.convert(updateReqVO);
MailTemplateDO updateObj = MailTemplateConvert.INSTANCE.convert(updateReqVO)
.setParams(parseTemplateContentParams(updateReqVO.getContent()));
mailTemplateMapper.updateById(updateObj);
// 发送刷新消息
mailProducer.sendMailTemplateRefreshMessage();
@ -118,6 +121,12 @@ public class MailTemplateServiceImpl implements MailTemplateService {
mailProducer.sendMailTemplateRefreshMessage();
}
private void validateMailTemplateExists(Long id) {
if (mailTemplateMapper.selectById(id) == null) {
throw exception(MAIL_TEMPLATE_NOT_EXISTS);
}
}
@Override
public MailTemplateDO getMailTemplate(Long id) {return mailTemplateMapper.selectById(id);}
@ -135,14 +144,13 @@ public class MailTemplateServiceImpl implements MailTemplateService {
}
@Override
public String formatMailTemplateContent(String content, Map<String, String> params) {
public String formatMailTemplateContent(String content, Map<String, Object> params) {
return StrUtil.format(content, params);
}
private void validateMailTemplateExists(Long id) {
if (mailTemplateMapper.selectById(id) == null) {
throw exception(MAIL_TEMPLATE_NOT_EXISTS);
}
@VisibleForTesting
public List<String> parseTemplateContentParams(String content) {
return ReUtil.findAllGroup1(PATTERN_PARAMS, content);
}
@Override

View File

@ -1,116 +0,0 @@
package cn.iocoder.yudao.module.system.service.mail.impl;
import cn.hutool.extra.mail.MailAccount;
import cn.hutool.extra.mail.MailUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.module.system.convert.mail.MailAccountConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
import cn.iocoder.yudao.module.system.dal.mysql.mail.MailAccountMapper;
import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
import cn.iocoder.yudao.module.system.service.mail.MailLogService;
import cn.iocoder.yudao.module.system.service.mail.MailSendService;
import cn.iocoder.yudao.module.system.service.mail.MailTemplateService;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
/**
*
*
* @author wangjingyi
* @since 2022-03-21
*/
@Service
@Validated
@Slf4j
public class MailSendServiceImpl implements MailSendService {
@Resource
private MailAccountMapper mailAccountMapper;
@Resource
private MailTemplateService mailTemplateService;
@Resource
private MailLogService mailLogService;
@Resource
private MailProducer mailProducer;
@Override
public void sendMail(Long userId, Integer userType, String templateCode, String from,String to, String content, Map<String, Object> templateParams) {
// TODO @@wangjingyi发送的时候参考下短信DONE
//校验邮箱模版是否合法
MailTemplateDO mailTemplateDO = this.checkMailTemplateValid(templateCode);
// 创建发送日志。如果模板被禁用,则不发送短信,只记录日志
Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(mailTemplateDO.getStatus());
//校验账号信息是否合法
MailAccountDO mailAccountDO = this.checkMailAccountValid(from);
Map<String , String> params = MailAccountConvert.INSTANCE.convertToMap(mailAccountDO , content);
content = mailTemplateService.formatMailTemplateContent(mailTemplateDO.getContent(), params);
Long sendLogId = mailLogService.createMailLog(userId,userType,to,mailAccountDO , mailTemplateDO , content, templateParams, isSend);
List<KeyValue<String,Object>> newTemplateParams = buildTemplateParams(mailTemplateDO,templateParams);
// 发送 MQ 消息,异步执行发送短信
if (isSend) {
mailProducer.sendMailSendMessage(sendLogId,mailAccountDO , mailTemplateDO ,content, newTemplateParams,to);
}
}
private MailAccountDO checkMailAccountValid(String from) {
MailAccountDO mailAccountDO = mailAccountMapper.selectOneByFrom(from);
if(null == mailAccountDO){
throw exception(MAIL_ACCOUNT_NOT_EXISTS);
}
return mailAccountDO;
}
@Override
public void doSendMail(MailSendMessage message) {
// TODO @wangjingyi直接使用 hutool 发送,不要封装 mail client 哈,因为短信的客户端都是比较统一的 DONE
//装载账号信息
MailAccount account = MailAccountConvert.INSTANCE.convertAccount(message);
//发送邮件
try{
String messageId = MailUtil.send(account,message.getTo(),message.getTitle(),message.getContent(),false,null);
mailLogService.updateMailSendResult(message.getLogId() , messageId);
}catch (Exception e){
mailLogService.updateFailMailSendResult(message.getLogId() , e.getMessage());
}
}
private MailTemplateDO checkMailTemplateValid(String templateCode) {
MailTemplateDO mailTemplateDO = mailTemplateService.getMailTemplateByCodeFromCache(templateCode);
if (mailTemplateDO == null){
throw exception(MAIL_TEMPLATE_NOT_EXISTS);
}
return mailTemplateDO;
}
/**
* KeyValue
*
* @param template
* @param templateParams
* @return
*/
@VisibleForTesting
public List<KeyValue<String, Object>> buildTemplateParams(MailTemplateDO template, Map<String, Object> templateParams) {
return template.getParams().stream().map(key -> {
Object value = templateParams.get(key);
if (value == null) {
throw exception(MAIL_SEND_TEMPLATE_PARAM_MISS, key);
}
return new KeyValue<>(key, value);
}).collect(Collectors.toList());
}
}

View File

@ -15,4 +15,12 @@ public interface MemberService {
*/
String getMemberUserMobile(Long id);
/**
*
*
* @param id
* @return
*/
String getMemberUserEmail(Long id);
}

View File

@ -21,16 +21,29 @@ public class MemberServiceImpl implements MemberService {
@Override
public String getMemberUserMobile(Long id) {
if (id == null) {
return null;
}
Object user = ReflectUtil.invoke(getMemberUserApi(), "getUser", id);
Object user = getMemberUser(id);
if (user == null) {
return null;
}
return ReflectUtil.invoke(user, "getMobile");
}
@Override
public String getMemberUserEmail(Long id) {
Object user = getMemberUser(id);
if (user == null) {
return null;
}
return ReflectUtil.invoke(user, "getEmail");
}
private Object getMemberUser(Long id) {
if (id == null) {
return null;
}
return ReflectUtil.invoke(getMemberUserApi(), "getUser", id);
}
private Object getMemberUserApi() {
if (memberUserApi == null) {
memberUserApi = SpringUtil.getBean(ClassUtil.loadClass(String.format("%s.module.member.api.user.MemberUserApi", basePackage)));

View File

@ -39,7 +39,7 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
* Service
*
* @author zzf
* @date 2021/1/25 9:25
* @since 2021/1/25 9:25
*/
@Service
@Slf4j