From b18a5a00506c1b285cc18041b5bafac900b40f68 Mon Sep 17 00:00:00 2001 From: xupei Date: Tue, 25 Mar 2025 11:22:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A8=A1=E7=89=B9=E6=AF=94=E4=BE=8B=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E3=80=81sketch=E6=8B=BC=E8=B4=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ai/da/common/utils/MailUtil.java | 168 +++++ .../com/ai/da/controller/EmailController.java | 43 ++ .../ai/da/mapper/primary/EmailLogMapper.java | 7 + .../mapper/primary/EmailTemplateMapper.java | 7 + .../ai/da/mapper/primary/entity/EmailLog.java | 35 + .../mapper/primary/entity/EmailTemplate.java | 24 + .../ai/da/model/dto/BasicEmailParamDTO.java | 29 + .../java/com/ai/da/service/EmailService.java | 138 ++++ .../ai/da/service/impl/EmailServiceImpl.java | 664 ++++++++++++++++++ 9 files changed, 1115 insertions(+) create mode 100644 src/main/java/com/ai/da/common/utils/MailUtil.java create mode 100644 src/main/java/com/ai/da/controller/EmailController.java create mode 100644 src/main/java/com/ai/da/mapper/primary/EmailLogMapper.java create mode 100644 src/main/java/com/ai/da/mapper/primary/EmailTemplateMapper.java create mode 100644 src/main/java/com/ai/da/mapper/primary/entity/EmailLog.java create mode 100644 src/main/java/com/ai/da/mapper/primary/entity/EmailTemplate.java create mode 100644 src/main/java/com/ai/da/model/dto/BasicEmailParamDTO.java create mode 100644 src/main/java/com/ai/da/service/EmailService.java create mode 100644 src/main/java/com/ai/da/service/impl/EmailServiceImpl.java diff --git a/src/main/java/com/ai/da/common/utils/MailUtil.java b/src/main/java/com/ai/da/common/utils/MailUtil.java new file mode 100644 index 00000000..cd5882ec --- /dev/null +++ b/src/main/java/com/ai/da/common/utils/MailUtil.java @@ -0,0 +1,168 @@ +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 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 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); + } + +} diff --git a/src/main/java/com/ai/da/controller/EmailController.java b/src/main/java/com/ai/da/controller/EmailController.java new file mode 100644 index 00000000..f7424937 --- /dev/null +++ b/src/main/java/com/ai/da/controller/EmailController.java @@ -0,0 +1,43 @@ +package com.ai.da.controller; + +import com.ai.da.model.dto.BasicEmailParamDTO; +import com.ai.da.service.EmailService; +import com.alibaba.fastjson.JSONObject; +import io.swagger.annotations.Api; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.thymeleaf.context.Context; + +import javax.annotation.Resource; +import java.util.Collections; + +@Api(tags = "邮件模块") +@Slf4j +@RestController +@RequestMapping("/api/email") +public class EmailController { + + @Resource + private EmailService emailService; + + @GetMapping("/loadSingleTemplate") + public void loadSingleEmailTemplate(){ + emailService.loadSingleEmailTemplate("templates\\upgrade\\122899_AiDA发版完成通知中文版.html"); + } + + @GetMapping("/loadTemplate") + public void loadTemplatesFromResources(){ + emailService.loadTemplatesFromResources("templates"); + } + + @GetMapping("/sendEmailTest") + public void sendEmailTest(){ + Context context = new Context(); + context.setVariable("username", "小白"); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("username", "小白"); + emailService.sendEmail(Collections.singletonList("xupei3360@163.com"), jsonObject, "132124_affiliate_accepted_en.html", "测试邮件", null, null ); + } +} diff --git a/src/main/java/com/ai/da/mapper/primary/EmailLogMapper.java b/src/main/java/com/ai/da/mapper/primary/EmailLogMapper.java new file mode 100644 index 00000000..8bdeabc9 --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/EmailLogMapper.java @@ -0,0 +1,7 @@ +package com.ai.da.mapper.primary; + +import com.ai.da.common.config.mybatis.plus.CommonMapper; +import com.ai.da.mapper.primary.entity.EmailLog; + +public interface EmailLogMapper extends CommonMapper { +} diff --git a/src/main/java/com/ai/da/mapper/primary/EmailTemplateMapper.java b/src/main/java/com/ai/da/mapper/primary/EmailTemplateMapper.java new file mode 100644 index 00000000..570cc302 --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/EmailTemplateMapper.java @@ -0,0 +1,7 @@ +package com.ai.da.mapper.primary; + +import com.ai.da.common.config.mybatis.plus.CommonMapper; +import com.ai.da.mapper.primary.entity.EmailTemplate; + +public interface EmailTemplateMapper extends CommonMapper { +} diff --git a/src/main/java/com/ai/da/mapper/primary/entity/EmailLog.java b/src/main/java/com/ai/da/mapper/primary/entity/EmailLog.java new file mode 100644 index 00000000..4fb2e205 --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/entity/EmailLog.java @@ -0,0 +1,35 @@ +package com.ai.da.mapper.primary.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("t_email_log") +public class EmailLog extends BaseEntity { + + private Long templateId; + + private String parameter; + + // from是SQL关键字,直接使用会报错 + private String sender; + + private String recipients; + + private String cc = null; + + private String bcc = null; + + private String subject; + + /** + * failed 邮件发送失败(如网络问题、邮件服务器问题等)。 + * retrying 邮件发送失败后,正在重试发送。 + * delivered 邮件已成功投递到收件人的邮箱服务器。 + */ + private String status; + + private int retryCount = 0; +} diff --git a/src/main/java/com/ai/da/mapper/primary/entity/EmailTemplate.java b/src/main/java/com/ai/da/mapper/primary/entity/EmailTemplate.java new file mode 100644 index 00000000..e43ebf09 --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/entity/EmailTemplate.java @@ -0,0 +1,24 @@ +package com.ai.da.mapper.primary.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("t_email_template") +public class EmailTemplate extends BaseEntity { + + // 考虑添加唯一索引 + private String templateName; + + private String templatePath; + + private String content; + + private int version; + + private String language; + + private byte isDeleted = 0; +} diff --git a/src/main/java/com/ai/da/model/dto/BasicEmailParamDTO.java b/src/main/java/com/ai/da/model/dto/BasicEmailParamDTO.java new file mode 100644 index 00000000..e973f914 --- /dev/null +++ b/src/main/java/com/ai/da/model/dto/BasicEmailParamDTO.java @@ -0,0 +1,29 @@ +package com.ai.da.model.dto; + +import lombok.Data; + +import javax.mail.internet.InternetAddress; + +@Data +public class BasicEmailParamDTO { + /** 邮箱服务器 */ + private String serviceAddress; + /** 邮箱服务器端口 */ + private String servicePort; + /** 发件人邮箱地址 */ + private String senderUserMail; + /** 发件人账号 */ + private String senderUser; + /** 发件人密码 */ + private String password; + /** 邮件标题 */ + private String subject; + /** 邮件内容 */ + private String content; + /** 收件人邮箱地址 */ + private InternetAddress[] mailTo; + /** 抄送人 */ + private InternetAddress[] cc; + /** 暗抄送人 */ + private InternetAddress[] bcc; +} diff --git a/src/main/java/com/ai/da/service/EmailService.java b/src/main/java/com/ai/da/service/EmailService.java new file mode 100644 index 00000000..00373e8a --- /dev/null +++ b/src/main/java/com/ai/da/service/EmailService.java @@ -0,0 +1,138 @@ +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 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 receiverAddress, String fileName, InputStreamSource inputStreamSource); + + /** + * 发送昨日的试用订单用户数据--无试用订单情况 + * + * @param receiverAddress 收件人地址 + */ + void sendNoExcelEmail(List 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 receiverAddress, AffiliateEmailParamsDTO paramsDTO, String type); + + void creditsPurchaseReminder(String username, String quantity, String amount); + + void commonExceptionReminder(String functionName, List destination); +} diff --git a/src/main/java/com/ai/da/service/impl/EmailServiceImpl.java b/src/main/java/com/ai/da/service/impl/EmailServiceImpl.java new file mode 100644 index 00000000..fe0b90f8 --- /dev/null +++ b/src/main/java/com/ai/da/service/impl/EmailServiceImpl.java @@ -0,0 +1,664 @@ +package com.ai.da.service.impl; + +import com.ai.da.common.config.exception.BusinessException; +import com.ai.da.common.utils.DateUtil; +import com.ai.da.common.utils.MailUtil; +import com.ai.da.mapper.primary.EmailLogMapper; +import com.ai.da.mapper.primary.EmailTemplateMapper; +import com.ai.da.mapper.primary.entity.*; +import com.ai.da.model.dto.AffiliateEmailParamsDTO; +import com.ai.da.model.dto.BasicEmailParamDTO; +import com.ai.da.model.dto.SubscriptionEmailParamsDTO; +import com.ai.da.service.EmailService; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson2.JSON; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.tencentcloudapi.common.Credential; +import com.tencentcloudapi.common.exception.TencentCloudSDKException; +import com.tencentcloudapi.common.profile.ClientProfile; +import com.tencentcloudapi.common.profile.HttpProfile; +import com.tencentcloudapi.ses.v20201002.SesClient; +import com.tencentcloudapi.ses.v20201002.models.SendEmailRequest; +import com.tencentcloudapi.ses.v20201002.models.SendEmailResponse; +import com.tencentcloudapi.ses.v20201002.models.Template; +import jdk.nashorn.internal.ir.ObjectNode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.InputStreamSource; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import javax.mail.MessagingException; +import javax.mail.internet.AddressException; +import java.io.*; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Stream; + +@Slf4j +@Service +public class EmailServiceImpl implements EmailService { + + @Resource + private MailUtil mailUtil; + @Resource + private EmailTemplateMapper emailTemplateMapper; + @Resource + private EmailLogMapper emailLogMapper; + + public void loadSingleEmailTemplate(String templatePath){ + // 获取 ClassLoader + ClassLoader classLoader = this.getClass().getClassLoader(); + // 获取文件的 URL + URL resourceUrl = classLoader.getResource(templatePath); + + if (resourceUrl == null) { + System.out.println("File not found: " + templatePath); + return; + } + // 获取文件名 + String fileName = templatePath.substring(templatePath.lastIndexOf("\\") + 1); + // 获取文件内容 + try (InputStream inputStream = resourceUrl.openStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + StringBuilder content = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + content.append(line).append("\n"); + } + // 调用方法将数据存入数据库 + saveTemplateToDatabase(fileName, removePrefixAndFileExtension(templatePath), String.valueOf(content)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void loadTemplatesFromResources(String resourcesPath) { + try { + // 获取 ClassLoader + ClassLoader classLoader = this.getClass().getClassLoader(); + // 获取 resources 文件夹的路径 + Path path = Paths.get(classLoader.getResource(resourcesPath).toURI()); + + // 遍历文件夹 + try (Stream paths = Files.walk(path)) { + paths.filter(Files::isRegularFile) + .forEach(file -> { + try { + // 读取文件内容 + String content = new String(Files.readAllBytes(file), StandardCharsets.UTF_8); + // 获取文件名和路径 + String fileName = file.getFileName().toString(); + // 去除文件后缀 + String filePath = removePrefixAndFileExtension(file.toString()); + // 调用方法将数据存入数据库 + saveTemplateToDatabase(fileName, filePath, content); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + + /** + * 去掉文件路径的前缀和后缀 + * + * @param filePath 文件路径(可以包含路径) + * @return 去掉后缀的文件名 + */ + private static String removePrefixAndFileExtension(String filePath) { + String keyword = "templates\\"; + int index = filePath.indexOf(keyword); + if (index == -1) { + return null; // 如果路径中不包含 templates/,返回空 + } + int lastDotIndex = filePath.lastIndexOf('.'); + if (lastDotIndex == -1) { + return filePath.substring(index + keyword.length()); // 没有后缀,直接返回 + } + return filePath.substring(index+ keyword.length(), lastDotIndex); + } + + public void saveTemplateToDatabase(String fileName, String filePath, String content) { + // 这里实现将数据存入数据库的逻辑 + // 使用 JDBC 或 JPA 将 fileName、filePath 和 content 插入到 email_template 表 + log.info("Saving to database: {}", fileName); + + EmailTemplate emailTemplate = new EmailTemplate(); + emailTemplate.setTemplateName(fileName); + emailTemplate.setTemplatePath(filePath); + emailTemplate.setContent(content); + emailTemplate.setVersion(1); + emailTemplate.setCreateTime(LocalDateTime.now()); + if (fileName.endsWith("en.html")){ + emailTemplate.setLanguage("EN"); + }else { + emailTemplate.setLanguage("CN"); + } + + emailTemplateMapper.insert(emailTemplate); + } + + /** + * 发邮件 + * @param mailTo 收件人邮箱 + * @param jsonObject 动态邮件模板【参数】 + * @param templateName 邮件模板名(只有文件名且需要带文件后缀) + * @param title 邮件标题 + * @param fileName 附件文件名 + * @param inputStreamSource 附件 + */ + public void sendEmail(List mailTo, JSONObject jsonObject, String templateName, String title, String fileName, InputStreamSource inputStreamSource) { + EmailTemplate emailTemplate = getEmailTemplateByName(templateName); + if (Objects.isNull(emailTemplate)){ + log.error("Email template: {}, dose not exist!", templateName); + return; + } + try { + BasicEmailParamDTO basicEmailParamDTO = mailUtil.setBasicEmailParams(mailTo, jsonObject, emailTemplate.getTemplatePath(), title); + int lastReturnCode = mailUtil.sendMail(basicEmailParamDTO, fileName, inputStreamSource); + + if (lastReturnCode == 250) { + log.info("邮件发送成功!Subject : {}", basicEmailParamDTO.getSubject()); + } else if (lastReturnCode == 450) { + log.info("目标邮箱 {} 暂时不可用,请稍后重试", mailTo); + } else if (lastReturnCode == 550) { + log.info("目标邮箱 {} 不可用,邮件发送失败", mailTo); + } else { + log.info("邮件发送失败,Subject : {}, 状态码: {}", basicEmailParamDTO.getSubject(), lastReturnCode); + } + + EmailLog emailLog = new EmailLog(); + emailLog.setTemplateId(emailTemplate.getId()); + if (Objects.nonNull(jsonObject)) emailLog.setParameter(jsonObject.toString()); + emailLog.setSender("info@aida.com.hk"); + emailLog.setRecipients(mailTo.toString()); + emailLog.setSubject(title); + emailLog.setCreateTime(LocalDateTime.now()); + switch (lastReturnCode) { + case 0: + break; + case 250: + emailLog.setStatus("delivered"); + break; + case 450: + emailLog.setStatus("retrying"); + break; + case 550: + emailLog.setStatus("failed"); + break; + } + emailLogMapper.insert(emailLog); + + } catch (MessagingException e) { + throw new RuntimeException(e); + } + } + + /** + * 适用于 : 需要自定义发件人信息 + * @param jsonObject 模板参数 + * @param basicEmailParamDTO 包含发件人信息、邮件标题、收件人信息 + * @param templateName 使用的模板文件名 + * @param fileName 附件文件名 + * @param inputStreamSource 附件文件信息 + */ + public void sendEmail(JSONObject jsonObject, BasicEmailParamDTO basicEmailParamDTO, String templateName, String fileName, InputStreamSource inputStreamSource) { + EmailTemplate emailTemplate = getEmailTemplateByName(templateName); + if (Objects.isNull(emailTemplate)){ + log.error("Email template: {}, dose not exist!", templateName); + return; + } + try { + basicEmailParamDTO.setContent(mailUtil.setContent(jsonObject, emailTemplate.getTemplatePath())); + int lastReturnCode = mailUtil.sendMail(basicEmailParamDTO, fileName, inputStreamSource); + EmailLog emailLog = new EmailLog(); + emailLog.setTemplateId(emailTemplate.getId()); + if (Objects.nonNull(jsonObject)) emailLog.setParameter(jsonObject.toString()); + emailLog.setSender(basicEmailParamDTO.getSenderUser()); + emailLog.setRecipients(basicEmailParamDTO.getMailTo().toString()); + emailLog.setSubject(basicEmailParamDTO.getSubject()); + emailLog.setCreateTime(LocalDateTime.now()); + switch (lastReturnCode) { + case 0: + break; + case 250: + emailLog.setStatus("delivered"); + break; + case 450: + emailLog.setStatus("retrying"); + break; + case 550: + emailLog.setStatus("failed"); + break; + } + emailLogMapper.insert(emailLog); + + } catch (MessagingException e) { + throw new RuntimeException(e); + } + } + + public EmailTemplate getEmailTemplateByName(String templateName) { + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("template_name", templateName).orderByDesc("id"); + List emailTemplates = emailTemplateMapper.selectList(qw); + if (emailTemplates.isEmpty()) { + return null; + } + return emailTemplates.get(0); + } + + // 登入主题 + public final String LOGIN_SUBJECT = "Log on"; + // 忘记密码主题 + public final String FORGET_PWD_SUBJECT = "Reset password"; + // 异常ip + public final String EXCEPTION_ID_SUBJECT = "Exception ip"; + // 绑定邮箱 + public final String BIND_MAILBOX_SUBJECT = "绑定邮箱"; + // 更换邮箱 + public final String CHANGE_MAILBOX_SUBJECT = "Change Mailbox"; + + // 登入模板id +// public final String LOGIN_TEMPLATE_ID = "58020_login_aida_en.html"; + // 修改密码模板id +// public final String UPDATE_PWD_TEMPLATE_ID = "58022_update_password.html"; + // 异常ip模板id +// public final String EXCEPTION_ID_TEMPLATE_ID = "58021_exception_ip.html"; + // 绑定邮箱模板id +// public final String BIND_MAILBOX_TEMPLATE_ID = "132754_绑定邮箱.html"; + // 更换绑定邮箱 +// public final String CHANGE_MAILBOX_TEMPLATE_ID = "128210_change_mailbox_en.html"; + + public Boolean send(String receiverAddress, String ip, String templateId, String verifyCode) { + String subject = Objects.equals(templateId, LOGIN_TEMPLATE_ID) ? LOGIN_SUBJECT : + Objects.equals(templateId, UPDATE_PWD_TEMPLATE_ID) ? FORGET_PWD_SUBJECT : + Objects.equals(templateId, EXCEPTION_ID_TEMPLATE_ID) ? EXCEPTION_ID_SUBJECT : + Objects.equals(templateId, CHANGE_MAILBOX_TEMPLATE_ID) ? CHANGE_MAILBOX_SUBJECT : BIND_MAILBOX_SUBJECT; + + JSONObject jsonObject = contractTemplate(templateId, verifyCode, ip); + sendEmail(Collections.singletonList(receiverAddress), jsonObject, templateId, subject, null, null); + return Boolean.TRUE; + } + + private static JSONObject contractTemplate(String templateId, String verifyCode, String ip) { + JSONObject jsonObject = new JSONObject(); + if (templateId == EXCEPTION_ID_TEMPLATE_ID) { + jsonObject.put("exceptionIp", ip); + jsonObject.put("loginTime", DateUtil.dateToStr(new Date(), DateUtil.YYYY_MM_DD_HH_MM_SS)); + } else { + jsonObject.put("code", verifyCode); + } + return jsonObject; + } + + + private final static String YOUR_TRIAL_TEMPLATE_ID = "117214_trailOrderTemplate.html"; + private final static String APPROVAL_TEMPLATE_ID = "117215_trailOrderApprovalTemplate.html"; + private final static String NOTIFICATION_TEMPLATE_ID = "117216_notificationTemplate.html"; + private final static String NOTIFICATION_CHINESE_TEMPLATE_ID = "122229_notificationChineseTemplate.html"; + + /** + * 发送不同类型的邮件 + * + * @param receiverAddress 收件人邮箱地址 + * @param emailType 邮件类型:1 - 提交试用请求,2 - 审批通过,3 - 试用请求通过通知 + * @return 发送结果 + */ + // 由于原本的参数【String senderAddress】在实际使用过程中都是空,即使用默认发件地址,故这里删除该参数 + // todo 确认入参能否被删除 + public void sendCustomEmail(String receiverAddress, TrialOrder trialOrder, int emailType, String country, Boolean link) { + + // 根据邮件类型设置不同的主题和模板 + String subject = ""; + String templateId = ""; + JSONObject jsonObject = new JSONObject(); + switch (emailType) { + case 1: + subject = "试用订单请求"; + templateId = YOUR_TRIAL_TEMPLATE_ID; + jsonObject = buildTrialOrderData(trialOrder, null); + break; + case 2: + subject = "试用订单审批通过"; + templateId = APPROVAL_TEMPLATE_ID; + jsonObject = buildTrialOrderData(trialOrder, null); + break; + case 3: + subject = "Approval Confirmation for AiDA System Trial Access"; + if (country.equals("China")) { + templateId = NOTIFICATION_CHINESE_TEMPLATE_ID; + } else { + templateId = NOTIFICATION_TEMPLATE_ID; + } + jsonObject = buildTrialOrderData(trialOrder, link); + break; + default: + break; + } + // 发送邮件 + sendEmail(Collections.singletonList(receiverAddress), jsonObject, templateId, subject, null, null); + } + + // 构建试用订单数据 + private JSONObject buildTrialOrderData(TrialOrder trialOrder, Boolean link) { + JSONObject jsonObject = new JSONObject(); + // 设置试用订单通过通知相关数据 + jsonObject.put("title", trialOrder.getTitle()); + jsonObject.put("surname", trialOrder.getSurname()); + jsonObject.put("givenName", trialOrder.getGivenName()); + jsonObject.put("userName", trialOrder.getUserName()); + jsonObject.put("email", trialOrder.getEmail()); + if (Objects.nonNull(link)) { + if (link) { + jsonObject.put("days", 14); + } else { + jsonObject.put("days", 5); + } + } + return jsonObject; + } + + private final static String TRIAL_ORDER_LIST_ID = "122273_trailOrderData.html"; + public void sendExcelEmail(List receiverAddress, String fileName, InputStreamSource inputStreamSource) { + // 根据邮件类型设置不同的主题和模板 + String subject = "昨日试用订单数据"; + // 发送邮件 + sendEmail(receiverAddress, null, TRIAL_ORDER_LIST_ID, subject, fileName, inputStreamSource); + } + + private final static String NO_TRIAL_ORDER_LIST_ID = "122591_noTrailOrderTemplate.html"; + public void sendNoExcelEmail(List receiverAddress) { + // 根据邮件类型设置不同的主题和模板 + String subject = "昨日试用订单数据"; + // 发送邮件 + sendEmail(receiverAddress, null, NO_TRIAL_ORDER_LIST_ID, subject, null, null); + } + + private final static String WILLBEEXPIRED_TEMPLATE_ID = "118178_willBeExpiredNotification.html"; + public void sendWillBeExpiredEmail(Account account) { + // 根据邮件类型设置不同的主题和模板 + String subject = "Renewal notice"; + // 发送邮件 + sendEmail(Collections.singletonList(account.getUserEmail()), buildAccountData(account), WILLBEEXPIRED_TEMPLATE_ID, subject, null, null); + } + + private static JSONObject buildAccountData(Account account) { + JSONObject jsonObject = new JSONObject(); + // 设置试用订单相关数据 + jsonObject.put("userName", account.getUserName()); + + // 用户到期时间戳 + Long timestamp = account.getValidEndTime(); // 替换为你的时间戳 + if (null != timestamp) { + // 获取当前时间戳 + Long currentTimestamp = System.currentTimeMillis(); + // 计算时间差(毫秒) + long timeDifference = currentTimestamp - timestamp; + // 向上取整计算天数 + long days = (timeDifference + 24 * 60 * 60 * 1000 - 1) / (24 * 60 * 60 * 1000); + jsonObject.put("days", days); + } + return jsonObject; + } + + private final static String UPGRADE_SUCCESS_NOTIFICATION_ID = "118856_AiDA发版完成通知英文版.html"; + private final static String UPGRADE_SUCCESS_NOTIFICATION_ID_CHINESE = "122899_AiDA发版完成通知中文版.html"; + + // todo 将这里的发件人信息包装为枚举类,枚举类中包含发件邮箱的服务地址,密码,发件人邮箱 + public void sendUpgradeNotification(Account account, String senderAddress, Integer type) { + try { + // 根据邮件类型设置不同的主题和模板 + String subject = ""; + String templateId = ""; + + if (type == 1) { + subject = "Successful System Upgrade and New Features in AiDA 3.0"; + templateId = UPGRADE_SUCCESS_NOTIFICATION_ID; + }else { + subject = "系统升级成功和AiDA 3.0新功能"; + templateId = UPGRADE_SUCCESS_NOTIFICATION_ID_CHINESE; + } + + BasicEmailParamDTO basicEmailParamDTO = new BasicEmailParamDTO(); + basicEmailParamDTO.setServiceAddress("mail.code-create.com.hk."); + basicEmailParamDTO.setSenderUserMail("info@code-create.com.hk"); + basicEmailParamDTO.setSenderUser("info@code-create.com.hk"); + basicEmailParamDTO.setPassword("???"); + basicEmailParamDTO.setSubject(subject); + basicEmailParamDTO.setMailTo(mailUtil.getInternetAddressList(Collections.singletonList(account.getUserEmail()))); + + // 发送邮件 + sendEmail(buildAccountData(account), basicEmailParamDTO, templateId, null, null); + + } catch (AddressException e) { + throw new RuntimeException(e); + } + } + + private final static String NEW_USER_PAYMENT_NOTIFICATION_EN = "124889_new_user_payment_notification_en.html"; + private final static String NEW_USER_PAYMENT_NOTIFICATION_CN = "124888_new_user_payment_notification_cn.html"; + private final static String RENEWAL_NOTIFICATION_FOR_OLD_USER_EN = "124892_renewal_notification_for_old_user_en.html"; + private final static String RENEWAL_NOTIFICATION_FOR_OLD_USER_CN = "124891_renewal_notification_for_old_user_cn.html"; + + public void notificationForPaidUser(String receiverAddress, int emailType, String country, String userName, String date) { + // 根据邮件类型设置不同的主题和模板 + String subject = ""; + String templateId = ""; + JSONObject parameter = new JSONObject(); + switch (emailType) { + // 新用户 + case 1: + subject = "Welcome to AiDA!"; + if (country.equals("China")) { + templateId = NEW_USER_PAYMENT_NOTIFICATION_CN; + } else { + templateId = NEW_USER_PAYMENT_NOTIFICATION_EN; + } + parameter.put("userName", userName); + parameter.put("email", receiverAddress); + break; + // 续费用户 + case 2: + subject = "Account renewal notification"; + if (country.equals("China")) { + templateId = RENEWAL_NOTIFICATION_FOR_OLD_USER_CN; + } else { + templateId = RENEWAL_NOTIFICATION_FOR_OLD_USER_EN; + } + break; + default: + break; + } + parameter.put("userName", userName); + parameter.put("date", date); + // 发送邮件 + sendEmail(Collections.singletonList(receiverAddress), parameter, templateId, subject, null, null); + } + + private final static String PORTFOLIO_REGISTER_ID = "124847_portfolio-account-register.html"; + public Boolean designWorksRegister(String userEmail, String randomVerifyCode) { + String subject = "Tourist registration"; + sendEmail(Collections.singletonList(userEmail), contractTemplate(PORTFOLIO_REGISTER_ID, randomVerifyCode, null), PORTFOLIO_REGISTER_ID, subject, null, null); + return Boolean.TRUE; + } + + private final static String UPLOAD_TIMEOUT_REMINDER = "128324_upload_timeout_reminder.html"; + public void uploadTimeoutReminder(String userName, String time) { + String xp = "xupei3360@163.com"; + String shb = "shahaibodd99@gmail.com"; + String wxd = "X1627315083@163.com"; + String pkc = "kaicpang.pang@connect.polyu.hk"; + JSONObject param = new JSONObject(); + param.put("username", userName); + param.put("time", time); + + // 返回的resp是一个SendEmailResponse的实例,与请求对象对应 + sendEmail(Arrays.asList(shb, xp, wxd, pkc), param, UPLOAD_TIMEOUT_REMINDER, "上传图片超时提醒", null, null); + } + + private final static String CANCEL_MERCHANT_EN = "130720_cancel-merchant-en.html"; + private final static String NEW_MERCHANT_EN = "135190_new-merchant-en-updated01.html"; + private final static String NEW_USER_EN = "135189_new-user-en-updated01.html"; + private final static String NEW_USER_CN = "135186_new-user-cn-updated01.html"; + private final static String RENEWAL_MERCHANT_EN = "130724_renewal-merchant-en.html"; + private final static String RENEWAL_USER_EN = "130725_renewal-user-en.html"; + private final static String RENEWAL_USER_CN = "130726_renewal-user-cn.html"; + private final static String RENEWAL_REMINDER_USER_EN = "130727_renewal-reminder-user-en.html"; + private final static String RENEWAL_REMINDER_USER_CN = "130728_renewal-reminder-user-cn.html"; + private final static String PAYMENT_FAILED_NEW_MERCHANT_EN = "131230_payment_failed_new_merchant_en.html"; + private final static String PAYMENT_FAILED_RENEWAL_MERCHANT_EN = "131225_payment_failed_renewal_merchant_en.html"; + private final static String PAYMENT_FAILED_RENEWAL_USER_EN = "131563_payment_failed_renewal_user_en.html"; + private final static String PAYMENT_FAILED_RENEWAL_USER_CN = "131564_payment_failed_renewal_user_cn.html"; + + public boolean subscriptionEmailReminder(String type, SubscriptionEmailParamsDTO subscriptionEmailParamsDTO, String language, String receiverAddress) { + try { + String merchantEmail = "kimwong@code-create.com.hk"; + String developer = "xupei3360@163.com"; + List merchantReceiver = Arrays.asList(/*merchantEmail, */developer); + + String merchantSubject = null; + String merchantTemplate = null; + String userSubject = null; + String userTemplate = null; + switch (type) { + case "cancel": + merchantSubject = "[Code-Create] Subscription Cancelled"; + merchantTemplate = CANCEL_MERCHANT_EN; + break; + case "fail_new": + merchantSubject = "[Code-Create] Payment Failed : New Order (" + subscriptionEmailParamsDTO.getOrderId() + ")"; + merchantTemplate = PAYMENT_FAILED_NEW_MERCHANT_EN; + break; + case "fail_renewal": + merchantSubject = "[Code-Create] Payment Failed : Renewal Order (" + subscriptionEmailParamsDTO.getOrderId() + ")"; + merchantTemplate = PAYMENT_FAILED_RENEWAL_MERCHANT_EN; + if (language.equals("ENGLISH")) { + userSubject = "[Code-Create] Payment Failed : Renewal Order (" + subscriptionEmailParamsDTO.getOrderId() + ")"; + userTemplate = PAYMENT_FAILED_RENEWAL_USER_EN; + } else { + userSubject = "[Code-Create] 自动续费失败 (" + subscriptionEmailParamsDTO.getOrderId() + ")"; + userTemplate = PAYMENT_FAILED_RENEWAL_USER_CN; + } + break; + case "new": + merchantSubject = "[Code-Create] New Order(" + subscriptionEmailParamsDTO.getOrderId() + ")"; + merchantTemplate = NEW_MERCHANT_EN; + if (language.equals("ENGLISH")) { + userSubject = "[Code-Create] You have successfully subscribed to AiDA"; + userTemplate = NEW_USER_EN; + } else { + userSubject = "[Code-Create] 您已成功订阅AiDA"; + userTemplate = NEW_USER_CN; + } + break; + case "renewal": + merchantSubject = "[Code-Create] New subscription renewal order (" + subscriptionEmailParamsDTO.getOrderId() + ")"; + merchantTemplate = RENEWAL_MERCHANT_EN; + if (language.equals("ENGLISH")) { + userSubject = "[Code-Create] AiDA Renewal Successful"; + userTemplate = RENEWAL_USER_EN; + } else { + userSubject = "[Code-Create] AiDA续订成功"; + userTemplate = RENEWAL_USER_CN; + } + break; + case "reminder": + if (language.equals("ENGLISH")) { + userSubject = "[Code-Create] AiDA Subscription Renewal Reminder"; + userTemplate = RENEWAL_REMINDER_USER_EN; + } else { + userSubject = "[Code-Create] AiDA续订提醒"; + userTemplate = RENEWAL_REMINDER_USER_CN; + } + break; + default: + log.error("unknown subscription email type"); + return false; + } + JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(subscriptionEmailParamsDTO)); + + // 排除不向用户发送邮件的情况 + if (!type.equals("cancel") && !type.equals("fail_new")) { + sendEmail(Collections.singletonList(receiverAddress), jsonObject, userTemplate, userSubject, null, null); + } + // 排除不向商家发送邮件的情况 + if (!type.equals("reminder")) { + sendEmail(merchantReceiver, jsonObject, merchantTemplate, merchantSubject, null, null); + } + return true; + } catch (Exception e) { + log.error("邮件发送失败,{}", e.getMessage()); + return false; + } + } + + private final static String NEW_REGISTRATION = "132123_affiliate_registration_en.html"; + private final static String AFFILIATE_ACCEPTED = "132124_affiliate_accepted_en.html"; + private final static String AFFILIATE_REFUSED = "132125_affiliate_refused_en.html"; + private final static String AFFILIATE_MONTHLY_SUMMARY = "132126_affiliate_monthly_summary_en.html"; + + public void affiliateEmailReminder(List receiverAddress, AffiliateEmailParamsDTO paramsDTO, String type) { + String subject = ""; + String templateId = ""; + switch (type) { + case "new": + subject = "New Affiliate Registration"; + templateId = NEW_REGISTRATION; + break; + case "accepted": + subject = "Affiliate Application Accepted"; + templateId = AFFILIATE_ACCEPTED; + break; + case "refused": + subject = "Affiliate Application Refused"; + templateId = AFFILIATE_REFUSED; + break; + case "summary": + subject = "Your Monthly AffiliateWP Summary for AiDA"; + templateId = AFFILIATE_MONTHLY_SUMMARY; + break; + } + + // 将 DTO 转换为 JSONObject + JSONObject jsonObject = (JSONObject) JSONObject.toJSON(paramsDTO); + sendEmail(receiverAddress, jsonObject, templateId, subject, null, null); + } + + private final static String CREDITS_PURCHASE_MERCHANT = "133275_AiDA 积分购买通知-merchant.html"; + public void creditsPurchaseReminder(String username, String quantity, String amount) { + String merchantEmail = "kimwong@code-create.com.hk"; + String developerEmail = "xupei@code-create.com.hk"; + JSONObject jsonObject = new JSONObject(); + // 设置试用订单相关数据 + jsonObject.put("username", username); + jsonObject.put("quantity", quantity); + jsonObject.put("totalFee", amount); + + sendEmail(Arrays.asList(/*merchantEmail,*/developerEmail), jsonObject, CREDITS_PURCHASE_MERCHANT, "New Credit Purchase Order", null, null); + } + + private final static String COMMON_EXCEPTION_REMINDER = "135279_common-exception-reminder.html"; + public void commonExceptionReminder(String functionName, List destination) { + // 邮件内容 {{function}}处理异常,请及时查看 + JSONObject param = new JSONObject(); + param.put("function", functionName); + + sendEmail(destination, param, COMMON_EXCEPTION_REMINDER, "AiDA发生异常,请及时处理", null, null); + } +} + +