TASK: 发送邮件功能及发送失败后的重试机制

This commit is contained in:
2025-07-31 15:57:47 +08:00
parent 7edc959432
commit 739e637267
12 changed files with 5131 additions and 4826 deletions

View File

@@ -0,0 +1,96 @@
package com.ai.da.common.RabbitMQ;
import com.ai.da.common.utils.MailUtil;
import com.ai.da.model.dto.BasicEmailParamDTO;
import com.ai.da.service.EmailService;
import com.alibaba.fastjson.JSONObject;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.core.io.InputStreamSource;
import org.springframework.stereotype.Component;
import software.amazon.awssdk.core.exception.RetryableException;
import org.springframework.amqp.core.Message;
import javax.annotation.Resource;
import javax.mail.MessagingException;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
@Slf4j
@Component
public class EmailRetryConsumer {
@Resource
private MailUtil mailUtil;
@Resource
private MQPublisher mqPublisher;
@Resource
private EmailService emailService;
// @RabbitListener(queues = "#{rabbitMQProperties.deadLetter.queue}")
public void handleRetry(Map<String, String> mailParams, Message message, Channel channel) throws IOException {
long tag = message.getMessageProperties().getDeliveryTag();
try {
log.info("死信队列收到消息:{}", message);
// 处理邮件发送参数
BasicEmailParamDTO basicEmailParamDTO = JSONObject.parseObject(mailParams.get("dto"), BasicEmailParamDTO.class);
String fileName = mailParams.get("filename");
InputStreamSource inputStreamSource = Objects.isNull(mailParams.get("source")) ?
null : JSONObject.parseObject(mailParams.get("source"), InputStreamSource.class);
JSONObject templateParams = JSONObject.parseObject(mailParams.get("templateParams"), JSONObject.class);
String templateName = mailParams.get("templatePath");
long logId = Long.parseLong(mailParams.get("logId"));
basicEmailParamDTO.setContent(mailUtil.setContent(templateParams, templateName));
// 发邮件
int lastReturnCode = mailUtil.sendMail(basicEmailParamDTO, fileName, inputStreamSource);
if (lastReturnCode == 250) {
log.info("邮件发送成功Subject : {}", basicEmailParamDTO.getSubject());
emailService.updateStatus(logId, EmailService.DELIVERED);
} else if (lastReturnCode == 450) {
log.info("目标邮箱 {} 暂时不可用,请稍后重试", (Object) basicEmailParamDTO.getMailTo());
// 重试
retry(mailParams, message, channel, tag, logId);
} else if (lastReturnCode == 550) {
log.info("目标邮箱 {} 不可用,邮件发送失败", (Object) basicEmailParamDTO.getMailTo());
emailService.updateStatus(logId, EmailService.FAILED);
} else {
log.info("邮件发送失败Subject : {}, 状态码: {}", basicEmailParamDTO.getSubject(), lastReturnCode);
retry(mailParams, message, channel, tag, logId);
emailService.updateStatus(logId, EmailService.FAILED);
}
channel.basicAck(tag, false);
} catch (RetryableException e) {
log.info("邮件重试发生异常:RetryableException -> {}", e.getMessage());
channel.basicAck(tag, false); // 确认原消息
} catch (Exception e) {
log.info("邮件重试发生异常:Exception -> {}", e.getMessage());
channel.basicAck(tag, false); // 确认原消息
}
}
private int getRetryAttempt(Message message) {
Integer attempt = message.getMessageProperties()
.getHeader("x-retry-attempt");
return attempt != null ? attempt : 1;
}
private void retry(Map<String, String> mailParams, Message message, Channel channel, long tag, long logId) throws IOException{
int attempt = getRetryAttempt(message);
if (attempt >= 3) { // 最大重试次数
channel.basicReject(tag, false);
emailService.updateStatus(logId, EmailService.FAILED);
log.error("重试结束,邮件最终发送失败: {}", mailParams);
} else {
log.info("重新将邮件信息发送到重试队列");
mqPublisher.sendEmailMsg(mailParams, attempt);
channel.basicAck(tag, false); // 确认原消息
// 更新数据库
emailService.updateRetryCount(logId, attempt + 1);
}
}
}

View File

