feat: 模版方法模式实现消息发送器

This commit is contained in:
xiongxiaoyang 2023-03-25 12:54:15 +08:00
parent 46d03883ed
commit c866702e48
10 changed files with 345 additions and 0 deletions

View File

@ -185,6 +185,12 @@
<version>${springdoc-openapi.version}</version>
</dependency>
<!-- 邮件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>

View File

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

View File

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

View File

@ -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);
// 使用 MimeMessageMIME 协议
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);
}
}
}

View File

@ -0,0 +1,92 @@
package io.github.xxyopen.novel.manager.message;
/**
* 抽象的消息发送器
* <p>
* 遵循松耦合的设计原则所有的属性都使用构造函数注入 Spring 框架解藕
* <p>
* 所有的消息发送器既可以注册到 Spring 容器中作为 Spring 的一个组件使用也可以直接通过 new 对象的方式使用
* <p>
* 每种类型的消息发送时机可能都不一样不同类型和发送时机的消息格式可能也不一样所以由各个子类去拓展消息的格式
*
* @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;
}
/**
* 通过给定的参数列表解析消息内容模版默认实现是使用参数列表来替换消息内容模版中的占位符可以由子类来拓展它的功能
* <p>
* 子类可以根据第一个/前几个参数去数据库中查询动态内容然后重组参数列表
*
* @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;
}
}

View File

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

View File

@ -0,0 +1,21 @@
package io.github.xxyopen.novel.manager.message;
/**
* 消息发送器接口用来发送各种消息
* <p>
* 消息按类型分系统通知邮件短信小程序通知等按发送时机分注册成功消息充值成功消息活动通知消息账户封禁消息小说下架消息等
*
* @author xiongxiaoyang
* @date 2023/3/25
*/
public interface MessageSender {
/**
* 发送消息支持动态消息标题和动态消息内容
*
* @param toUserId 消息接收方的用户ID
* @param args 用来动态生成消息标题和消息内容的参数列表
*/
void sendMessage(Long toUserId, Object... args);
}

View File

@ -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 """
<div>
感谢你注册小说精品屋你的账户现在处于活动状态
</div>
<ul>
<li> 你的账户电子邮件{}
<li> 你的账户用户名{}
</ul>
<div style="padding: 10px 0 50px 0; text-align: center;">
<a style="background: #0274be; color: #fff; padding: 12px 30px; text-decoration: none; border-radius: 3px; letter-spacing: 0.3px;" href="{}" target="_blank" rel="noopener">
登录我们的网站
</a>
</div>
如果你有任何问题请通过 {} 与我们联系
""";
}
@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());
}
}

View File

@ -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 "{}秒杀,{}即将开始,不要错过哦!点击 {} 前往。";
}
}

View File

@ -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:
# 跨域配置