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,43 +1,82 @@
|
||||
package com.ai.da.common.RabbitMQ;
|
||||
|
||||
import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class MQConfig {
|
||||
|
||||
@Autowired
|
||||
private RabbitMQProperties rabbitMQProperties;
|
||||
|
||||
@Bean
|
||||
public Queue generateQueue() {
|
||||
return new Queue(rabbitMQProperties.getQueues().getGenerate());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue SRQueue() {
|
||||
return new Queue(rabbitMQProperties.getQueues().getSr());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue SRResultQueue() {
|
||||
return new Queue(rabbitMQProperties.getQueues().getSrResult());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue generateResultQueue() {
|
||||
return new Queue(rabbitMQProperties.getQueues().getGenerateResult());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue toProductImageResultQueue() {
|
||||
return new Queue(rabbitMQProperties.getQueues().getToProductImageResult());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue relightResultQueue() {
|
||||
return new Queue(rabbitMQProperties.getQueues().getRelightResult());
|
||||
}
|
||||
}
|
||||
package com.ai.da.common.RabbitMQ;
|
||||
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class MQConfig {
|
||||
|
||||
@Autowired
|
||||
private RabbitMQProperties rabbitMQProperties;
|
||||
|
||||
@Bean
|
||||
public Queue generateQueue() {
|
||||
return new Queue(rabbitMQProperties.getQueues().getGenerate());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue SRQueue() {
|
||||
return new Queue(rabbitMQProperties.getQueues().getSr());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue SRResultQueue() {
|
||||
return new Queue(rabbitMQProperties.getQueues().getSrResult());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue generateResultQueue() {
|
||||
return new Queue(rabbitMQProperties.getQueues().getGenerateResult());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue toProductImageResultQueue() {
|
||||
return new Queue(rabbitMQProperties.getQueues().getToProductImageResult());
|
||||
}
|
||||
|
||||
@Bean
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,29 +1,56 @@
|
||||
package com.ai.da.common.RabbitMQ;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.core.AmqpTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class MQPublisher {
|
||||
|
||||
@Autowired
|
||||
private RabbitMQProperties rabbitMQProperties;
|
||||
|
||||
@Autowired
|
||||
private AmqpTemplate amqpTemplate;
|
||||
|
||||
public void sendGenerateMessage(String mm) {
|
||||
log.info("send message: " + mm);
|
||||
amqpTemplate.convertAndSend(rabbitMQProperties.getQueues().getGenerate(), mm);
|
||||
}
|
||||
|
||||
public void sendSRMessage(String mm) {
|
||||
log.info("send message: " + mm);
|
||||
amqpTemplate.convertAndSend(rabbitMQProperties.getQueues().getSr(), mm);
|
||||
}
|
||||
}
|
||||
package com.ai.da.common.RabbitMQ;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.core.AmqpTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class MQPublisher {
|
||||
|
||||
@Autowired
|
||||
private RabbitMQProperties rabbitMQProperties;
|
||||
|
||||
@Autowired
|
||||
private AmqpTemplate amqpTemplate;
|
||||
|
||||
public void sendGenerateMessage(String mm) {
|
||||
log.info("send generate message: {}", mm);
|
||||
amqpTemplate.convertAndSend(rabbitMQProperties.getQueues().getGenerate(), mm);
|
||||
}
|
||||
|
||||
public void sendSRMessage(String 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;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,35 +1,45 @@
|
||||
package com.ai.da.common.RabbitMQ;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "rabbitmq")
|
||||
@Data
|
||||
public class RabbitMQProperties {
|
||||
|
||||
private Queues queues;
|
||||
private Exchange exchange;
|
||||
|
||||
@Data
|
||||
public static class Queues {
|
||||
private String generate;
|
||||
private String sr;
|
||||
private String srResult;
|
||||
private String generateResult;
|
||||
private String toProductImageResult;
|
||||
private String relightResult;
|
||||
private String poseTransform;
|
||||
private String designBatch;
|
||||
private String relightBatch;
|
||||
private String toProductImageBatch;
|
||||
private String poseTransformBatch;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Exchange {
|
||||
private String generate;
|
||||
}
|
||||
}
|
||||
|
||||
package com.ai.da.common.RabbitMQ;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "rabbitmq")
|
||||
@Data
|
||||
public class RabbitMQProperties {
|
||||
|
||||
private Queues queues;
|
||||
private Exchange exchange;
|
||||
private DeadLetter deadLetter; // 新增死信配置
|
||||
|
||||
@Data
|
||||
public static class Queues {
|
||||
private String generate;
|
||||
private String sr;
|
||||
private String srResult;
|
||||
private String generateResult;
|
||||
private String toProductImageResult;
|
||||
private String relightResult;
|
||||
private String poseTransform;
|
||||
private String emailRetry;
|
||||
private String designBatch;
|
||||
private String relightBatch;
|
||||
private String toProductImageBatch;
|
||||
private String poseTransformBatch;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Exchange {
|
||||
private String generate;
|
||||
}
|
||||
|
||||
// 新增死信配置内部类
|
||||
@Data
|
||||
public static class DeadLetter {
|
||||
private String exchange;
|
||||
private String queue;
|
||||
private String routingKey;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,168 +1,176 @@
|
||||
package com.ai.da.common.utils;
|
||||
|
||||
import com.ai.da.model.dto.BasicEmailParamDTO;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.sun.mail.smtp.SMTPTransport;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.InputStreamSource;
|
||||
import org.springframework.mail.MailException;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.thymeleaf.TemplateEngine;
|
||||
import org.thymeleaf.context.Context;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.*;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class MailUtil {
|
||||
|
||||
@Resource
|
||||
private JavaMailSender javaMailSender;
|
||||
|
||||
@Resource
|
||||
private TemplateEngine templateEngine;
|
||||
|
||||
/**
|
||||
* 发送邮件 - 默认发件人
|
||||
*
|
||||
* @param basicEmailParamDTO 发送邮件所需参数
|
||||
* @param inputStreamSource 附件(如果有)
|
||||
*/
|
||||
public int sendMail(BasicEmailParamDTO basicEmailParamDTO, String fileName, InputStreamSource inputStreamSource) throws MessagingException {
|
||||
MimeMessage mimeMessage = createSimpleMail(basicEmailParamDTO, fileName, inputStreamSource);
|
||||
// 提取配置
|
||||
String host;
|
||||
String username;
|
||||
String password;
|
||||
if (StringUtil.isNullOrEmpty(basicEmailParamDTO.getServiceAddress())) {
|
||||
host = ((JavaMailSenderImpl) javaMailSender).getHost();
|
||||
} else {
|
||||
host = basicEmailParamDTO.getServiceAddress();
|
||||
}
|
||||
if (StringUtil.isNullOrEmpty(basicEmailParamDTO.getSenderUser())) {
|
||||
username = ((JavaMailSenderImpl) javaMailSender).getUsername();
|
||||
} else {
|
||||
username = basicEmailParamDTO.getSenderUser();
|
||||
}
|
||||
if (StringUtil.isNullOrEmpty(basicEmailParamDTO.getServiceAddress())) {
|
||||
password = ((JavaMailSenderImpl) javaMailSender).getPassword();
|
||||
} else {
|
||||
password = basicEmailParamDTO.getPassword();
|
||||
}
|
||||
return sendMail(mimeMessage, host, username, password);
|
||||
}
|
||||
|
||||
private int sendMail(MimeMessage mimeMessage, String host, String username, String password) throws MessagingException {
|
||||
SMTPTransport transport = null;
|
||||
try {
|
||||
// 获取 SMTPTransport
|
||||
transport = (SMTPTransport) mimeMessage.getSession().getTransport("smtp");
|
||||
// 连接到 SMTP 服务器
|
||||
transport.connect(host, username, password);
|
||||
// 发送邮件
|
||||
transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients());
|
||||
// 获取 SMTP 服务器的响应
|
||||
String lastServerResponse = transport.getLastServerResponse();
|
||||
int lastReturnCode = transport.getLastReturnCode();
|
||||
|
||||
log.info("SMTP 状态码: {}, SMTP 服务器响应: {}", lastReturnCode, lastServerResponse);
|
||||
return lastReturnCode;
|
||||
} catch (MailException | MessagingException e) {
|
||||
// 记录日志或执行其他补偿逻辑
|
||||
log.info("邮件发送失败:{}", e.getMessage());
|
||||
} finally {
|
||||
// 关闭连接
|
||||
assert transport != null;
|
||||
transport.close();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一封邮件
|
||||
*
|
||||
* @param basicEmailParamDTO 创建邮件需要的参数
|
||||
* @param inputStreamSource 附件(如果有)
|
||||
* @return 一封邮件
|
||||
*/
|
||||
private MimeMessage createSimpleMail(BasicEmailParamDTO basicEmailParamDTO, String fileName, InputStreamSource inputStreamSource) throws MessagingException {
|
||||
// 创建邮件对象
|
||||
MimeMessage message = javaMailSender.createMimeMessage();
|
||||
// 使用 MimeMessageHelper 简化邮件内容和附件的设置
|
||||
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(message, true);
|
||||
// 设置发件人
|
||||
mimeMessageHelper.setFrom(new InternetAddress(basicEmailParamDTO.getSenderUserMail()));
|
||||
// 设置收件人
|
||||
mimeMessageHelper.setTo(basicEmailParamDTO.getMailTo());
|
||||
// 设置抄送人
|
||||
if (basicEmailParamDTO.getCc() != null && basicEmailParamDTO.getCc().length > 0) {
|
||||
mimeMessageHelper.setCc(basicEmailParamDTO.getCc());
|
||||
}
|
||||
// 设置暗送人
|
||||
if (basicEmailParamDTO.getBcc() != null && basicEmailParamDTO.getBcc().length > 0) {
|
||||
mimeMessageHelper.setBcc(basicEmailParamDTO.getBcc());
|
||||
}
|
||||
// 设置邮件主题
|
||||
mimeMessageHelper.setSubject(basicEmailParamDTO.getSubject());
|
||||
// 设置邮件内容(HTML 格式)
|
||||
mimeMessageHelper.setText(basicEmailParamDTO.getContent(), true);
|
||||
// 设置附件
|
||||
if (inputStreamSource != null) {
|
||||
mimeMessageHelper.addAttachment(fileName, inputStreamSource);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置实体参数
|
||||
*
|
||||
* @param mailTo 接收邮件的邮箱地址
|
||||
* @param jsonObject 模板中变量的值
|
||||
* @return 返回一个MailEntity
|
||||
* @throws AddressException 邮箱地址值异常
|
||||
*/
|
||||
public BasicEmailParamDTO setBasicEmailParams(List<String> mailTo, JSONObject jsonObject, String templatePath, String title) throws AddressException {
|
||||
BasicEmailParamDTO basicEmailParamDTO = new BasicEmailParamDTO();
|
||||
basicEmailParamDTO.setSenderUserMail("info@aida.com.hk");
|
||||
basicEmailParamDTO.setMailTo(getInternetAddressList(mailTo));
|
||||
basicEmailParamDTO.setSubject(title);
|
||||
// todo 邮件模板不存在的报错与重试机制
|
||||
basicEmailParamDTO.setContent(setContent(jsonObject, templatePath));
|
||||
return basicEmailParamDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将地址转换为InternetAddress类型
|
||||
*
|
||||
* @param addressList 普通的地址字符串列表
|
||||
* @return InternetAddress类型的地址列表
|
||||
* @throws AddressException 地址异常
|
||||
*/
|
||||
public InternetAddress[] getInternetAddressList(List<String> addressList) throws AddressException {
|
||||
InternetAddress[] toAddress = new InternetAddress[addressList.size()];
|
||||
for (String address : addressList) {
|
||||
toAddress[addressList.indexOf(address)] = new InternetAddress(address);
|
||||
}
|
||||
return toAddress;
|
||||
}
|
||||
|
||||
public String setContent(JSONObject jsonObject, String templatePath) {
|
||||
Context context = new Context();
|
||||
if (Objects.nonNull(jsonObject)) {
|
||||
for (String key : jsonObject.keySet()) {
|
||||
context.setVariable(key, jsonObject.get(key));
|
||||
}
|
||||
}
|
||||
return templateEngine.process(templatePath, context);
|
||||
}
|
||||
|
||||
}
|
||||
package com.ai.da.common.utils;
|
||||
|
||||
import com.ai.da.model.dto.BasicEmailParamDTO;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.sun.mail.smtp.SMTPTransport;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.InputStreamSource;
|
||||
import org.springframework.mail.MailException;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.thymeleaf.TemplateEngine;
|
||||
import org.thymeleaf.context.Context;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.*;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class MailUtil {
|
||||
|
||||
@Resource
|
||||
private JavaMailSender javaMailSender;
|
||||
|
||||
@Resource
|
||||
private TemplateEngine templateEngine;
|
||||
|
||||
/**
|
||||
* 发送邮件 - 默认发件人
|
||||
*
|
||||
* @param basicEmailParamDTO 发送邮件所需参数
|
||||
* @param inputStreamSource 附件(如果有)
|
||||
*/
|
||||
public int sendMail(BasicEmailParamDTO basicEmailParamDTO, String fileName, InputStreamSource inputStreamSource) throws MessagingException {
|
||||
MimeMessage mimeMessage = createSimpleMail(basicEmailParamDTO, fileName, inputStreamSource);
|
||||
// 提取配置
|
||||
String host;
|
||||
String username;
|
||||
String password;
|
||||
if (StringUtil.isNullOrEmpty(basicEmailParamDTO.getServiceAddress())) {
|
||||
host = ((JavaMailSenderImpl) javaMailSender).getHost();
|
||||
} else {
|
||||
host = basicEmailParamDTO.getServiceAddress();
|
||||
}
|
||||
if (StringUtil.isNullOrEmpty(basicEmailParamDTO.getSenderUser())) {
|
||||
username = ((JavaMailSenderImpl) javaMailSender).getUsername();
|
||||
} else {
|
||||
username = basicEmailParamDTO.getSenderUser();
|
||||
}
|
||||
if (StringUtil.isNullOrEmpty(basicEmailParamDTO.getServiceAddress())) {
|
||||
password = ((JavaMailSenderImpl) javaMailSender).getPassword();
|
||||
} else {
|
||||
password = basicEmailParamDTO.getPassword();
|
||||
}
|
||||
return sendMail(mimeMessage, host, username, password);
|
||||
}
|
||||
|
||||
private int sendMail(MimeMessage mimeMessage, String host, String username, String password) throws MessagingException {
|
||||
SMTPTransport transport = null;
|
||||
try {
|
||||
// 获取 SMTPTransport
|
||||
transport = (SMTPTransport) mimeMessage.getSession().getTransport("smtp");
|
||||
// 连接到 SMTP 服务器
|
||||
transport.connect(host, username, password);
|
||||
// 发送邮件
|
||||
transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients());
|
||||
// 获取 SMTP 服务器的响应
|
||||
String lastServerResponse = transport.getLastServerResponse();
|
||||
int lastReturnCode = transport.getLastReturnCode();
|
||||
|
||||
log.info("SMTP 状态码: {}, SMTP 服务器响应: {}", lastReturnCode, lastServerResponse);
|
||||
return lastReturnCode;
|
||||
} catch (MailException | MessagingException e) {
|
||||
// 记录日志或执行其他补偿逻辑
|
||||
log.info("邮件发送失败:{}", e.getMessage());
|
||||
} finally {
|
||||
// 关闭连接
|
||||
assert transport != null;
|
||||
transport.close();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一封邮件
|
||||
*
|
||||
* @param basicEmailParamDTO 创建邮件需要的参数
|
||||
* @param inputStreamSource 附件(如果有)
|
||||
* @return 一封邮件
|
||||
*/
|
||||
private MimeMessage createSimpleMail(BasicEmailParamDTO basicEmailParamDTO, String fileName, InputStreamSource inputStreamSource) throws MessagingException {
|
||||
// 创建邮件对象
|
||||
MimeMessage message = javaMailSender.createMimeMessage();
|
||||
// 使用 MimeMessageHelper 简化邮件内容和附件的设置
|
||||
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(message, true);
|
||||
// 设置发件人
|
||||
mimeMessageHelper.setFrom(new InternetAddress(basicEmailParamDTO.getSenderUserMail()));
|
||||
// 设置收件人
|
||||
mimeMessageHelper.setTo(basicEmailParamDTO.getMailTo());
|
||||
// 设置抄送人
|
||||
if (basicEmailParamDTO.getCc() != null && basicEmailParamDTO.getCc().length > 0) {
|
||||
mimeMessageHelper.setCc(basicEmailParamDTO.getCc());
|
||||
}
|
||||
// 设置暗送人
|
||||
if (basicEmailParamDTO.getBcc() != null && basicEmailParamDTO.getBcc().length > 0) {
|
||||
mimeMessageHelper.setBcc(basicEmailParamDTO.getBcc());
|
||||
}
|
||||
// 设置邮件主题
|
||||
mimeMessageHelper.setSubject(basicEmailParamDTO.getSubject());
|
||||
// 设置邮件内容(HTML 格式)
|
||||
mimeMessageHelper.setText(basicEmailParamDTO.getContent(), true);
|
||||
// 设置附件
|
||||
if (inputStreamSource != null) {
|
||||
mimeMessageHelper.addAttachment(fileName, inputStreamSource);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置实体参数
|
||||
*
|
||||
* @param mailTo 接收邮件的邮箱地址
|
||||
* @param jsonObject 模板中变量的值
|
||||
* @return 返回一个MailEntity
|
||||
* @throws AddressException 邮箱地址值异常
|
||||
*/
|
||||
public BasicEmailParamDTO setBasicEmailParams(List<String> mailTo, JSONObject jsonObject, String templatePath, String title) throws AddressException {
|
||||
BasicEmailParamDTO basicEmailParamDTO = new BasicEmailParamDTO();
|
||||
basicEmailParamDTO.setSenderUserMail("info@aida.com.hk");
|
||||
basicEmailParamDTO.setMailTo(getInternetAddressList(mailTo));
|
||||
basicEmailParamDTO.setSubject(title);
|
||||
// todo 邮件模板不存在的报错与重试机制
|
||||
basicEmailParamDTO.setContent(setContent(jsonObject, templatePath));
|
||||
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类型
|
||||
*
|
||||
* @param addressList 普通的地址字符串列表
|
||||
* @return InternetAddress类型的地址列表
|
||||
* @throws AddressException 地址异常
|
||||
*/
|
||||
public InternetAddress[] getInternetAddressList(List<String> addressList) throws AddressException {
|
||||
InternetAddress[] toAddress = new InternetAddress[addressList.size()];
|
||||
for (String address : addressList) {
|
||||
toAddress[addressList.indexOf(address)] = new InternetAddress(address);
|
||||
}
|
||||
return toAddress;
|
||||
}
|
||||
|
||||
public String setContent(JSONObject jsonObject, String templatePath) {
|
||||
Context context = new Context();
|
||||
if (Objects.nonNull(jsonObject)) {
|
||||
for (String key : jsonObject.keySet()) {
|
||||
context.setVariable(key, jsonObject.get(key));
|
||||
}
|
||||
}
|
||||
return templateEngine.process(templatePath, context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,138 +1,145 @@
|
||||
package com.ai.da.service;
|
||||
|
||||
import com.ai.da.mapper.primary.entity.Account;
|
||||
import com.ai.da.mapper.primary.entity.TrialOrder;
|
||||
import com.ai.da.model.dto.AffiliateEmailParamsDTO;
|
||||
import com.ai.da.model.dto.BasicEmailParamDTO;
|
||||
import com.ai.da.model.dto.SubscriptionEmailParamsDTO;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.springframework.core.io.InputStreamSource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface EmailService {
|
||||
|
||||
|
||||
void loadSingleEmailTemplate(String templatePath);
|
||||
|
||||
void loadTemplatesFromResources(String resourcesPath);
|
||||
|
||||
/**
|
||||
* 发邮件
|
||||
*
|
||||
* @param mailTo 收件人邮箱
|
||||
* @param jsonObject 动态邮件模板参数
|
||||
* @param templateName 邮件模板名(只有文件名且需要带文件后缀)
|
||||
* @param title 邮件标题
|
||||
* @param fileName 附件文件名。没有附件置为null
|
||||
* @param inputStreamSource 附件文件数据。没有附件置为null
|
||||
*/
|
||||
|
||||
void sendEmail(List<String> mailTo, JSONObject jsonObject, String templateName, String title, String fileName, InputStreamSource inputStreamSource);
|
||||
|
||||
/**
|
||||
* 适用于 : 需要自定义发件人信息
|
||||
*
|
||||
* @param jsonObject 模板参数
|
||||
* @param basicEmailParamDTO 包含发件人信息、邮件标题、收件人信息
|
||||
* @param templateName 使用的模板文件名
|
||||
* @param fileName 附件文件名
|
||||
* @param inputStreamSource 附件文件信息
|
||||
*/
|
||||
|
||||
void sendEmail(JSONObject jsonObject, BasicEmailParamDTO basicEmailParamDTO, String templateName, String fileName, InputStreamSource inputStreamSource);
|
||||
|
||||
// 登入模板id
|
||||
String LOGIN_TEMPLATE_ID = "58020_login_aida_en.html";
|
||||
// 修改密码模板id
|
||||
String UPDATE_PWD_TEMPLATE_ID = "58022_update_password.html";
|
||||
// 异常ip模板id
|
||||
String EXCEPTION_ID_TEMPLATE_ID = "58021_exception_ip.html";
|
||||
// 绑定邮箱模板id
|
||||
String BIND_MAILBOX_TEMPLATE_ID = "132754_绑定邮箱.html";
|
||||
// 更换绑定邮箱
|
||||
String CHANGE_MAILBOX_TEMPLATE_ID = "128210_change_mailbox_en.html";
|
||||
|
||||
/**
|
||||
* 发送登录相关的邮件
|
||||
*
|
||||
* @param receiverAddress 收件地址
|
||||
* @param ip 请求ip
|
||||
* @param templateId 模板ID名
|
||||
* @param verifyCode 验证码
|
||||
*/
|
||||
Boolean send(String receiverAddress, String ip, String templateId, String verifyCode);
|
||||
|
||||
/**
|
||||
* 发送试用订单相关的邮件
|
||||
*
|
||||
* @param receiverAddress 收件人邮箱地址
|
||||
* @param trialOrder 试用订单相关参数
|
||||
* @param emailType 邮件类型:1 - 提交试用请求,2 - 审批通过,3 - 试用请求通过通知
|
||||
* @param country 通过城市判断邮件模板的语言
|
||||
* @param link ?
|
||||
*/
|
||||
void sendCustomEmail(String receiverAddress, TrialOrder trialOrder, int emailType, String country, Boolean link);
|
||||
|
||||
/**
|
||||
* 发送昨日的试用订单用户数据
|
||||
*
|
||||
* @param receiverAddress 收件人地址
|
||||
* @param fileName 附件文件名
|
||||
* @param inputStreamSource 附件文件数据
|
||||
*/
|
||||
void sendExcelEmail(List<String> receiverAddress, String fileName, InputStreamSource inputStreamSource);
|
||||
|
||||
/**
|
||||
* 发送昨日的试用订单用户数据--无试用订单情况
|
||||
*
|
||||
* @param receiverAddress 收件人地址
|
||||
*/
|
||||
void sendNoExcelEmail(List<String> receiverAddress);
|
||||
|
||||
/**
|
||||
* 向账号快要到期的用户发送提醒邮件
|
||||
*
|
||||
* @param account 账号信息
|
||||
*/
|
||||
void sendWillBeExpiredEmail(Account account);
|
||||
|
||||
/**
|
||||
* 发送系统升级通知邮件
|
||||
*
|
||||
* @param account 用户信息
|
||||
* @param senderAddress 发件人邮件
|
||||
* @param type 邮件类型
|
||||
*/
|
||||
void sendUpgradeNotification(Account account, String senderAddress, Integer type);
|
||||
|
||||
/**
|
||||
* 通知在Code_Create上付费的用户,AiDA账号的更新
|
||||
*
|
||||
* @param receiverAddress 收件人地址
|
||||
* @param emailType 邮件类型
|
||||
* @param country 国家(确定发送邮件的语言)
|
||||
* @param userName 用户名
|
||||
* @param date 账号到期时间
|
||||
*/
|
||||
void notificationForPaidUser(String receiverAddress, int emailType, String country, String userName, String date);
|
||||
|
||||
/**
|
||||
* 广场用户注册通知邮件
|
||||
*
|
||||
* @param userEmail
|
||||
* @param randomVerifyCode
|
||||
* @return
|
||||
*/
|
||||
Boolean designWorksRegister(String userEmail, String randomVerifyCode);
|
||||
|
||||
void uploadTimeoutReminder(String userName, String time);
|
||||
|
||||
boolean subscriptionEmailReminder(String type, SubscriptionEmailParamsDTO subscriptionEmailParamsDTO, String language, String receiverAddress);
|
||||
|
||||
void affiliateEmailReminder(List<String> receiverAddress, AffiliateEmailParamsDTO paramsDTO, String type);
|
||||
|
||||
void creditsPurchaseReminder(String username, String quantity, String amount);
|
||||
|
||||
void commonExceptionReminder(String functionName, List<String> destination);
|
||||
}
|
||||
package com.ai.da.service;
|
||||
|
||||
import com.ai.da.mapper.primary.entity.Account;
|
||||
import com.ai.da.mapper.primary.entity.TrialOrder;
|
||||
import com.ai.da.model.dto.AffiliateEmailParamsDTO;
|
||||
import com.ai.da.model.dto.BasicEmailParamDTO;
|
||||
import com.ai.da.model.dto.SubscriptionEmailParamsDTO;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.springframework.core.io.InputStreamSource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface EmailService {
|
||||
|
||||
String FAILED = "failed";
|
||||
String DELIVERED = "delivered";
|
||||
String RETRYING = "retrying";
|
||||
|
||||
void loadSingleEmailTemplate(String templatePath);
|
||||
|
||||
void loadTemplatesFromResources(String resourcesPath);
|
||||
|
||||
/**
|
||||
* 发邮件
|
||||
*
|
||||
* @param mailTo 收件人邮箱
|
||||
* @param jsonObject 动态邮件模板参数
|
||||
* @param templateName 邮件模板名(只有文件名且需要带文件后缀)
|
||||
* @param title 邮件标题
|
||||
* @param fileName 附件文件名。没有附件置为null
|
||||
* @param inputStreamSource 附件文件数据。没有附件置为null
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* 适用于 : 需要自定义发件人信息
|
||||
*
|
||||
* @param jsonObject 模板参数
|
||||
* @param basicEmailParamDTO 包含发件人信息、邮件标题、收件人信息
|
||||
* @param templateName 使用的模板文件名
|
||||
* @param fileName 附件文件名
|
||||
* @param inputStreamSource 附件文件信息
|
||||
*/
|
||||
|
||||
void sendEmail(JSONObject jsonObject, BasicEmailParamDTO basicEmailParamDTO, String templateName, String fileName, InputStreamSource inputStreamSource);
|
||||
|
||||
// 登入模板id
|
||||
String LOGIN_TEMPLATE_ID = "58020_login_aida_en.html";
|
||||
// 修改密码模板id
|
||||
String UPDATE_PWD_TEMPLATE_ID = "58022_update_password.html";
|
||||
// 异常ip模板id
|
||||
String EXCEPTION_ID_TEMPLATE_ID = "58021_exception_ip.html";
|
||||
// 绑定邮箱模板id
|
||||
String BIND_MAILBOX_TEMPLATE_ID = "132754_绑定邮箱.html";
|
||||
// 更换绑定邮箱
|
||||
String CHANGE_MAILBOX_TEMPLATE_ID = "128210_change_mailbox_en.html";
|
||||
|
||||
/**
|
||||
* 发送登录相关的邮件
|
||||
*
|
||||
* @param receiverAddress 收件地址
|
||||
* @param ip 请求ip
|
||||
* @param templateId 模板ID名
|
||||
* @param verifyCode 验证码
|
||||
*/
|
||||
Boolean send(String receiverAddress, String ip, String templateId, String verifyCode);
|
||||
|
||||
/**
|
||||
* 发送试用订单相关的邮件
|
||||
*
|
||||
* @param receiverAddress 收件人邮箱地址
|
||||
* @param trialOrder 试用订单相关参数
|
||||
* @param emailType 邮件类型:1 - 提交试用请求,2 - 审批通过,3 - 试用请求通过通知
|
||||
* @param country 通过城市判断邮件模板的语言
|
||||
* @param link ?
|
||||
*/
|
||||
void sendCustomEmail(String receiverAddress, TrialOrder trialOrder, int emailType, String country, Boolean link);
|
||||
|
||||
/**
|
||||
* 发送昨日的试用订单用户数据
|
||||
*
|
||||
* @param receiverAddress 收件人地址
|
||||
* @param fileName 附件文件名
|
||||
* @param inputStreamSource 附件文件数据
|
||||
*/
|
||||
void sendExcelEmail(List<String> receiverAddress, String fileName, InputStreamSource inputStreamSource);
|
||||
|
||||
/**
|
||||
* 发送昨日的试用订单用户数据--无试用订单情况
|
||||
*
|
||||
* @param receiverAddress 收件人地址
|
||||
*/
|
||||
void sendNoExcelEmail(List<String> receiverAddress);
|
||||
|
||||
/**
|
||||
* 向账号快要到期的用户发送提醒邮件
|
||||
*
|
||||
* @param account 账号信息
|
||||
*/
|
||||
void sendWillBeExpiredEmail(Account account);
|
||||
|
||||
/**
|
||||
* 发送系统升级通知邮件
|
||||
*
|
||||
* @param account 用户信息
|
||||
* @param senderAddress 发件人邮件
|
||||
* @param type 邮件类型
|
||||
*/
|
||||
void sendUpgradeNotification(Account account, String senderAddress, Integer type);
|
||||
|
||||
/**
|
||||
* 通知在Code_Create上付费的用户,AiDA账号的更新
|
||||
*
|
||||
* @param receiverAddress 收件人地址
|
||||
* @param emailType 邮件类型
|
||||
* @param country 国家(确定发送邮件的语言)
|
||||
* @param userName 用户名
|
||||
* @param date 账号到期时间
|
||||
*/
|
||||
void notificationForPaidUser(String receiverAddress, int emailType, String country, String userName, String date);
|
||||
|
||||
/**
|
||||
* 广场用户注册通知邮件
|
||||
*
|
||||
* @param userEmail
|
||||
* @param randomVerifyCode
|
||||
* @return
|
||||
*/
|
||||
Boolean designWorksRegister(String userEmail, String randomVerifyCode);
|
||||
|
||||
void uploadTimeoutReminder(String userName, String time);
|
||||
|
||||
boolean subscriptionEmailReminder(String type, SubscriptionEmailParamsDTO subscriptionEmailParamsDTO, String language, String receiverAddress);
|
||||
|
||||
void affiliateEmailReminder(List<String> receiverAddress, AffiliateEmailParamsDTO paramsDTO, String type);
|
||||
|
||||
void creditsPurchaseReminder(String username, String quantity, String amount);
|
||||
|
||||
void commonExceptionReminder(String functionName, List<String> destination);
|
||||
}
|
||||
|
||||
@@ -1,388 +1,394 @@
|
||||
package com.ai.da.service.impl;
|
||||
|
||||
import com.ai.da.common.config.exception.BusinessException;
|
||||
import com.ai.da.common.constant.CommonConstant;
|
||||
import com.ai.da.common.context.UserContext;
|
||||
import com.ai.da.common.response.PageBaseResponse;
|
||||
import com.ai.da.common.response.ResultEnum;
|
||||
import com.ai.da.common.utils.CopyUtil;
|
||||
import com.ai.da.common.utils.RedisUtil;
|
||||
import com.ai.da.common.utils.SendEmailUtil;
|
||||
import com.ai.da.mapper.primary.AffiliateIncomeMapper;
|
||||
import com.ai.da.mapper.primary.AffiliateMapper;
|
||||
import com.ai.da.mapper.primary.ProductCouponsMapper;
|
||||
import com.ai.da.mapper.primary.SubscriptionInfoMapper;
|
||||
import com.ai.da.mapper.primary.entity.*;
|
||||
import com.ai.da.model.dto.AffiliateEmailParamsDTO;
|
||||
import com.ai.da.model.dto.AffiliateQueryDTO;
|
||||
import com.ai.da.model.vo.AffiliateInvitationDetailsVO;
|
||||
import com.ai.da.model.vo.AffiliateVO;
|
||||
import com.ai.da.model.vo.AuthPrincipalVo;
|
||||
import com.ai.da.service.*;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.mysql.cj.util.StringUtils;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate> implements AffiliateService {
|
||||
|
||||
@Resource
|
||||
private OrderInfoService orderInfoService;
|
||||
@Resource
|
||||
private AccountService accountService;
|
||||
@Resource
|
||||
private PaymentInfoService paymentInfoService;
|
||||
@Resource
|
||||
private SubscriptionInfoMapper subscriptionInfoMapper;
|
||||
@Resource
|
||||
private AffiliateIncomeMapper affiliateIncomeMapper;
|
||||
@Resource
|
||||
private StripeService stripeService;
|
||||
@Resource
|
||||
private ProductCouponsMapper productCouponsMapper;
|
||||
@Resource
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
// 推广者注册
|
||||
public Boolean registerAsAnAffiliate(String promotionMethod){
|
||||
AuthPrincipalVo userHolder = UserContext.getUserHolder();
|
||||
// 判断该用户是否已注册
|
||||
QueryWrapper<Affiliate> qw = new QueryWrapper<>();
|
||||
qw.eq("account_id", userHolder.getId());
|
||||
Affiliate affiliate = baseMapper.selectOne(qw);
|
||||
if (Objects.isNull(affiliate)){
|
||||
affiliate = new Affiliate();
|
||||
affiliate.setAccountId(userHolder.getId());
|
||||
affiliate.setStatus("Pending");
|
||||
affiliate.setCreateTime(LocalDateTime.now());
|
||||
affiliate.setPromotionMethod(promotionMethod);
|
||||
baseMapper.insert(affiliate);
|
||||
// 邮件通知审批者
|
||||
String merchantEmail = "kimwong@code-create.com.hk";
|
||||
String developer = "xupei3360@163.com";
|
||||
String[] receiverEmail = {merchantEmail, developer};
|
||||
SendEmailUtil.affiliateEmailReminder(receiverEmail, new AffiliateEmailParamsDTO(userHolder.getUsername(), promotionMethod), "new");
|
||||
}else {
|
||||
throw new BusinessException("You have registered an Affiliate", ResultEnum.PROMPT.getCode());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public PageBaseResponse<AffiliateVO> getAffiliateList(AffiliateQueryDTO affiliateQueryDTO){
|
||||
log.info("parameter => {}", affiliateQueryDTO.toString());
|
||||
|
||||
int offset = (affiliateQueryDTO.getPage() - 1) * affiliateQueryDTO.getSize();
|
||||
List<AffiliateVO> affiliateList = baseMapper.getAffiliateList(affiliateQueryDTO.getStatus(),
|
||||
affiliateQueryDTO.getStartTime(),
|
||||
affiliateQueryDTO.getEndTime(),
|
||||
affiliateQueryDTO.getOrder(),
|
||||
affiliateQueryDTO.getAffiliateId(),
|
||||
affiliateQueryDTO.getSize(),
|
||||
offset
|
||||
);
|
||||
if (CollectionUtils.isEmpty(affiliateList)) {
|
||||
return PageBaseResponse.success(new Page<>());
|
||||
}else {
|
||||
int totalCount = baseMapper.queryAffiliateTotalCount(affiliateQueryDTO.getStatus(),
|
||||
affiliateQueryDTO.getStartTime(),
|
||||
affiliateQueryDTO.getEndTime(),
|
||||
affiliateQueryDTO.getAffiliateId()
|
||||
);
|
||||
IPage<AffiliateVO> orderListVOIPage = new Page<>();
|
||||
Integer size = affiliateQueryDTO.getSize();
|
||||
orderListVOIPage.setSize(size);
|
||||
orderListVOIPage.setRecords(affiliateList);
|
||||
orderListVOIPage.setCurrent(affiliateQueryDTO.getPage());
|
||||
orderListVOIPage.setPages((long)Math.ceil((double) totalCount / size));
|
||||
orderListVOIPage.setTotal(totalCount);
|
||||
return PageBaseResponse.success(orderListVOIPage);
|
||||
}
|
||||
/*QueryWrapper<Affiliate> qw = new QueryWrapper<>();
|
||||
qw.eq(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getStatus()), "status", affiliateQueryDTO.getStatus())
|
||||
.gt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getStartTime()), "create_time", affiliateQueryDTO.getStartTime())
|
||||
.lt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getEndTime()), "create_time", affiliateQueryDTO.getEndTime())
|
||||
.eq(!Objects.isNull(affiliateQueryDTO.getAffiliateId()), "id", affiliateQueryDTO.getAffiliateId())
|
||||
.orderByDesc(affiliateQueryDTO.getOrder().equals("DESC"), "create_time");
|
||||
Page<Affiliate> affiliatePage = baseMapper.selectPage(new Page<>(affiliateQueryDTO.getPage(), affiliateQueryDTO.getSize()), qw);
|
||||
affiliatePage.convert((Function<Affiliate, AffiliateVO>) affiliate-> {
|
||||
AffiliateVO affiliateVO = CopyUtil.copyObject(affiliate, AffiliateVO.class);
|
||||
affiliateVO.setUsername();
|
||||
});
|
||||
return affiliatePage;*/
|
||||
}
|
||||
|
||||
public AffiliateVO personalAffiliateCenter(){
|
||||
QueryWrapper<Affiliate> qw = new QueryWrapper<>();
|
||||
Long accountId = UserContext.getUserHolder().getId();
|
||||
qw.eq("account_id", accountId);
|
||||
Affiliate affiliate = baseMapper.selectOne(qw);
|
||||
AffiliateVO affiliateVO = CopyUtil.copyObject(affiliate, AffiliateVO.class);
|
||||
affiliateVO.setLinkViewCount(getAffiliateLinkViewCount(affiliate.getId()));
|
||||
return affiliateVO;
|
||||
}
|
||||
|
||||
public double[] getPersonalMonthlyIncome(int year){
|
||||
Long accountId = UserContext.getUserHolder().getId();
|
||||
List<Map<String, Object>> personalMonthlyIncome = affiliateIncomeMapper.getPersonalMonthlyIncome(accountId, year);
|
||||
double[] commissions = new double[12];
|
||||
personalMonthlyIncome.forEach(income -> {
|
||||
int month = Integer.parseInt(income.get("yearMonth").toString());
|
||||
commissions[month-1] = (double)income.get("totalCommission");
|
||||
});
|
||||
|
||||
return commissions;
|
||||
}
|
||||
|
||||
// 审批申请
|
||||
public Boolean applicationApproval(Long id, Boolean isApproved, Float commission){
|
||||
Affiliate affiliate = baseMapper.selectById(id);
|
||||
|
||||
// 1、更新db状态
|
||||
if (isApproved){
|
||||
// 更新状态
|
||||
affiliate.setStatus("Active");
|
||||
affiliate.setApproved(true);
|
||||
affiliate.setLink(CommonConstant.AFFILIATE_LINK + affiliate.getId());
|
||||
if (Objects.isNull(commission)) {
|
||||
// 未设置佣金比例的情况下,默认25%
|
||||
affiliate.setCommissionPercent(25f);
|
||||
} else {
|
||||
affiliate.setCommissionPercent(commission);
|
||||
}
|
||||
} else {
|
||||
affiliate.setStatus("Refused");
|
||||
affiliate.setApproved(false);
|
||||
}
|
||||
affiliate.setUpdateTime(LocalDateTime.now());
|
||||
baseMapper.updateById(affiliate);
|
||||
|
||||
// 2、将批准结果邮件通知用户
|
||||
Account account = accountService.getById(affiliate.getAccountId());
|
||||
String[] userEmail = {account.getUserEmail()};
|
||||
String userName = account.getUserName();
|
||||
if (isApproved){
|
||||
SendEmailUtil.affiliateEmailReminder(userEmail, new AffiliateEmailParamsDTO(userName), "accepted");
|
||||
}else {
|
||||
SendEmailUtil.affiliateEmailReminder(userEmail, new AffiliateEmailParamsDTO(userName), "refused");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void updateCommissionPercentage(Long id, Float commission){
|
||||
Affiliate affiliate = baseMapper.selectById(id);
|
||||
if (Objects.isNull(affiliate)){
|
||||
log.info("未知affiliate id :{}", id);
|
||||
throw new BusinessException("unknown affiliate");
|
||||
}
|
||||
if (!Objects.equals(affiliate.getCommissionPercent(), commission)){
|
||||
affiliate.setCommissionPercent(commission);
|
||||
affiliate.setUpdateTime(LocalDateTime.now());
|
||||
baseMapper.updateById(affiliate);
|
||||
}
|
||||
}
|
||||
|
||||
// 定时计算佣金
|
||||
public void updateAffiliateInfoWithPayment(){
|
||||
// id存redis
|
||||
String lastTime = redisUtil.getFromString(RedisUtil.PAYMENT_INFO_LAST_SCAN_TIME);
|
||||
String currentTime = LocalDateTime.now().toString();
|
||||
// 1、查上次更新之后有无新订单
|
||||
QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();
|
||||
if (!StringUtil.isNullOrEmpty(lastTime)){
|
||||
queryWrapper.gt("create_time", lastTime);
|
||||
}
|
||||
queryWrapper.eq("type","new").eq("trade_state", "paid");
|
||||
|
||||
List<PaymentInfo> paymentInfos = paymentInfoService.getBaseMapper().selectList(queryWrapper);
|
||||
if (!paymentInfos.isEmpty()){
|
||||
paymentInfos.forEach(paymentInfo -> {
|
||||
// 2、根据order_no查付款用户id
|
||||
OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(paymentInfo.getOrderNo());
|
||||
if (Objects.isNull(orderInfo)){
|
||||
return;
|
||||
}
|
||||
Long accountId = orderInfo.getAccountId();
|
||||
// 3、查该用户之前是否有初次订阅的订单
|
||||
QueryWrapper<OrderInfo> qwOrderInfo = new QueryWrapper<>();
|
||||
qwOrderInfo.eq("account_id", accountId).eq("is_first_subscription", 1);
|
||||
List<OrderInfo> orderInfos = orderInfoService.getBaseMapper().selectList(qwOrderInfo);
|
||||
// 该用户首次订阅(非首次订阅,不分配佣金)
|
||||
if (orderInfos.isEmpty()){
|
||||
// 查询是否绑定affiliateId
|
||||
Account account = accountService.getById(accountId);
|
||||
if (!Objects.isNull(account.getInvitationCode())){
|
||||
log.info("结算订单id为{}的佣金", orderInfo.getId());
|
||||
// 3、若有, 直接更新affiliate的所得
|
||||
Affiliate affiliate = baseMapper.selectById(account.getInvitationCode());
|
||||
Float payerTotal = paymentInfo.getPayerTotal();
|
||||
|
||||
if (payerTotal > 0){
|
||||
// 分配新用户首次订阅所付费用 预设的佣金比例 作为佣金
|
||||
BigDecimal commission = BigDecimal.valueOf(payerTotal).multiply(BigDecimal.valueOf(affiliate.getCommissionPercent() / 100));
|
||||
BigDecimal monthlyEarning = BigDecimal.valueOf(affiliate.getMonthlyEarnings()).add(commission);
|
||||
BigDecimal unpaidEarnings = BigDecimal.valueOf(affiliate.getUnpaidEarnings()).add(commission);
|
||||
int visits = affiliate.getVisits() + 1;
|
||||
affiliate.setMonthlyEarnings(monthlyEarning.floatValue());
|
||||
affiliate.setUnpaidEarnings(unpaidEarnings.floatValue());
|
||||
affiliate.setVisits(visits);
|
||||
affiliate.setUpdateTime(LocalDateTime.now());
|
||||
baseMapper.updateById(affiliate);
|
||||
|
||||
orderInfo.setIsCommissionCalculated((byte)1);
|
||||
|
||||
// 4、添加到t_affiliate_income
|
||||
AffiliateIncome affiliateIncome = new AffiliateIncome();
|
||||
affiliateIncome.setAffiliateId(affiliate.getId());
|
||||
affiliateIncome.setAffiliateAccountId(affiliate.getAccountId());
|
||||
affiliateIncome.setInviteeAccountId(accountId);
|
||||
affiliateIncome.setAmount(payerTotal);
|
||||
affiliateIncome.setPaymentInfoId(paymentInfo.getId());
|
||||
affiliateIncome.setPaymentTime(paymentInfo.getCreateTime());
|
||||
affiliateIncome.setCommission(commission.floatValue());
|
||||
affiliateIncome.setCreateTime(LocalDateTime.now());
|
||||
affiliateIncomeMapper.insert(affiliateIncome);
|
||||
}
|
||||
}
|
||||
orderInfo.setIsFirstSubscription((byte)1);
|
||||
orderInfo.setUpdateTime(LocalDateTime.now());
|
||||
orderInfoService.updateById(orderInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
redisUtil.addToString(RedisUtil.PAYMENT_INFO_LAST_SCAN_TIME, currentTime);
|
||||
}
|
||||
|
||||
public Boolean affiliateLinkViewsIncrease(Long affiliateId) {
|
||||
redisUtil.increaseAffiliateLinkViewCount(affiliateId);
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
private Long getAffiliateLinkViewCount(Long affiliateId) {
|
||||
return redisUtil.getAffiliateLinkViewCount(affiliateId);
|
||||
}
|
||||
|
||||
// 查看每个affiliate带来收入的详情
|
||||
@Override
|
||||
public IPage<AffiliateInvitationDetailsVO> getEachAffiliateGeneratedRevenue(AffiliateQueryDTO affiliateQueryDTO) {
|
||||
if (Objects.isNull(affiliateQueryDTO.getAffiliateId())){
|
||||
throw new BusinessException("Please specify the affiliate ID.", ResultEnum.PROMPT.getCode());
|
||||
}
|
||||
|
||||
QueryWrapper<AffiliateIncome> affiliateIncomeQueryWrapper = new QueryWrapper<>();
|
||||
affiliateIncomeQueryWrapper
|
||||
.gt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getStartTime()), "create_time", affiliateQueryDTO.getStartTime())
|
||||
.lt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getEndTime()), "create_time", affiliateQueryDTO.getEndTime())
|
||||
.eq(!Objects.isNull(affiliateQueryDTO.getAffiliateId()), "affiliate_id", affiliateQueryDTO.getAffiliateId())
|
||||
.orderByDesc(affiliateQueryDTO.getOrder().equals("DESC"), "create_time");
|
||||
IPage<AffiliateIncome> affiliateIncomePage = affiliateIncomeMapper.selectPage(new Page<>(affiliateQueryDTO.getPage(), affiliateQueryDTO.getSize()), affiliateIncomeQueryWrapper);
|
||||
return affiliateIncomePage.convert((Function<AffiliateIncome, AffiliateInvitationDetailsVO>) affiliateIncome -> {
|
||||
AffiliateInvitationDetailsVO affiliateInvitationDetailsVO = CopyUtil.copyObject(affiliateIncome, AffiliateInvitationDetailsVO.class);
|
||||
affiliateInvitationDetailsVO.setAccountId(affiliateIncome.getInviteeAccountId());
|
||||
affiliateInvitationDetailsVO.setUsername(accountService.getBaseMapper().selectById(affiliateIncome.getInviteeAccountId()).getUserName());
|
||||
affiliateInvitationDetailsVO.setFirstSubscriptionPaymentAmount(affiliateIncome.getAmount());
|
||||
affiliateInvitationDetailsVO.setCommission(affiliateIncome.getCommission());
|
||||
affiliateInvitationDetailsVO.setTime(affiliateIncome.getPaymentTime());
|
||||
return affiliateInvitationDetailsVO;
|
||||
});
|
||||
}
|
||||
|
||||
public void commissionCalculation(Integer year, Integer month) {
|
||||
if (Objects.isNull(year)) {
|
||||
year = LocalDateTime.now().getYear();
|
||||
}
|
||||
if (Objects.isNull(month)) {
|
||||
month = LocalDateTime.now().getMonthValue();
|
||||
}
|
||||
|
||||
List<Map<String, Object>> monthlyAffiliateIncome = affiliateIncomeMapper.getMonthlyAffiliateIncome(year, month);
|
||||
// 1、总收入(近一个月通过affiliate产生的收入),未支付的金额 affiliate表中unpaid的总和
|
||||
Double totalAmount = 0.0;
|
||||
Double totalCommission = 0.0;
|
||||
if (!monthlyAffiliateIncome.isEmpty()){
|
||||
Map<String, Object> monthlyIncome = monthlyAffiliateIncome.get(0);
|
||||
totalAmount = (Double) monthlyIncome.get("totalAmount");
|
||||
totalCommission = (Double) monthlyIncome.get("totalCommission");
|
||||
}
|
||||
|
||||
// 2、本月新注册的Affiliate
|
||||
Map<String, Long> monthlyApprovedAffiliate = baseMapper.getMonthlyApprovedAffiliate(year, month);
|
||||
Long count = monthlyApprovedAffiliate.get("count");
|
||||
|
||||
AffiliateEmailParamsDTO affiliateEmailParamsDTO = new AffiliateEmailParamsDTO();
|
||||
affiliateEmailParamsDTO.setTotalProgramRevenue(totalAmount.toString());
|
||||
affiliateEmailParamsDTO.setNewApprovedAffiliates(count.toString());
|
||||
affiliateEmailParamsDTO.setUnpaidEarnings(totalCommission.toString());
|
||||
affiliateEmailParamsDTO.setPaidEarnings("0");
|
||||
|
||||
String merchantEmail = "kimwong@code-create.com.hk";
|
||||
String developer = "xupei3360@163.com";
|
||||
String[] receiverEmail = {merchantEmail, developer};
|
||||
// 邮件通知
|
||||
SendEmailUtil.affiliateEmailReminder(receiverEmail, affiliateEmailParamsDTO, "summary");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Affiliate getByAccountId(Long accountId) {
|
||||
QueryWrapper<Affiliate> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("account_id", accountId).orderByDesc("id").last("limit 1");
|
||||
|
||||
return baseMapper.selectOne(queryWrapper);
|
||||
}
|
||||
|
||||
public void calcCouponsCommission(){
|
||||
// id存redis
|
||||
String lastTime = redisUtil.getFromString(RedisUtil.PAYMENT_INFO_LAST_SCAN_TIME);
|
||||
String currentTime = LocalDateTime.now().toString();
|
||||
// 1、查上次更新之后有无使用了优惠券的新订单
|
||||
QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();
|
||||
if (!StringUtil.isNullOrEmpty(lastTime)){
|
||||
queryWrapper.gt("create_time", lastTime)
|
||||
.lt("create_time", currentTime)
|
||||
.isNotNull("promotion_code");
|
||||
}
|
||||
List<PaymentInfo> paymentInfos = paymentInfoService.getBaseMapper().selectList(queryWrapper);
|
||||
|
||||
// key:推广码, value:用户支付的金额
|
||||
HashMap<String, Float> codeAmount = new HashMap<>();
|
||||
if (!paymentInfos.isEmpty()){
|
||||
for (PaymentInfo paymentInfo : paymentInfos){
|
||||
String promotionCode = paymentInfo.getPromotionCode();
|
||||
Float sum = codeAmount.get(promotionCode);
|
||||
if (sum == null || sum == 0.0f){
|
||||
codeAmount.put(promotionCode, paymentInfo.getPayerTotal());
|
||||
}else {
|
||||
codeAmount.put(promotionCode, sum + paymentInfo.getPayerTotal());
|
||||
}
|
||||
}
|
||||
for (Map.Entry<String, Float> entry : codeAmount.entrySet()){
|
||||
String promotionCode = entry.getKey();
|
||||
ProductCoupons productCoupons = stripeService.getProductCoupon(promotionCode, null);
|
||||
if (!Objects.isNull(productCoupons)){
|
||||
// 2、计算支付金额的总和,更新totalEarnings,commission,unpaidCommission
|
||||
float sum = productCoupons.getTotalEarnings() + entry.getValue();
|
||||
productCoupons.setTotalEarnings(sum);
|
||||
float commission = sum * productCoupons.getCommissionRate() / 100;
|
||||
productCoupons.setCommission(commission);
|
||||
productCoupons.setUnpaidCommission(commission - productCoupons.getPaidCommission());
|
||||
productCouponsMapper.updateById(productCoupons);
|
||||
}
|
||||
}
|
||||
}
|
||||
redisUtil.addToString(RedisUtil.PAYMENT_INFO_LAST_SCAN_TIME, currentTime);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
package com.ai.da.service.impl;
|
||||
|
||||
import com.ai.da.common.config.exception.BusinessException;
|
||||
import com.ai.da.common.constant.CommonConstant;
|
||||
import com.ai.da.common.context.UserContext;
|
||||
import com.ai.da.common.response.PageBaseResponse;
|
||||
import com.ai.da.common.response.ResultEnum;
|
||||
import com.ai.da.common.utils.CopyUtil;
|
||||
import com.ai.da.common.utils.RedisUtil;
|
||||
import com.ai.da.common.utils.SendEmailUtil;
|
||||
import com.ai.da.mapper.primary.AffiliateIncomeMapper;
|
||||
import com.ai.da.mapper.primary.AffiliateMapper;
|
||||
import com.ai.da.mapper.primary.ProductCouponsMapper;
|
||||
import com.ai.da.mapper.primary.SubscriptionInfoMapper;
|
||||
import com.ai.da.mapper.primary.entity.*;
|
||||
import com.ai.da.model.dto.AffiliateEmailParamsDTO;
|
||||
import com.ai.da.model.dto.AffiliateQueryDTO;
|
||||
import com.ai.da.model.vo.AffiliateInvitationDetailsVO;
|
||||
import com.ai.da.model.vo.AffiliateVO;
|
||||
import com.ai.da.model.vo.AuthPrincipalVo;
|
||||
import com.ai.da.service.*;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.mysql.cj.util.StringUtils;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate> implements AffiliateService {
|
||||
|
||||
@Resource
|
||||
private OrderInfoService orderInfoService;
|
||||
@Resource
|
||||
private AccountService accountService;
|
||||
@Resource
|
||||
private PaymentInfoService paymentInfoService;
|
||||
@Resource
|
||||
private SubscriptionInfoMapper subscriptionInfoMapper;
|
||||
@Resource
|
||||
private AffiliateIncomeMapper affiliateIncomeMapper;
|
||||
@Resource
|
||||
private StripeService stripeService;
|
||||
@Resource
|
||||
private ProductCouponsMapper productCouponsMapper;
|
||||
@Resource
|
||||
private RedisUtil redisUtil;
|
||||
@Resource
|
||||
private EmailService emailService;
|
||||
|
||||
// 推广者注册
|
||||
public Boolean registerAsAnAffiliate(String promotionMethod){
|
||||
AuthPrincipalVo userHolder = UserContext.getUserHolder();
|
||||
// 判断该用户是否已注册
|
||||
QueryWrapper<Affiliate> qw = new QueryWrapper<>();
|
||||
qw.eq("account_id", userHolder.getId());
|
||||
Affiliate affiliate = baseMapper.selectOne(qw);
|
||||
if (Objects.isNull(affiliate)){
|
||||
affiliate = new Affiliate();
|
||||
affiliate.setAccountId(userHolder.getId());
|
||||
affiliate.setStatus("Pending");
|
||||
affiliate.setCreateTime(LocalDateTime.now());
|
||||
affiliate.setPromotionMethod(promotionMethod);
|
||||
baseMapper.insert(affiliate);
|
||||
// 邮件通知审批者
|
||||
String merchantEmail = "kimwong@code-create.com.hk";
|
||||
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());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public PageBaseResponse<AffiliateVO> getAffiliateList(AffiliateQueryDTO affiliateQueryDTO){
|
||||
log.info("parameter => {}", affiliateQueryDTO.toString());
|
||||
|
||||
int offset = (affiliateQueryDTO.getPage() - 1) * affiliateQueryDTO.getSize();
|
||||
List<AffiliateVO> affiliateList = baseMapper.getAffiliateList(affiliateQueryDTO.getStatus(),
|
||||
affiliateQueryDTO.getStartTime(),
|
||||
affiliateQueryDTO.getEndTime(),
|
||||
affiliateQueryDTO.getOrder(),
|
||||
affiliateQueryDTO.getAffiliateId(),
|
||||
affiliateQueryDTO.getSize(),
|
||||
offset
|
||||
);
|
||||
if (CollectionUtils.isEmpty(affiliateList)) {
|
||||
return PageBaseResponse.success(new Page<>());
|
||||
}else {
|
||||
int totalCount = baseMapper.queryAffiliateTotalCount(affiliateQueryDTO.getStatus(),
|
||||
affiliateQueryDTO.getStartTime(),
|
||||
affiliateQueryDTO.getEndTime(),
|
||||
affiliateQueryDTO.getAffiliateId()
|
||||
);
|
||||
IPage<AffiliateVO> orderListVOIPage = new Page<>();
|
||||
Integer size = affiliateQueryDTO.getSize();
|
||||
orderListVOIPage.setSize(size);
|
||||
orderListVOIPage.setRecords(affiliateList);
|
||||
orderListVOIPage.setCurrent(affiliateQueryDTO.getPage());
|
||||
orderListVOIPage.setPages((long)Math.ceil((double) totalCount / size));
|
||||
orderListVOIPage.setTotal(totalCount);
|
||||
return PageBaseResponse.success(orderListVOIPage);
|
||||
}
|
||||
/*QueryWrapper<Affiliate> qw = new QueryWrapper<>();
|
||||
qw.eq(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getStatus()), "status", affiliateQueryDTO.getStatus())
|
||||
.gt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getStartTime()), "create_time", affiliateQueryDTO.getStartTime())
|
||||
.lt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getEndTime()), "create_time", affiliateQueryDTO.getEndTime())
|
||||
.eq(!Objects.isNull(affiliateQueryDTO.getAffiliateId()), "id", affiliateQueryDTO.getAffiliateId())
|
||||
.orderByDesc(affiliateQueryDTO.getOrder().equals("DESC"), "create_time");
|
||||
Page<Affiliate> affiliatePage = baseMapper.selectPage(new Page<>(affiliateQueryDTO.getPage(), affiliateQueryDTO.getSize()), qw);
|
||||
affiliatePage.convert((Function<Affiliate, AffiliateVO>) affiliate-> {
|
||||
AffiliateVO affiliateVO = CopyUtil.copyObject(affiliate, AffiliateVO.class);
|
||||
affiliateVO.setUsername();
|
||||
});
|
||||
return affiliatePage;*/
|
||||
}
|
||||
|
||||
public AffiliateVO personalAffiliateCenter(){
|
||||
QueryWrapper<Affiliate> qw = new QueryWrapper<>();
|
||||
Long accountId = UserContext.getUserHolder().getId();
|
||||
qw.eq("account_id", accountId);
|
||||
Affiliate affiliate = baseMapper.selectOne(qw);
|
||||
AffiliateVO affiliateVO = CopyUtil.copyObject(affiliate, AffiliateVO.class);
|
||||
affiliateVO.setLinkViewCount(getAffiliateLinkViewCount(affiliate.getId()));
|
||||
return affiliateVO;
|
||||
}
|
||||
|
||||
public double[] getPersonalMonthlyIncome(int year){
|
||||
Long accountId = UserContext.getUserHolder().getId();
|
||||
List<Map<String, Object>> personalMonthlyIncome = affiliateIncomeMapper.getPersonalMonthlyIncome(accountId, year);
|
||||
double[] commissions = new double[12];
|
||||
personalMonthlyIncome.forEach(income -> {
|
||||
int month = Integer.parseInt(income.get("yearMonth").toString());
|
||||
commissions[month-1] = (double)income.get("totalCommission");
|
||||
});
|
||||
|
||||
return commissions;
|
||||
}
|
||||
|
||||
// 审批申请
|
||||
public Boolean applicationApproval(Long id, Boolean isApproved, Float commission){
|
||||
Affiliate affiliate = baseMapper.selectById(id);
|
||||
|
||||
// 1、更新db状态
|
||||
if (isApproved){
|
||||
// 更新状态
|
||||
affiliate.setStatus("Active");
|
||||
affiliate.setApproved(true);
|
||||
affiliate.setLink(CommonConstant.AFFILIATE_LINK + affiliate.getId());
|
||||
if (Objects.isNull(commission)) {
|
||||
// 未设置佣金比例的情况下,默认25%
|
||||
affiliate.setCommissionPercent(25f);
|
||||
} else {
|
||||
affiliate.setCommissionPercent(commission);
|
||||
}
|
||||
} else {
|
||||
affiliate.setStatus("Refused");
|
||||
affiliate.setApproved(false);
|
||||
}
|
||||
affiliate.setUpdateTime(LocalDateTime.now());
|
||||
baseMapper.updateById(affiliate);
|
||||
|
||||
// 2、将批准结果邮件通知用户
|
||||
Account account = accountService.getById(affiliate.getAccountId());
|
||||
String[] userEmail = {account.getUserEmail()};
|
||||
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;
|
||||
}
|
||||
|
||||
public void updateCommissionPercentage(Long id, Float commission){
|
||||
Affiliate affiliate = baseMapper.selectById(id);
|
||||
if (Objects.isNull(affiliate)){
|
||||
log.info("未知affiliate id :{}", id);
|
||||
throw new BusinessException("unknown affiliate");
|
||||
}
|
||||
if (!Objects.equals(affiliate.getCommissionPercent(), commission)){
|
||||
affiliate.setCommissionPercent(commission);
|
||||
affiliate.setUpdateTime(LocalDateTime.now());
|
||||
baseMapper.updateById(affiliate);
|
||||
}
|
||||
}
|
||||
|
||||
// 定时计算佣金
|
||||
public void updateAffiliateInfoWithPayment(){
|
||||
// id存redis
|
||||
String lastTime = redisUtil.getFromString(RedisUtil.PAYMENT_INFO_LAST_SCAN_TIME);
|
||||
String currentTime = LocalDateTime.now().toString();
|
||||
// 1、查上次更新之后有无新订单
|
||||
QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();
|
||||
if (!StringUtil.isNullOrEmpty(lastTime)){
|
||||
queryWrapper.gt("create_time", lastTime);
|
||||
}
|
||||
queryWrapper.eq("type","new").eq("trade_state", "paid");
|
||||
|
||||
List<PaymentInfo> paymentInfos = paymentInfoService.getBaseMapper().selectList(queryWrapper);
|
||||
if (!paymentInfos.isEmpty()){
|
||||
paymentInfos.forEach(paymentInfo -> {
|
||||
// 2、根据order_no查付款用户id
|
||||
OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(paymentInfo.getOrderNo());
|
||||
if (Objects.isNull(orderInfo)){
|
||||
return;
|
||||
}
|
||||
Long accountId = orderInfo.getAccountId();
|
||||
// 3、查该用户之前是否有初次订阅的订单
|
||||
QueryWrapper<OrderInfo> qwOrderInfo = new QueryWrapper<>();
|
||||
qwOrderInfo.eq("account_id", accountId).eq("is_first_subscription", 1);
|
||||
List<OrderInfo> orderInfos = orderInfoService.getBaseMapper().selectList(qwOrderInfo);
|
||||
// 该用户首次订阅(非首次订阅,不分配佣金)
|
||||
if (orderInfos.isEmpty()){
|
||||
// 查询是否绑定affiliateId
|
||||
Account account = accountService.getById(accountId);
|
||||
if (!Objects.isNull(account.getInvitationCode())){
|
||||
log.info("结算订单id为{}的佣金", orderInfo.getId());
|
||||
// 3、若有, 直接更新affiliate的所得
|
||||
Affiliate affiliate = baseMapper.selectById(account.getInvitationCode());
|
||||
Float payerTotal = paymentInfo.getPayerTotal();
|
||||
|
||||
if (payerTotal > 0){
|
||||
// 分配新用户首次订阅所付费用 预设的佣金比例 作为佣金
|
||||
BigDecimal commission = BigDecimal.valueOf(payerTotal).multiply(BigDecimal.valueOf(affiliate.getCommissionPercent() / 100));
|
||||
BigDecimal monthlyEarning = BigDecimal.valueOf(affiliate.getMonthlyEarnings()).add(commission);
|
||||
BigDecimal unpaidEarnings = BigDecimal.valueOf(affiliate.getUnpaidEarnings()).add(commission);
|
||||
int visits = affiliate.getVisits() + 1;
|
||||
affiliate.setMonthlyEarnings(monthlyEarning.floatValue());
|
||||
affiliate.setUnpaidEarnings(unpaidEarnings.floatValue());
|
||||
affiliate.setVisits(visits);
|
||||
affiliate.setUpdateTime(LocalDateTime.now());
|
||||
baseMapper.updateById(affiliate);
|
||||
|
||||
orderInfo.setIsCommissionCalculated((byte)1);
|
||||
|
||||
// 4、添加到t_affiliate_income
|
||||
AffiliateIncome affiliateIncome = new AffiliateIncome();
|
||||
affiliateIncome.setAffiliateId(affiliate.getId());
|
||||
affiliateIncome.setAffiliateAccountId(affiliate.getAccountId());
|
||||
affiliateIncome.setInviteeAccountId(accountId);
|
||||
affiliateIncome.setAmount(payerTotal);
|
||||
affiliateIncome.setPaymentInfoId(paymentInfo.getId());
|
||||
affiliateIncome.setPaymentTime(paymentInfo.getCreateTime());
|
||||
affiliateIncome.setCommission(commission.floatValue());
|
||||
affiliateIncome.setCreateTime(LocalDateTime.now());
|
||||
affiliateIncomeMapper.insert(affiliateIncome);
|
||||
}
|
||||
}
|
||||
orderInfo.setIsFirstSubscription((byte)1);
|
||||
orderInfo.setUpdateTime(LocalDateTime.now());
|
||||
orderInfoService.updateById(orderInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
redisUtil.addToString(RedisUtil.PAYMENT_INFO_LAST_SCAN_TIME, currentTime);
|
||||
}
|
||||
|
||||
public Boolean affiliateLinkViewsIncrease(Long affiliateId) {
|
||||
redisUtil.increaseAffiliateLinkViewCount(affiliateId);
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
private Long getAffiliateLinkViewCount(Long affiliateId) {
|
||||
return redisUtil.getAffiliateLinkViewCount(affiliateId);
|
||||
}
|
||||
|
||||
// 查看每个affiliate带来收入的详情
|
||||
@Override
|
||||
public IPage<AffiliateInvitationDetailsVO> getEachAffiliateGeneratedRevenue(AffiliateQueryDTO affiliateQueryDTO) {
|
||||
if (Objects.isNull(affiliateQueryDTO.getAffiliateId())){
|
||||
throw new BusinessException("Please specify the affiliate ID.", ResultEnum.PROMPT.getCode());
|
||||
}
|
||||
|
||||
QueryWrapper<AffiliateIncome> affiliateIncomeQueryWrapper = new QueryWrapper<>();
|
||||
affiliateIncomeQueryWrapper
|
||||
.gt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getStartTime()), "create_time", affiliateQueryDTO.getStartTime())
|
||||
.lt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getEndTime()), "create_time", affiliateQueryDTO.getEndTime())
|
||||
.eq(!Objects.isNull(affiliateQueryDTO.getAffiliateId()), "affiliate_id", affiliateQueryDTO.getAffiliateId())
|
||||
.orderByDesc(affiliateQueryDTO.getOrder().equals("DESC"), "create_time");
|
||||
IPage<AffiliateIncome> affiliateIncomePage = affiliateIncomeMapper.selectPage(new Page<>(affiliateQueryDTO.getPage(), affiliateQueryDTO.getSize()), affiliateIncomeQueryWrapper);
|
||||
return affiliateIncomePage.convert((Function<AffiliateIncome, AffiliateInvitationDetailsVO>) affiliateIncome -> {
|
||||
AffiliateInvitationDetailsVO affiliateInvitationDetailsVO = CopyUtil.copyObject(affiliateIncome, AffiliateInvitationDetailsVO.class);
|
||||
affiliateInvitationDetailsVO.setAccountId(affiliateIncome.getInviteeAccountId());
|
||||
affiliateInvitationDetailsVO.setUsername(accountService.getBaseMapper().selectById(affiliateIncome.getInviteeAccountId()).getUserName());
|
||||
affiliateInvitationDetailsVO.setFirstSubscriptionPaymentAmount(affiliateIncome.getAmount());
|
||||
affiliateInvitationDetailsVO.setCommission(affiliateIncome.getCommission());
|
||||
affiliateInvitationDetailsVO.setTime(affiliateIncome.getPaymentTime());
|
||||
return affiliateInvitationDetailsVO;
|
||||
});
|
||||
}
|
||||
|
||||
public void commissionCalculation(Integer year, Integer month) {
|
||||
if (Objects.isNull(year)) {
|
||||
year = LocalDateTime.now().getYear();
|
||||
}
|
||||
if (Objects.isNull(month)) {
|
||||
month = LocalDateTime.now().getMonthValue();
|
||||
}
|
||||
|
||||
List<Map<String, Object>> monthlyAffiliateIncome = affiliateIncomeMapper.getMonthlyAffiliateIncome(year, month);
|
||||
// 1、总收入(近一个月通过affiliate产生的收入),未支付的金额 affiliate表中unpaid的总和
|
||||
Double totalAmount = 0.0;
|
||||
Double totalCommission = 0.0;
|
||||
if (!monthlyAffiliateIncome.isEmpty()){
|
||||
Map<String, Object> monthlyIncome = monthlyAffiliateIncome.get(0);
|
||||
totalAmount = (Double) monthlyIncome.get("totalAmount");
|
||||
totalCommission = (Double) monthlyIncome.get("totalCommission");
|
||||
}
|
||||
|
||||
// 2、本月新注册的Affiliate
|
||||
Map<String, Long> monthlyApprovedAffiliate = baseMapper.getMonthlyApprovedAffiliate(year, month);
|
||||
Long count = monthlyApprovedAffiliate.get("count");
|
||||
|
||||
AffiliateEmailParamsDTO affiliateEmailParamsDTO = new AffiliateEmailParamsDTO();
|
||||
affiliateEmailParamsDTO.setTotalProgramRevenue(totalAmount.toString());
|
||||
affiliateEmailParamsDTO.setNewApprovedAffiliates(count.toString());
|
||||
affiliateEmailParamsDTO.setUnpaidEarnings(totalCommission.toString());
|
||||
affiliateEmailParamsDTO.setPaidEarnings("0");
|
||||
|
||||
String merchantEmail = "kimwong@code-create.com.hk";
|
||||
String developer = "xupei3360@163.com";
|
||||
String[] receiverEmail = {merchantEmail, developer};
|
||||
// 邮件通知
|
||||
SendEmailUtil.affiliateEmailReminder(receiverEmail, affiliateEmailParamsDTO, "summary");
|
||||
// emailService.affiliateEmailReminder(Arrays.asList(/*merchantEmail,*/ developer), affiliateEmailParamsDTO, "summary");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Affiliate getByAccountId(Long accountId) {
|
||||
QueryWrapper<Affiliate> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("account_id", accountId).orderByDesc("id").last("limit 1");
|
||||
|
||||
return baseMapper.selectOne(queryWrapper);
|
||||
}
|
||||
|
||||
public void calcCouponsCommission(){
|
||||
// id存redis
|
||||
String lastTime = redisUtil.getFromString(RedisUtil.PAYMENT_INFO_LAST_SCAN_TIME);
|
||||
String currentTime = LocalDateTime.now().toString();
|
||||
// 1、查上次更新之后有无使用了优惠券的新订单
|
||||
QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();
|
||||
if (!StringUtil.isNullOrEmpty(lastTime)){
|
||||
queryWrapper.gt("create_time", lastTime)
|
||||
.lt("create_time", currentTime)
|
||||
.isNotNull("promotion_code");
|
||||
}
|
||||
List<PaymentInfo> paymentInfos = paymentInfoService.getBaseMapper().selectList(queryWrapper);
|
||||
|
||||
// key:推广码, value:用户支付的金额
|
||||
HashMap<String, Float> codeAmount = new HashMap<>();
|
||||
if (!paymentInfos.isEmpty()){
|
||||
for (PaymentInfo paymentInfo : paymentInfos){
|
||||
String promotionCode = paymentInfo.getPromotionCode();
|
||||
Float sum = codeAmount.get(promotionCode);
|
||||
if (sum == null || sum == 0.0f){
|
||||
codeAmount.put(promotionCode, paymentInfo.getPayerTotal());
|
||||
}else {
|
||||
codeAmount.put(promotionCode, sum + paymentInfo.getPayerTotal());
|
||||
}
|
||||
}
|
||||
for (Map.Entry<String, Float> entry : codeAmount.entrySet()){
|
||||
String promotionCode = entry.getKey();
|
||||
ProductCoupons productCoupons = stripeService.getProductCoupon(promotionCode, null);
|
||||
if (!Objects.isNull(productCoupons)){
|
||||
// 2、计算支付金额的总和,更新totalEarnings,commission,unpaidCommission
|
||||
float sum = productCoupons.getTotalEarnings() + entry.getValue();
|
||||
productCoupons.setTotalEarnings(sum);
|
||||
float commission = sum * productCoupons.getCommissionRate() / 100;
|
||||
productCoupons.setCommission(commission);
|
||||
productCoupons.setUnpaidCommission(commission - productCoupons.getPaidCommission());
|
||||
productCouponsMapper.updateById(productCoupons);
|
||||
}
|
||||
}
|
||||
}
|
||||
redisUtil.addToString(RedisUtil.PAYMENT_INFO_LAST_SCAN_TIME, currentTime);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user