@@ -1,6 +1,6 @@
package com.ai.da.common.RabbitMQ; package com.ai.da.common.RabbitMQ;
import org.springframework.amqp.core.Queue; import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@@ -40,4 +40,43 @@ public class MQConfig {
public Queue relightResultQueue() { public Queue relightResultQueue() {
return new Queue(rabbitMQProperties.getQueues().getRelightResult()); return new Queue(rabbitMQProperties.getQueues().getRelightResult());
} }
@Bean
public Queue poseTransformQueue() {
return new Queue(rabbitMQProperties.getQueues().getPoseTransform());
}
@Bean
public Queue mailRetryQueue() {
// 普通队列不绑定DLX首次失败后才进入MQ
// durable 持久化队列
return QueueBuilder.durable(rabbitMQProperties.getQueues().getEmailRetry())
// 关键参数:绑定死信交换机
.withArgument("x-dead-letter-exchange", rabbitMQProperties.getDeadLetter().getExchange())
// 可选:指定死信路由键(默认使用原消息的路由键)
.withArgument("x-dead-letter-routing-key", rabbitMQProperties.getDeadLetter().getRoutingKey())
.build();
}
// 新增死信交换机
@Bean
public DirectExchange deadLetterExchange() {
return new DirectExchange(rabbitMQProperties.getDeadLetter().getExchange());
}
// 新增死信队列
@Bean
public Queue deadLetterQueue() {
return QueueBuilder.durable(rabbitMQProperties.getDeadLetter().getQueue()).build();
}
// 绑定死信队列
@Bean
public Binding deadLetterBinding() {
return BindingBuilder.bind(deadLetterQueue())
.to(deadLetterExchange())
.with(rabbitMQProperties.getDeadLetter().getRoutingKey());
}
} }

View File

@@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Map;
@Slf4j @Slf4j
@Component @Component
@@ -18,12 +19,38 @@ public class MQPublisher {
private AmqpTemplate amqpTemplate; private AmqpTemplate amqpTemplate;
public void sendGenerateMessage(String mm) { public void sendGenerateMessage(String mm) {
log.info("send message: " + mm); log.info("send generate message: {}", mm);
amqpTemplate.convertAndSend(rabbitMQProperties.getQueues().getGenerate(), mm); amqpTemplate.convertAndSend(rabbitMQProperties.getQueues().getGenerate(), mm);
} }
public void sendSRMessage(String mm) { public void sendSRMessage(String mm) {
log.info("send message: " + mm); log.info("send message: {}", mm);
amqpTemplate.convertAndSend(rabbitMQProperties.getQueues().getSr(), mm); amqpTemplate.convertAndSend(rabbitMQProperties.getQueues().getSr(), mm);
} }
/**
*
* @param mailParams 含有的字段
* {"dto": basicEmailParamDTO, "filename": fileName, "source": inputStreamSource,
* "templateParams": jsonObject, "templatePath": path}
* 邮件发送参数,附件文件名,附件数据
* @param retryTimes 重试次数初始为0
*/
public void sendEmailMsg(Map<String, String> mailParams, int retryTimes){
log.info("send email MQ message: {} ", mailParams);
// // 重新入队(指数退避) 时间单位:毫秒
long newDelay = (long) (5000 * Math.pow(2, retryTimes + 1));
log.info("send email MQ delay: {} ms, retry attempt: {}", newDelay, retryTimes + 1);
amqpTemplate.convertAndSend(
rabbitMQProperties.getQueues().getEmailRetry(),
mailParams,
m -> {
m.getMessageProperties().setExpiration(String.valueOf(newDelay));
m.getMessageProperties().setHeader("x-retry-attempt", retryTimes + 1);
return m;
}
);
}
} }

View File

