com.mysql
mysql-connector-j
diff --git a/src/main/java/io/github/xxyopen/novel/core/config/MailProperties.java b/src/main/java/io/github/xxyopen/novel/core/config/MailProperties.java
new file mode 100644
index 0000000..1849903
--- /dev/null
+++ b/src/main/java/io/github/xxyopen/novel/core/config/MailProperties.java
@@ -0,0 +1,14 @@
+package io.github.xxyopen.novel.core.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * mail 配置属性
+ *
+ * @author xiongxiaoyang
+ * @date 2023/3/25
+ */
+@ConfigurationProperties(prefix = "spring.mail")
+public record MailProperties(String nickname, String username) {
+
+}
diff --git a/src/main/java/io/github/xxyopen/novel/core/constant/MessageSenderTypeConsts.java b/src/main/java/io/github/xxyopen/novel/core/constant/MessageSenderTypeConsts.java
new file mode 100644
index 0000000..8cbc25f
--- /dev/null
+++ b/src/main/java/io/github/xxyopen/novel/core/constant/MessageSenderTypeConsts.java
@@ -0,0 +1,25 @@
+package io.github.xxyopen.novel.core.constant;
+
+/**
+ * 消息发送器的类型
+ *
+ * @author xiongxiaoyang
+ * @date 2023/3/24
+ */
+public class MessageSenderTypeConsts {
+
+ private MessageSenderTypeConsts() {
+ throw new IllegalStateException("Constant class");
+ }
+
+ /**
+ * 注册成功的邮件发送器
+ */
+ public static final String REGISTER_MAIL_SENDER = "registerMailSender";
+
+ /**
+ * 秒杀活动的系统通知发送器
+ */
+ public static final String SECKILL_SYS_NOTICE_SENDER = "seckillSysNoticeSender";
+
+}
diff --git a/src/main/java/io/github/xxyopen/novel/manager/message/AbstractMailSender.java b/src/main/java/io/github/xxyopen/novel/manager/message/AbstractMailSender.java
new file mode 100644
index 0000000..10d64e6
--- /dev/null
+++ b/src/main/java/io/github/xxyopen/novel/manager/message/AbstractMailSender.java
@@ -0,0 +1,50 @@
+package io.github.xxyopen.novel.manager.message;
+
+import io.github.xxyopen.novel.core.config.MailProperties;
+import jakarta.mail.internet.InternetAddress;
+import jakarta.mail.internet.MimeMessage;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.MimeMessageHelper;
+
+/**
+ * 抽象的邮件消息发送者
+ *
+ * @author xiongxiaoyang
+ * @date 2023/3/24
+ */
+@Slf4j
+@RequiredArgsConstructor
+public abstract class AbstractMailSender extends AbstractMessageSender {
+
+ private final MailProperties mailProperties;
+
+ private final JavaMailSender mailSender;
+
+ @Override
+ protected void sendMessage(Long toUserId, String messageTitle, String messageContent) {
+ // TODO 根据消息接收方的用户ID查询出消息接收方的邮件地址
+ String toEmail = "xxyopen@foxmail.com";
+ // 开始发送邮件
+ log.info("发送 HTML 邮件开始:{},{},{}", toEmail, messageTitle, messageContent);
+ // 使用 MimeMessage,MIME 协议
+ MimeMessage message = mailSender.createMimeMessage();
+ MimeMessageHelper helper;
+ // MimeMessageHelper 帮助我们设置更丰富的内容
+ try {
+ helper = new MimeMessageHelper(message, true);
+ helper.setFrom(new InternetAddress(mailProperties.username(), mailProperties.nickname(), "UTF-8"));
+ helper.setTo(toEmail);
+ helper.setSubject(messageTitle);
+ // 第二个参数 true 代表支持 html
+ helper.setText(messageContent, true);
+ mailSender.send(message);
+ log.info("发送 HTML 邮件 to {} 成功", toEmail);
+ } catch (Exception e) {
+ // 邮件发送失败不会重试
+ log.error("发送 HTML 邮件 to {} 失败", toEmail, e);
+ }
+ }
+
+}
diff --git a/src/main/java/io/github/xxyopen/novel/manager/message/AbstractMessageSender.java b/src/main/java/io/github/xxyopen/novel/manager/message/AbstractMessageSender.java
new file mode 100644
index 0000000..3559779
--- /dev/null
+++ b/src/main/java/io/github/xxyopen/novel/manager/message/AbstractMessageSender.java
@@ -0,0 +1,92 @@
+package io.github.xxyopen.novel.manager.message;
+
+/**
+ * 抽象的消息发送器
+ *
+ * 遵循松耦合的设计原则,所有的属性都使用构造函数注入,与 Spring 框架解藕
+ *
+ * 所有的消息发送器既可以注册到 Spring 容器中,作为 Spring 的一个组件使用,也可以直接通过 new 对象的方式使用
+ *
+ * 每种类型的消息发送时机可能都不一样,不同类型和发送时机的消息格式可能也不一样,所以由各个子类去拓展消息的格式
+ *
+ * @author xiongxiaoyang
+ * @date 2023/3/24
+ */
+public abstract class AbstractMessageSender implements MessageSender {
+
+ private static final String PLACEHOLDER = "{}";
+
+ /**
+ * 定义消息发送的模版,子类不能修改此模版
+ */
+ @Override
+ public final void sendMessage(Long toUserId, Object... args) {
+ // 1.获取消息标题模版
+ String titleTemplate = getTitleTemplate();
+ // 2.获取消息内容模版
+ String contentTemplate = getContentTemplate();
+ // 3.解析消息模版,得到最终需要发送的消息标题
+ String title = resolveTitle(titleTemplate, args);
+ // 4.解析消息内容,得到最终需要发送的消息内容
+ String content = resolveContent(contentTemplate, args);
+ // 5.发送消息
+ sendMessage(toUserId, title, content);
+ }
+
+ /**
+ * 发送消息,具体发送到哪里由子类决定
+ *
+ * @param toUserId 消息接收方的用户ID
+ * @param messageTitle 消息标题
+ * @param messageContent 消息内容
+ */
+ protected abstract void sendMessage(Long toUserId, String messageTitle, String messageContent);
+
+ /**
+ * 获取消息标题的模版,具体如何制定模版由子类决定
+ *
+ * @return 消息标题
+ */
+ protected abstract String getTitleTemplate();
+
+ /**
+ * 获取消息内容的模版,具体如何制定模版由子类决定
+ *
+ * @return 消息内容
+ */
+ protected abstract String getContentTemplate();
+
+ /**
+ * 通过给定的参数列表解析消息标题模版,默认固定标题,不需要解析,可以由子类来拓展它的功能
+ *
+ * @param titleTemplate 消息标题模版
+ * @param arguments 用来解析的参数列表
+ * @return 解析后的消息标题
+ */
+ protected String resolveTitle(String titleTemplate, Object... arguments) {
+ return titleTemplate;
+ }
+
+ /**
+ * 通过给定的参数列表解析消息内容模版,默认实现是使用参数列表来替换消息内容模版中的占位符,可以由子类来拓展它的功能
+ *
+ * 子类可以根据第一个/前几个参数去数据库中查询动态内容,然后重组参数列表
+ *
+ * @param contentTemplate 消息内容模版
+ * @param args 用来解析的参数列表
+ * @return 解析后的消息内容
+ */
+ protected String resolveContent(String contentTemplate, Object... args) {
+ if (args.length > 0) {
+ StringBuilder formattedContent = new StringBuilder(contentTemplate);
+ for (Object arg : args) {
+ int start = formattedContent.indexOf(PLACEHOLDER);
+ formattedContent.replace(start, start + PLACEHOLDER.length(),
+ String.valueOf(arg));
+ }
+ return formattedContent.toString();
+ }
+ return contentTemplate;
+ }
+
+}
diff --git a/src/main/java/io/github/xxyopen/novel/manager/message/AbstractSysNoticeSender.java b/src/main/java/io/github/xxyopen/novel/manager/message/AbstractSysNoticeSender.java
new file mode 100644
index 0000000..c457f25
--- /dev/null
+++ b/src/main/java/io/github/xxyopen/novel/manager/message/AbstractSysNoticeSender.java
@@ -0,0 +1,26 @@
+package io.github.xxyopen.novel.manager.message;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * 抽象的系统通知发送者
+ *
+ * @author xiongxiaoyang
+ * @date 2023/3/24
+ */
+@Slf4j
+public abstract class AbstractSysNoticeSender extends AbstractMessageSender {
+
+ @Override
+ protected void sendMessage(Long toUserId, String messageTitle, String messageContent) {
+ // 生成消息的发送时间
+ LocalDateTime messageDateTime = LocalDateTime.now();
+ // TODO 在数据库系统通知表中插入一条记录
+ log.info("系统通知发送成功,{},{},{},{}", toUserId, messageDateTime.format(DateTimeFormatter.ISO_DATE_TIME),
+ messageTitle, messageContent);
+ }
+
+}
diff --git a/src/main/java/io/github/xxyopen/novel/manager/message/MessageSender.java b/src/main/java/io/github/xxyopen/novel/manager/message/MessageSender.java
new file mode 100644
index 0000000..ca109fa
--- /dev/null
+++ b/src/main/java/io/github/xxyopen/novel/manager/message/MessageSender.java
@@ -0,0 +1,21 @@
+package io.github.xxyopen.novel.manager.message;
+
+/**
+ * 消息发送器接口,用来发送各种消息
+ *
+ * 消息按类型分系统通知、邮件、短信、小程序通知等,按发送时机分注册成功消息、充值成功消息、活动通知消息、账户封禁消息、小说下架消息等
+ *
+ * @author xiongxiaoyang
+ * @date 2023/3/25
+ */
+public interface MessageSender {
+
+ /**
+ * 发送消息,支持动态消息标题和动态消息内容
+ *
+ * @param toUserId 消息接收方的用户ID
+ * @param args 用来动态生成消息标题和消息内容的参数列表
+ */
+ void sendMessage(Long toUserId, Object... args);
+
+}
diff --git a/src/main/java/io/github/xxyopen/novel/manager/message/RegisterMailSender.java b/src/main/java/io/github/xxyopen/novel/manager/message/RegisterMailSender.java
new file mode 100644
index 0000000..712ff85
--- /dev/null
+++ b/src/main/java/io/github/xxyopen/novel/manager/message/RegisterMailSender.java
@@ -0,0 +1,60 @@
+package io.github.xxyopen.novel.manager.message;
+
+import io.github.xxyopen.novel.core.config.MailProperties;
+import io.github.xxyopen.novel.core.constant.MessageSenderTypeConsts;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.stereotype.Component;
+
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+/**
+ * 注册成功的邮件发送器
+ *
+ * @author xiongxiaoyang
+ * @date 2023/3/24
+ */
+@Component(value = MessageSenderTypeConsts.REGISTER_MAIL_SENDER)
+@EnableConfigurationProperties(MailProperties.class)
+public class RegisterMailSender extends AbstractMailSender {
+
+ public RegisterMailSender(MailProperties mailProperties, JavaMailSender mailSender) {
+ super(mailProperties, mailSender);
+ }
+
+ @Override
+ protected String getTitleTemplate() {
+ return "欢迎来到小说精品屋";
+ }
+
+ @Override
+ protected String getContentTemplate() {
+ return """
+
+ 感谢你注册小说精品屋!你的账户现在处于活动状态。
+
+
+ - 你的账户电子邮件:{}
+
- 你的账户用户名:{}
+
+
+
+ 如果你有任何问题,请通过 {} 与我们联系。
+ """;
+ }
+
+ @Override
+ protected String resolveContent(String content, Object... args) {
+ // TODO 去数据库/配置文件中查询网站配置
+ String websiteLink = "https://www.xxyopen.com";
+ String websiteEmail = "xxyopen@foxmail.com";
+ return super.resolveContent(content,
+ Stream.of(args, new Object[]{websiteLink, websiteEmail}).flatMap(Arrays::stream).toArray());
+ }
+
+}
diff --git a/src/main/java/io/github/xxyopen/novel/manager/message/SeckillSystemNoticeSender.java b/src/main/java/io/github/xxyopen/novel/manager/message/SeckillSystemNoticeSender.java
new file mode 100644
index 0000000..5a7fb4a
--- /dev/null
+++ b/src/main/java/io/github/xxyopen/novel/manager/message/SeckillSystemNoticeSender.java
@@ -0,0 +1,25 @@
+package io.github.xxyopen.novel.manager.message;
+
+import io.github.xxyopen.novel.core.constant.MessageSenderTypeConsts;
+import org.springframework.stereotype.Component;
+
+/**
+ * 秒杀活动的系统通知发送器
+ *
+ * @author xiongxiaoyang
+ * @date 2023/3/24
+ */
+@Component(value = MessageSenderTypeConsts.SECKILL_SYS_NOTICE_SENDER)
+public class SeckillSystemNoticeSender extends AbstractSysNoticeSender {
+
+ @Override
+ protected String getTitleTemplate() {
+ return "秒杀即将开始";
+ }
+
+ @Override
+ protected String getContentTemplate() {
+ return "{}秒杀,{}即将开始,不要错过哦!点击 {} 前往。";
+ }
+
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 69d9e2c..15788be 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -189,6 +189,32 @@ springdoc:
api-docs:
enabled: false
+--- #----------------------邮箱配置-----------------------------
+#邮箱服务器
+spring:
+ mail:
+ host: smtp.163.com
+ #发件人昵称
+ nickname: xxyopen
+ #邮箱账户
+ username: xxx@163.com
+ #邮箱第三方授权码
+ password: xxx
+ #编码类型
+ default-encoding: UTF-8
+ port: 465
+ properties:
+ mail:
+ smtp:
+ auth: true
+ starttls:
+ enable: true
+ required: rue
+ socketFactory:
+ port: 465
+ class: javax.net.ssl.SSLSocketFactory
+ fallback: false
+
--- #---------------------自定义配置----------------------------
novel:
# 跨域配置