TASK: 发送邮件功能及发送失败后的重试机制
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
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.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -40,4 +40,43 @@ public class MQConfig {
|
||||
public Queue relightResultQueue() {
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@@ -18,12 +19,38 @@ public class MQPublisher {
|
||||
private AmqpTemplate amqpTemplate;
|
||||
|
||||
public void sendGenerateMessage(String mm) {
|
||||
log.info("send message: " + mm);
|
||||
log.info("send generate message: {}", mm);
|
||||
amqpTemplate.convertAndSend(rabbitMQProperties.getQueues().getGenerate(), mm);
|
||||
}
|
||||
|
||||
public void sendSRMessage(String mm) {
|
||||
log.info("send message: " + mm);
|
||||
log.info("send message: {}", 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;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ public class RabbitMQProperties {
|
||||
|
||||
private Queues queues;
|
||||
private Exchange exchange;
|
||||
private DeadLetter deadLetter; // 新增死信配置
|
||||
|
||||
@Data
|
||||
public static class Queues {
|
||||
@@ -21,6 +22,7 @@ public class RabbitMQProperties {
|
||||
private String toProductImageResult;
|
||||
private String relightResult;
|
||||
private String poseTransform;
|
||||
private String emailRetry;
|
||||
private String designBatch;
|
||||
private String relightBatch;
|
||||
private String toProductImageBatch;
|
||||
@@ -31,5 +33,13 @@ public class RabbitMQProperties {
|
||||
public static class Exchange {
|
||||
private String generate;
|
||||
}
|
||||
|
||||
// 新增死信配置内部类
|
||||
@Data
|
||||
public static class DeadLetter {
|
||||
private String exchange;
|
||||
private String queue;
|
||||
private String routingKey;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -140,6 +140,14 @@ public class MailUtil {
|
||||
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类型
|
||||
*
|
||||
|
||||
@@ -8,9 +8,9 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.ZSetOperations;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
@@ -18,6 +18,8 @@ import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -527,4 +529,69 @@ public class RedisUtil {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -426,45 +426,7 @@ public class SendEmailUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private final static Long GENERATE_EXCEPTION_WARNING_ID = 122589L;
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
// todo ?需要保留吗
|
||||
private final static Long QUESTIONNAIRE_FEEDBACK_EN_ID = 124151L;
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
public static void halfPricePromotion(Account account, String senderAddress, int type) {
|
||||
@@ -821,7 +739,7 @@ public class SendEmailUtil {
|
||||
log.info("邮件发送失败###{}", e.toString());
|
||||
throw new BusinessException("failed.to.send.mail");
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
private final static Long CANCEL_MERCHANT_EN = 130720L;
|
||||
// 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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ import java.util.List;
|
||||
|
||||
public interface EmailService {
|
||||
|
||||
String FAILED = "failed";
|
||||
String DELIVERED = "delivered";
|
||||
String RETRYING = "retrying";
|
||||
|
||||
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 updateRetryCount(Long logId, int retryCount);
|
||||
|
||||
void updateStatus(Long logId, String status);
|
||||
|
||||
/**
|
||||
* 适用于 : 需要自定义发件人信息
|
||||
*
|
||||
|
||||
@@ -55,6 +55,8 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
|
||||
private ProductCouponsMapper productCouponsMapper;
|
||||
@Resource
|
||||
private RedisUtil redisUtil;
|
||||
@Resource
|
||||
private EmailService emailService;
|
||||
|
||||
// 推广者注册
|
||||
public Boolean registerAsAnAffiliate(String promotionMethod){
|
||||
@@ -75,6 +77,7 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
|
||||
String developer = "xupei3360@163.com";
|
||||
String[] receiverEmail = {merchantEmail, developer};
|
||||
SendEmailUtil.affiliateEmailReminder(receiverEmail, new AffiliateEmailParamsDTO(userHolder.getUsername(), promotionMethod), "new");
|
||||
// emailService.affiliateEmailReminder(Arrays.asList(/*merchantEmail,*/ developer), new AffiliateEmailParamsDTO(userHolder.getUsername(), promotionMethod), "new");
|
||||
}else {
|
||||
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();
|
||||
if (isApproved){
|
||||
SendEmailUtil.affiliateEmailReminder(userEmail, new AffiliateEmailParamsDTO(userName), "accepted");
|
||||
// emailService.affiliateEmailReminder(Collections.singletonList(account.getUserEmail()), new AffiliateEmailParamsDTO(userName), "accepted");
|
||||
}else {
|
||||
SendEmailUtil.affiliateEmailReminder(userEmail, new AffiliateEmailParamsDTO(userName), "refused");
|
||||
// emailService.affiliateEmailReminder(Collections.singletonList(account.getUserEmail()), new AffiliateEmailParamsDTO(userName), "refused");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -332,6 +337,7 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
|
||||
String[] receiverEmail = {merchantEmail, developer};
|
||||
// 邮件通知
|
||||
SendEmailUtil.affiliateEmailReminder(receiverEmail, affiliateEmailParamsDTO, "summary");
|
||||
// emailService.affiliateEmailReminder(Arrays.asList(/*merchantEmail,*/ developer), affiliateEmailParamsDTO, "summary");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -90,6 +90,8 @@ public class CollectionElementServiceImpl extends ServiceImpl<CollectionElementM
|
||||
|
||||
// @Resource
|
||||
// private RedisUtil redisUtil;
|
||||
@Resource
|
||||
private EmailService emailService;
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
@@ -138,6 +140,7 @@ public class CollectionElementServiceImpl extends ServiceImpl<CollectionElementM
|
||||
// 邮件通知 一天最多通知3次
|
||||
log.info("上传超过5秒,发送邮件通知");
|
||||
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);
|
||||
}
|
||||
return collectionElementVO;
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
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.MailUtil;
|
||||
import com.ai.da.common.utils.RedisUtil;
|
||||
import com.ai.da.mapper.primary.EmailLogMapper;
|
||||
import com.ai.da.mapper.primary.EmailTemplateMapper;
|
||||
import com.ai.da.mapper.primary.entity.*;
|
||||
import com.ai.da.model.dto.AffiliateEmailParamsDTO;
|
||||
import com.ai.da.model.dto.BasicEmailParamDTO;
|
||||
import com.ai.da.model.dto.SubscriptionEmailParamsDTO;
|
||||
import com.ai.da.service.AccountService;
|
||||
import com.ai.da.service.EmailService;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.InputStreamSource;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -38,6 +44,12 @@ public class EmailServiceImpl implements EmailService {
|
||||
private EmailTemplateMapper emailTemplateMapper;
|
||||
@Resource
|
||||
private EmailLogMapper emailLogMapper;
|
||||
@Resource
|
||||
private AccountService accountService;
|
||||
@Resource
|
||||
private RedisUtil redisUtil;
|
||||
@Resource
|
||||
private MQPublisher mqPublisher;
|
||||
|
||||
public void loadSingleEmailTemplate(String templatePath){
|
||||
// 获取 ClassLoader
|
||||
@@ -146,6 +158,14 @@ public class EmailServiceImpl implements EmailService {
|
||||
* @param 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);
|
||||
if (Objects.isNull(emailTemplate)){
|
||||
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);
|
||||
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.setTemplateId(emailTemplate.getId());
|
||||
if (Objects.nonNull(jsonObject)) emailLog.setParameter(jsonObject.toString());
|
||||
@@ -174,24 +184,79 @@ public class EmailServiceImpl implements EmailService {
|
||||
emailLog.setCreateTime(LocalDateTime.now());
|
||||
switch (lastReturnCode) {
|
||||
case 0:
|
||||
case 550:
|
||||
emailLog.setStatus(FAILED);
|
||||
break;
|
||||
case 250:
|
||||
emailLog.setStatus("delivered");
|
||||
emailLog.setStatus(DELIVERED);
|
||||
break;
|
||||
case 450:
|
||||
emailLog.setStatus("retrying");
|
||||
break;
|
||||
case 550:
|
||||
emailLog.setStatus("failed");
|
||||
emailLog.setStatus(RETRYING);
|
||||
break;
|
||||
}
|
||||
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) {
|
||||
// 计数回滚
|
||||
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 模板参数
|
||||
@@ -246,6 +311,12 @@ public class EmailServiceImpl implements EmailService {
|
||||
return emailTemplates.get(0);
|
||||
}
|
||||
|
||||
public void asyncRetry(BasicEmailParamDTO basicEmailParamDTO, String fileName, InputStreamSource inputStreamSource){
|
||||
// todo
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 登入主题
|
||||
public final String LOGIN_SUBJECT = "Log on";
|
||||
// 忘记密码主题
|
||||
|
||||
@@ -266,6 +266,8 @@ public class StripeServiceImpl implements StripeService {
|
||||
return Price.create(priceCreateParams.build());
|
||||
}
|
||||
|
||||
@Resource
|
||||
private EmailService emailService;
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean notify(HttpServletRequest request) {
|
||||
@@ -1054,6 +1056,7 @@ public class StripeServiceImpl implements StripeService {
|
||||
setSubscriptionParams(paymentInfo, subscriptionInfo, orderByOrderNo, emailParamsDTO, language);
|
||||
|
||||
boolean b = SendEmailUtil.subscriptionEmailReminder(type, emailParamsDTO, language, account.getUserEmail());
|
||||
// boolean b = emailService.subscriptionEmailReminder(type, emailParamsDTO, language, account.getUserEmail());
|
||||
if (!b) return false;
|
||||
|
||||
// 邮件通知成功后,更新标志
|
||||
@@ -1092,6 +1095,7 @@ public class StripeServiceImpl implements StripeService {
|
||||
emailParamsDTO.setLast4(paymentInfo.getLast4());
|
||||
|
||||
boolean b = SendEmailUtil.subscriptionEmailReminder("fail_new", emailParamsDTO, language, account.getUserEmail());
|
||||
// boolean b = emailService.subscriptionEmailReminder("fail_new", emailParamsDTO, language, account.getUserEmail());
|
||||
if (!b) return false;
|
||||
|
||||
// 邮件通知成功后,更新标志
|
||||
@@ -1154,6 +1158,7 @@ public class StripeServiceImpl implements StripeService {
|
||||
|
||||
// 4、发邮件
|
||||
boolean b = SendEmailUtil.subscriptionEmailReminder("fail_renewal", emailParamsDTO, language, account.getUserEmail());
|
||||
// boolean b = emailService.subscriptionEmailReminder("fail_renewal", emailParamsDTO, language, account.getUserEmail());
|
||||
if (!b) return false;
|
||||
|
||||
PaymentInfo payment = new PaymentInfo();
|
||||
|
||||
Reference in New Issue
Block a user