@@ -11,6 +11,7 @@ public class RabbitMQProperties {
private Queues queues; private Queues queues;
private Exchange exchange; private Exchange exchange;
private DeadLetter deadLetter; // 新增死信配置
@Data @Data
public static class Queues { public static class Queues {
@@ -21,6 +22,7 @@ public class RabbitMQProperties {
private String toProductImageResult; private String toProductImageResult;
private String relightResult; private String relightResult;
private String poseTransform; private String poseTransform;
private String emailRetry;
private String designBatch; private String designBatch;
private String relightBatch; private String relightBatch;
private String toProductImageBatch; private String toProductImageBatch;
@@ -31,5 +33,13 @@ public class RabbitMQProperties {
public static class Exchange { public static class Exchange {
private String generate; private String generate;
} }
// 新增死信配置内部类
@Data
public static class DeadLetter {
private String exchange;
private String queue;
private String routingKey;
}
} }

View File

@@ -140,6 +140,14 @@ public class MailUtil {
return basicEmailParamDTO; return basicEmailParamDTO;
} }
public BasicEmailParamDTO setBasicEmailParams(List<String> mailTo, String title) throws AddressException {
BasicEmailParamDTO basicEmailParamDTO = new BasicEmailParamDTO();
basicEmailParamDTO.setSenderUserMail("info@aida.com.hk");
basicEmailParamDTO.setMailTo(getInternetAddressList(mailTo));
basicEmailParamDTO.setSubject(title);
return basicEmailParamDTO;
}
/** /**
* 将地址转换为InternetAddress类型 * 将地址转换为InternetAddress类型
* *

View File

@@ -8,9 +8,9 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations; import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@@ -18,6 +18,8 @@ import javax.annotation.Resource;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.Duration; import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -527,4 +529,69 @@ public class RedisUtil {
return new ProgressDTO(0, 0, false); return new ProgressDTO(0, 0, false);
} }
} }
// Lua脚本原子化操作
/*private static final String RATE_LIMIT_SCRIPT =
"local current = redis.call('INCR', KEYS[1])\n" +
"if tonumber(current) == 1 then\n" +
" redis.call('EXPIRE', KEYS[1], ARGV[1])\n" +
"end\n" +
"return tonumber(current) <= tonumber(ARGV[2])";*/
private static final String RATE_LIMIT_SCRIPT =
"local current = redis.call('INCR', KEYS[1])\n" +
"local ttl = redis.call('TTL', KEYS[1])\n" +
"if tonumber(current) == 1 or tonumber(ttl) == -1 then\n" +
" redis.call('EXPIRE', KEYS[1], ARGV[1])\n" +
"end\n" +
"return tonumber(current) <= tonumber(ARGV[2])";
/**
* 检查是否允许发送
* @param userId 用户ID
* @return true-允许发送false-已超限
*/
public boolean allowSend(Long userId) {
String hourKey = getCurrentHourKey(userId);
// 执行Lua脚本
List<String> keys = Collections.singletonList(hourKey);
List<Long> args = Arrays.asList(
3600L, // 1小时过期
10L // 限制数量 一小时只能向普通用户发10封
);
Boolean result = redisTemplate.execute(
new DefaultRedisScript<>(RATE_LIMIT_SCRIPT, Boolean.class),
keys,
args.toArray()
);
return Boolean.TRUE.equals(result);
}
/**
* 获取当前小时的Key
* 格式email_limit:{userId}:{yyyyMMddHH}
*/
private String getCurrentHourKey(Long userId) {
String hour = LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyyMMddHH"));
return String.format("email_limit:%s:%s", userId, hour);
}
/**
* 获取当前已发送数量
*/
public int getCurrentCount(Long userId) {
String key = getCurrentHourKey(userId);
String val = redisTemplate.opsForValue().get(key);
int count;
if (StringUtils.isBlank(val)){
count = 0;
}else {
count = Integer.parseInt(val);
}
return count;
}
} }

View File

