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

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

View File

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

View File

@@ -1,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());
}
}

View File

@@ -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;
}
);
}
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

@@ -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、计算支付金额的总和更新totalEarningscommissionunpaidCommission
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、计算支付金额的总和更新totalEarningscommissionunpaidCommission
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