@@ -426,45 +426,7 @@ public class SendEmailUtil {
} }
} }
private final static Long GENERATE_EXCEPTION_WARNING_ID = 122589L; // todo ?需要保留吗
public static void sendGenerateExceptionWarning(String message) {
try {
// 实例化一个认证对象
Credential cred = new Credential(SECRET_ID, SECRET_KEy);
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("ses.tencentcloudapi.com");
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
SesClient client = new SesClient(cred, "ap-hongkong", clientProfile);
SendEmailRequest req = new SendEmailRequest();
req.setFromEmailAddress(CODE_CREATE_SEND_ADDRESS);
req.setDestination(new String[]{"xupei3360@163.com"});
// 根据邮件类型设置不同的主题和模板
String subject = "";
Template template = new Template();
subject = "Warning: AiDA 3.0 Generate Exception Warning";
template.setTemplateID(GENERATE_EXCEPTION_WARNING_ID);
JSONObject parameter = new JSONObject();
parameter.put("errorMessage", message);
parameter.put("time", DateUtil.dateToStr(new Date(), DateUtil.YYYY_MM_DD_HH_MM_SS));
template.setTemplateData(parameter.toJSONString());
req.setSubject(subject);
req.setTemplate(template);
// 发送邮件
SendEmailResponse resp = client.SendEmail(req);
log.info("短信发送结果res###{}", SendEmailResponse.toJsonString(resp));
} catch (TencentCloudSDKException e) {
log.info("邮件发送失败###{}", e.toString());
throw new BusinessException("failed.to.send.mail");
}
}
private final static Long QUESTIONNAIRE_FEEDBACK_EN_ID = 124151L; private final static Long QUESTIONNAIRE_FEEDBACK_EN_ID = 124151L;
private final static Long QUESTIONNAIRE_FEEDBACK_CN_ID = 124156L; private final static Long QUESTIONNAIRE_FEEDBACK_CN_ID = 124156L;
@@ -606,6 +568,7 @@ public class SendEmailUtil {
} }
} }
// todo 目前该定时器已取消,是否需要保留该模板?
private final static Long NEW_USER_REGISTER_NOTIFICATION_EN = 126919L; private final static Long NEW_USER_REGISTER_NOTIFICATION_EN = 126919L;
public static void notificationForRegisterUser(String receiverAddress) { public static void notificationForRegisterUser(String receiverAddress) {
@@ -644,51 +607,6 @@ public class SendEmailUtil {
} }
} }
private final static Long CHANGE_MAILBOX_CONFIRM_CN = 128278L;
private final static Long CHANGE_MAILBOX_CONFIRM_EN = 128277L;
public static void changeMailboxConfirm(String receiverAddress, String language, String name, String link) {
try {
// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey此处还需注意密钥对的保密
// 代码泄露可能会导致 SecretId 和 SecretKey 泄露并威胁账号下所有资源的安全性。以下代码示例仅供参考建议采用更安全的方式来使用密钥请参见https://cloud.tencent.com/document/product/1278/85305
// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
Credential cred = new Credential(SECRET_ID, SECRET_KEy);
// 实例化一个http选项可选的没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("ses.tencentcloudapi.com");
// 实例化一个client选项可选的没有特殊需求可以跳过
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
// 实例化要请求产品的client对象,clientProfile是可选的
SesClient client = new SesClient(cred, "ap-hongkong", clientProfile);
// 实例化一个请求对象,每个接口都会对应一个request对象
SendEmailRequest req = new SendEmailRequest();
req.setFromEmailAddress(SEND_ADDRESS);
req.setDestination(new String[]{receiverAddress});
Template template = new Template();
if (language.equals("ENGLISH")) {
req.setSubject("Change the email address bound to the AiDA account");
template.setTemplateID(CHANGE_MAILBOX_CONFIRM_EN);
} else {
req.setSubject("更换AiDA账号绑定的邮箱地址");
template.setTemplateID(CHANGE_MAILBOX_CONFIRM_CN);
}
JSONObject param = new JSONObject();
param.put("userName", name);
param.put("link", link);
template.setTemplateData(param.toJSONString());
req.setTemplate(template);
// 返回的resp是一个SendEmailResponse的实例与请求对象对应
SendEmailResponse resp = client.SendEmail(req);
log.info("短信发送结果res###{}", SendEmailResponse.toJsonString(resp));
} catch (TencentCloudSDKException e) {
log.info("邮件发送失败###{}", e.toString());
throw new BusinessException("failed.to.send.mail");
}
}
private final static Long UPLOAD_TIMEOUT_REMINDER = 128324L; private final static Long UPLOAD_TIMEOUT_REMINDER = 128324L;
public static void uploadTimeoutReminder(String userName, String time) { public static void uploadTimeoutReminder(String userName, String time) {
@@ -732,7 +650,7 @@ public class SendEmailUtil {
} }
} }
private final static Long HALFPRICEPROMOTION_CN_ID = 128582L; /*private final static Long HALFPRICEPROMOTION_CN_ID = 128582L;
private final static Long HALFPRICEPROMOTION_EN_ID = 128583L; private final static Long HALFPRICEPROMOTION_EN_ID = 128583L;
public static void halfPricePromotion(Account account, String senderAddress, int type) { public static void halfPricePromotion(Account account, String senderAddress, int type) {
@@ -821,7 +739,7 @@ public class SendEmailUtil {
log.info("邮件发送失败###{}", e.toString()); log.info("邮件发送失败###{}", e.toString());
throw new BusinessException("failed.to.send.mail"); throw new BusinessException("failed.to.send.mail");
} }
} }*/
private final static Long CANCEL_MERCHANT_EN = 130720L; private final static Long CANCEL_MERCHANT_EN = 130720L;
// private final static Long NEW_MERCHANT_EN = 130721L; // private final static Long NEW_MERCHANT_EN = 130721L;
@@ -1074,4 +992,52 @@ public class SendEmailUtil {
} }
} }
private final static Long CN_2025_618 = 141425L;
private final static Long EN_2025_618 = 141424L;
public static void send618PromotionEmailTemp(String receiver, String language){
try {
// 实例化一个认证对象
Credential cred = new Credential(SECRET_ID, SECRET_KEy);
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("ses.tencentcloudapi.com");
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
SesClient client = new SesClient(cred, "ap-hongkong", clientProfile);
SendEmailRequest req = new SendEmailRequest();
req.setFromEmailAddress(CODE_CREATE_SEND_ADDRESS);
req.setDestination(new String[]{receiver});
// 根据邮件类型设置不同的主题和模板
String subject = "";
Template template = new Template();
// if (type == 1) {
// subject = "Upcoming System Upgrade for AiDA 3.0";
// template.setTemplateID(UPGRADE_NOTIFICATION_ID);
// }else {
// subject = "即将到来的AiDA 3.0系统升级";
// template.setTemplateID(UPGRADE_NOTIFICATION_ID_CHINESE);
// }
if (language.equals("ENGLISH")) {
subject = "Welcome back Subscribe AiDA with the discount code to enjoy 50% OFF!";
template.setTemplateID(EN_2025_618);
}else {
subject = "设计时速狂飙AiDA 618半价让灵感永不限流";
template.setTemplateID(CN_2025_618);
}
req.setSubject(subject);
req.setTemplate(template);
// 发送邮件
SendEmailResponse resp = client.SendEmail(req);
log.info("邮件发送成功,收件人地址:{}", receiver);
log.info("短信发送结果res###{}", SendEmailResponse.toJsonString(resp));
} catch (TencentCloudSDKException e) {
log.info(receiver);
log.error("邮件发送失败###{},收件人地址:{}", e.toString(), receiver);
}
}
} }

View File

@@ -12,6 +12,9 @@ import java.util.List;
public interface EmailService { public interface EmailService {
String FAILED = "failed";
String DELIVERED = "delivered";
String RETRYING = "retrying";
void loadSingleEmailTemplate(String templatePath); void loadSingleEmailTemplate(String templatePath);
@@ -30,6 +33,10 @@ public interface EmailService {
void sendEmail(List<String> mailTo, JSONObject jsonObject, String templateName, String title, String fileName, InputStreamSource inputStreamSource); void sendEmail(List<String> mailTo, JSONObject jsonObject, String templateName, String title, String fileName, InputStreamSource inputStreamSource);
void updateRetryCount(Long logId, int retryCount);
void updateStatus(Long logId, String status);
/** /**
* 适用于 需要自定义发件人信息 * 适用于 需要自定义发件人信息
* *

View File

@@ -55,6 +55,8 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
private ProductCouponsMapper productCouponsMapper; private ProductCouponsMapper productCouponsMapper;
@Resource @Resource
private RedisUtil redisUtil; private RedisUtil redisUtil;
@Resource
private EmailService emailService;
// 推广者注册 // 推广者注册
public Boolean registerAsAnAffiliate(String promotionMethod){ public Boolean registerAsAnAffiliate(String promotionMethod){
@@ -75,6 +77,7 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
String developer = "xupei3360@163.com"; String developer = "xupei3360@163.com";
String[] receiverEmail = {merchantEmail, developer}; String[] receiverEmail = {merchantEmail, developer};
SendEmailUtil.affiliateEmailReminder(receiverEmail, new AffiliateEmailParamsDTO(userHolder.getUsername(), promotionMethod), "new"); SendEmailUtil.affiliateEmailReminder(receiverEmail, new AffiliateEmailParamsDTO(userHolder.getUsername(), promotionMethod), "new");
// emailService.affiliateEmailReminder(Arrays.asList(/*merchantEmail,*/ developer), new AffiliateEmailParamsDTO(userHolder.getUsername(), promotionMethod), "new");
}else { }else {
throw new BusinessException("You have registered an Affiliate", ResultEnum.PROMPT.getCode()); throw new BusinessException("You have registered an Affiliate", ResultEnum.PROMPT.getCode());
} }
@@ -175,8 +178,10 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
String userName = account.getUserName(); String userName = account.getUserName();
if (isApproved){ if (isApproved){
SendEmailUtil.affiliateEmailReminder(userEmail, new AffiliateEmailParamsDTO(userName), "accepted"); SendEmailUtil.affiliateEmailReminder(userEmail, new AffiliateEmailParamsDTO(userName), "accepted");
// emailService.affiliateEmailReminder(Collections.singletonList(account.getUserEmail()), new AffiliateEmailParamsDTO(userName), "accepted");
}else { }else {
SendEmailUtil.affiliateEmailReminder(userEmail, new AffiliateEmailParamsDTO(userName), "refused"); SendEmailUtil.affiliateEmailReminder(userEmail, new AffiliateEmailParamsDTO(userName), "refused");
// emailService.affiliateEmailReminder(Collections.singletonList(account.getUserEmail()), new AffiliateEmailParamsDTO(userName), "refused");
} }
return true; return true;
} }
@@ -332,6 +337,7 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
String[] receiverEmail = {merchantEmail, developer}; String[] receiverEmail = {merchantEmail, developer};
// 邮件通知 // 邮件通知
SendEmailUtil.affiliateEmailReminder(receiverEmail, affiliateEmailParamsDTO, "summary"); SendEmailUtil.affiliateEmailReminder(receiverEmail, affiliateEmailParamsDTO, "summary");
// emailService.affiliateEmailReminder(Arrays.asList(/*merchantEmail,*/ developer), affiliateEmailParamsDTO, "summary");
} }
@Override @Override

View File

@@ -90,6 +90,8 @@ public class CollectionElementServiceImpl extends ServiceImpl<CollectionElementM
// @Resource // @Resource
// private RedisUtil redisUtil; // private RedisUtil redisUtil;
@Resource
private EmailService emailService;
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
@@ -138,6 +140,7 @@ public class CollectionElementServiceImpl extends ServiceImpl<CollectionElementM
// 邮件通知 一天最多通知3次 // 邮件通知 一天最多通知3次
log.info("上传超过5秒发送邮件通知"); log.info("上传超过5秒发送邮件通知");
SendEmailUtil.uploadTimeoutReminder(userInfo.getUsername(), String.valueOf(((end - start) / 1000))); SendEmailUtil.uploadTimeoutReminder(userInfo.getUsername(), String.valueOf(((end - start) / 1000)));
// emailService.uploadTimeoutReminder(userInfo.getUsername(), String.valueOf(((end - start) / 1000)));
redisUtil.increaseCount(RedisUtil.UPLOAD_TIMEOUT_REMINDER_COUNTER); redisUtil.increaseCount(RedisUtil.UPLOAD_TIMEOUT_REMINDER_COUNTER);
} }
return collectionElementVO; return collectionElementVO;

View File

@@ -1,16 +1,22 @@
package com.ai.da.service.impl; package com.ai.da.service.impl;
import com.ai.da.common.RabbitMQ.MQPublisher;
import com.ai.da.common.config.exception.BusinessException;
import com.ai.da.common.response.ResultEnum;
import com.ai.da.common.utils.DateUtil; import com.ai.da.common.utils.DateUtil;
import com.ai.da.common.utils.MailUtil; import com.ai.da.common.utils.MailUtil;
import com.ai.da.common.utils.RedisUtil;
import com.ai.da.mapper.primary.EmailLogMapper; import com.ai.da.mapper.primary.EmailLogMapper;
import com.ai.da.mapper.primary.EmailTemplateMapper; import com.ai.da.mapper.primary.EmailTemplateMapper;
import com.ai.da.mapper.primary.entity.*; import com.ai.da.mapper.primary.entity.*;
import com.ai.da.model.dto.AffiliateEmailParamsDTO; import com.ai.da.model.dto.AffiliateEmailParamsDTO;
import com.ai.da.model.dto.BasicEmailParamDTO; import com.ai.da.model.dto.BasicEmailParamDTO;
import com.ai.da.model.dto.SubscriptionEmailParamsDTO; import com.ai.da.model.dto.SubscriptionEmailParamsDTO;
import com.ai.da.service.AccountService;
import com.ai.da.service.EmailService; import com.ai.da.service.EmailService;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.InputStreamSource; import org.springframework.core.io.InputStreamSource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -38,6 +44,12 @@ public class EmailServiceImpl implements EmailService {
private EmailTemplateMapper emailTemplateMapper; private EmailTemplateMapper emailTemplateMapper;
@Resource @Resource
private EmailLogMapper emailLogMapper; private EmailLogMapper emailLogMapper;
@Resource
private AccountService accountService;
@Resource
private RedisUtil redisUtil;
@Resource
private MQPublisher mqPublisher;
public void loadSingleEmailTemplate(String templatePath){ public void loadSingleEmailTemplate(String templatePath){
// 获取 ClassLoader // 获取 ClassLoader
@@ -146,6 +158,14 @@ public class EmailServiceImpl implements EmailService {
* @param inputStreamSource 附件 * @param inputStreamSource 附件
*/ */
public void sendEmail(List<String> mailTo, JSONObject jsonObject, String templateName, String title, String fileName, InputStreamSource inputStreamSource) { public void sendEmail(List<String> mailTo, JSONObject jsonObject, String templateName, String title, String fileName, InputStreamSource inputStreamSource) {
if (mailTo.size() == 1){
String receiver = mailTo.get(0);
Account account = accountService.getBaseMapper().selectOne(new QueryWrapper<Account>().eq("user_email", receiver));
if (Objects.nonNull(account)){
boolean b = redisUtil.allowSend(account.getId());
if (!b){throw new BusinessException("email.count.limit", ResultEnum.PROMPT.getCode());}
}
}
EmailTemplate emailTemplate = getEmailTemplateByName(templateName); EmailTemplate emailTemplate = getEmailTemplateByName(templateName);
if (Objects.isNull(emailTemplate)){ if (Objects.isNull(emailTemplate)){
log.error("Email template: {}, dose not exist!", templateName); log.error("Email template: {}, dose not exist!", templateName);
@@ -155,16 +175,6 @@ public class EmailServiceImpl implements EmailService {
BasicEmailParamDTO basicEmailParamDTO = mailUtil.setBasicEmailParams(mailTo, jsonObject, emailTemplate.getTemplatePath(), title); BasicEmailParamDTO basicEmailParamDTO = mailUtil.setBasicEmailParams(mailTo, jsonObject, emailTemplate.getTemplatePath(), title);
int lastReturnCode = mailUtil.sendMail(basicEmailParamDTO, fileName, inputStreamSource); int lastReturnCode = mailUtil.sendMail(basicEmailParamDTO, fileName, inputStreamSource);
if (lastReturnCode == 250) {
log.info("邮件发送成功Subject : {}", basicEmailParamDTO.getSubject());
} else if (lastReturnCode == 450) {
log.info("目标邮箱 {} 暂时不可用,请稍后重试", mailTo);
} else if (lastReturnCode == 550) {
log.info("目标邮箱 {} 不可用,邮件发送失败", mailTo);
} else {
log.info("邮件发送失败Subject : {}, 状态码: {}", basicEmailParamDTO.getSubject(), lastReturnCode);
}
EmailLog emailLog = new EmailLog(); EmailLog emailLog = new EmailLog();
emailLog.setTemplateId(emailTemplate.getId()); emailLog.setTemplateId(emailTemplate.getId());
if (Objects.nonNull(jsonObject)) emailLog.setParameter(jsonObject.toString()); if (Objects.nonNull(jsonObject)) emailLog.setParameter(jsonObject.toString());
@@ -174,24 +184,79 @@ public class EmailServiceImpl implements EmailService {
emailLog.setCreateTime(LocalDateTime.now()); emailLog.setCreateTime(LocalDateTime.now());
switch (lastReturnCode) { switch (lastReturnCode) {
case 0: case 0:
case 550:
emailLog.setStatus(FAILED);
break; break;
case 250: case 250:
emailLog.setStatus("delivered"); emailLog.setStatus(DELIVERED);
break; break;
case 450: case 450:
emailLog.setStatus("retrying"); emailLog.setStatus(RETRYING);
break;
case 550:
emailLog.setStatus("failed");
break; break;
} }
emailLogMapper.insert(emailLog); emailLogMapper.insert(emailLog);
if (lastReturnCode == 250) {
log.info("邮件发送成功Subject : {}", basicEmailParamDTO.getSubject());
} else if (lastReturnCode == 450) {
log.info("目标邮箱 {} 暂时不可用,请稍后重试", mailTo);
Map<String, String> params = setRetryParams(mailTo, jsonObject, emailTemplate.getTemplatePath(), title, fileName, inputStreamSource, emailLog.getId());
// 异步重试
mqPublisher.sendEmailMsg(params, 0);
// 更新数据库重试次数
emailLog.setRetryCount(1);
emailLog.setUpdateTime(LocalDateTime.now());
emailLogMapper.updateById(emailLog);
} else if (lastReturnCode == 550) {
log.info("目标邮箱 {} 不可用,邮件发送失败", mailTo);
} else {
log.info("邮件发送失败Subject : {}, 状态码: {}", basicEmailParamDTO.getSubject(), lastReturnCode);
Map<String, String> params = setRetryParams(mailTo, jsonObject, emailTemplate.getTemplatePath(), title, fileName, inputStreamSource, emailLog.getId());
// 异步重试
mqPublisher.sendEmailMsg(params, 0);
// 更新数据库重试次数
emailLog.setRetryCount(1);
emailLog.setUpdateTime(LocalDateTime.now());
emailLogMapper.updateById(emailLog);
}
} catch (MessagingException e) { } catch (MessagingException e) {
// 计数回滚
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
public void updateRetryCount(Long logId, int retryCount){
UpdateWrapper<EmailLog> uw = new UpdateWrapper<>();
uw.eq("id", logId);
uw.set("retry_count", retryCount);
uw.set("update_time", LocalDateTime.now());
emailLogMapper.update(null, uw);
}
public void updateStatus(Long logId, String status){
UpdateWrapper<EmailLog> uw = new UpdateWrapper<>();
uw.eq("id", logId);
uw.set("status", status);
uw.set("update_time", LocalDateTime.now());
emailLogMapper.update(null, uw);
}
private Map<String, String> setRetryParams(List<String> mailTo, JSONObject jsonObject, String templatePath,
String title, String fileName, InputStreamSource inputStreamSource, Long logId) throws AddressException {
BasicEmailParamDTO dto = mailUtil.setBasicEmailParams(mailTo, title);
Map<String, String> params = new HashMap<>();
params.put("dto", JSONObject.toJSONString(dto));
params.put("filename", fileName);
String source = Objects.nonNull(inputStreamSource) ? inputStreamSource.toString() : null;
params.put("source", source);
params.put("templateParams", JSONObject.toJSONString(jsonObject));
params.put("templatePath", templatePath);
params.put("logId", logId.toString());
return params;
}
/** /**
* 适用于 需要自定义发件人信息 * 适用于 需要自定义发件人信息
* @param jsonObject 模板参数 * @param jsonObject 模板参数
@@ -246,6 +311,12 @@ public class EmailServiceImpl implements EmailService {
return emailTemplates.get(0); return emailTemplates.get(0);
} }
public void asyncRetry(BasicEmailParamDTO basicEmailParamDTO, String fileName, InputStreamSource inputStreamSource){
// todo
}
// 登入主题 // 登入主题
public final String LOGIN_SUBJECT = "Log on"; public final String LOGIN_SUBJECT = "Log on";
// 忘记密码主题 // 忘记密码主题

View File

@@ -266,6 +266,8 @@ public class StripeServiceImpl implements StripeService {
return Price.create(priceCreateParams.build()); return Price.create(priceCreateParams.build());
} }
@Resource
private EmailService emailService;
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public Boolean notify(HttpServletRequest request) { public Boolean notify(HttpServletRequest request) {
@@ -1054,6 +1056,7 @@ public class StripeServiceImpl implements StripeService {
setSubscriptionParams(paymentInfo, subscriptionInfo, orderByOrderNo, emailParamsDTO, language); setSubscriptionParams(paymentInfo, subscriptionInfo, orderByOrderNo, emailParamsDTO, language);
boolean b = SendEmailUtil.subscriptionEmailReminder(type, emailParamsDTO, language, account.getUserEmail()); boolean b = SendEmailUtil.subscriptionEmailReminder(type, emailParamsDTO, language, account.getUserEmail());
// boolean b = emailService.subscriptionEmailReminder(type, emailParamsDTO, language, account.getUserEmail());
if (!b) return false; if (!b) return false;
// 邮件通知成功后,更新标志 // 邮件通知成功后,更新标志
@@ -1092,6 +1095,7 @@ public class StripeServiceImpl implements StripeService {
emailParamsDTO.setLast4(paymentInfo.getLast4()); emailParamsDTO.setLast4(paymentInfo.getLast4());
boolean b = SendEmailUtil.subscriptionEmailReminder("fail_new", emailParamsDTO, language, account.getUserEmail()); boolean b = SendEmailUtil.subscriptionEmailReminder("fail_new", emailParamsDTO, language, account.getUserEmail());
// boolean b = emailService.subscriptionEmailReminder("fail_new", emailParamsDTO, language, account.getUserEmail());
if (!b) return false; if (!b) return false;
// 邮件通知成功后,更新标志 // 邮件通知成功后,更新标志
@@ -1154,6 +1158,7 @@ public class StripeServiceImpl implements StripeService {
// 4、发邮件 // 4、发邮件
boolean b = SendEmailUtil.subscriptionEmailReminder("fail_renewal", emailParamsDTO, language, account.getUserEmail()); boolean b = SendEmailUtil.subscriptionEmailReminder("fail_renewal", emailParamsDTO, language, account.getUserEmail());
// boolean b = emailService.subscriptionEmailReminder("fail_renewal", emailParamsDTO, language, account.getUserEmail());
if (!b) return false; if (!b) return false;
PaymentInfo payment = new PaymentInfo(); PaymentInfo payment = new PaymentInfo();