领导直接给我派了个任务,几句话就是,开发一个消息推送的模块,可以先暂定邮件推送,后续需要支持其他的渠道,比如公众号,短信,等等
身为一个成熟的工程师,需要自己能够给出设计方案了 ╮(╯_╰)╭
初步的需求分析
-
需要考虑多渠道切换或者是扩展为多渠道同步推送,那这个推送的模块,最好设计为继承结构,类似于策略模式,或者是模版模式,大体的推送结构在父类定义好,具体的细节放子类
-
推送推送,具体还是要将消息推送给对应的用户,总不可能一有消息,我直接推送给全体用户吧(点名某些喜欢 @所有人 的屑),这里就隐藏了对用户的分组管理
-
都聊到对用户分组管理了,那我发消息的话,要发给哪些小组的小伙伴呢?手动硬编码先推送给几个具体的小组成员?等需要调整的时候,再硬编码调整下小组?总感觉很弱智的样子
-
分组。。带条件判断。。。,既然这样,那直接给个函数式接口不就得了,通过调用这个小组内的接口来判断是否合适,我可真行
好了,分析结束
画出草图
先做简单的,搞定几个基础类
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
String name;
String email;
String phone;
String wxOpenId; // 关注公众号产生的 openId
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Group {
String name; // 白班|晚班|紧急联系
Condition condition;
List<User> users;
}
@FunctionalInterface
public interface Condition {
// 判断条件是否满足
boolean judge();
}
这个 Condition.class
目前能确定的只有3种,就是判断白晚班和紧急联系人,白晚班是根据当前的时间进行判断,那紧急联系人该怎么判断?根据现场,当报警的异常级别较高的话,肯定是需要推送给工程师,这么一分析,就需要引入 ErrorMsg.class
,接口也得改造一下
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class ErrorMsg {
String msg;
String level;
}
@FunctionalInterface
public interface Condition {
// 判断条件是否满足
boolean judge(ErrorMsg msg);
// 超过一定的异常等级则判定为 true
final Condition ERROR_LEVEL = (ErrorMsg msg) -> {
return Integer.valueOf(msg.level) > 3 ? true : false;
};
// 判断时间在白班区间 [8:00, 20:00)
final Condition DAY_WORK = (ErrorMsg msg) -> {
int hour = LocalDateTime.now().getHour();
return (hour >= 8 && hour < 20) ? true : false;
};
// 夜班时间判断
final Condition NIGHT_WORK = (ErrorMsg msg) -> {
return !DAY_WORK.judge(msg);
};
}
Notify 推送基类设计
好了,简单的活完了,得考虑下这个消息推送的细节了,比如,该怎么设计才方便使用呢?比如,实例化子类,调用方式就是 sub.send(msg)
,或者是定义成一个静态的方法,然后 Notify.send(msg)
emmm,想不出来哪种合适,单从调用的角度,静态调用就很舒服了,但静态调用的话,实际上还是需要将需求转给对应的子类,这里就需要考虑一个初始化,以及数据变更的情况了
Notify.class
最终版本的实现代码:
public abstract class Notify {
static List<Group> groups;
// 暂定只使用一种
static Notify type;
// 处理 groups, type 变更的情况
public static void init(List<Group> actulGroups, Notify actulType) {
groups = actulGroups;
type = actulType;
}
synchronized public static void sendMsg(ErrorMsg msg) {
if (ObjectUtils.isEmpty(groups) || ObjectUtils.isEmpty(type)) {
// 没有初始化的报一个运行异常(懒得捕获)
throw new RuntimeException("not call Notify.init()");
}
List<User> users = new ArrayList<>();
groups.forEach(g -> {
// 通过调用 group 自己的 condition 判断,来确认自己小组的用户是否需要通知
if (g.getCondition().judge(msg)) {
users.addAll(g.getUsers());
}
});
// 用户去重,避免重复通知
type.sendToUsers(distinctUser(users), msg);
}
// 利用 map 覆盖赋值,去掉重复项
private static List<User> distinctUser(List<User> users) {
Map<String, User> map = new HashMap<>();
users.forEach(u -> map.put(u.getName(), u));
return new ArrayList<>(map.values());
}
// 消息发送的细节,子类去搞定
protected abstract void sendToUsers(List<User> users, ErrorMsg msg);
}
效果预览
ok,架子搭的差不多了,来实现一个子类 + 测试看看效果
@Component
public class MailNotify extends Notify {
@Override
protected void sendToUsers(List<User> users, ErrorMsg msg) {
System.out.println(users.stream().map(n -> n.getName()).collect(Collectors.joining(", ")) + "|" + msg.getMsg());
}
}
@Configuration
public class NotifyConfig {
@Bean
public void notifyInitDatas() {
Notify.init(actulGroups(), mailNotify);
}
@Bean
List<Group> actulGroups() {
User u1 = new User("wangb", "641031***@qq.com", "", ""),
u2 = new User("bin", "105466***@qq.com", "", ""),
u3 = new User("wang", "24480***@qq.com", "", "");
Group g1 = new Group("day", Condition.DAY_WORK, Arrays.asList(u1, u2));
Group g2 = new Group("night", Condition.NIGHT_WORK, Arrays.asList(u3, u2));
return Arrays.asList(g1, g2);
}
@Autowired
MailNotify mailNotify;
}
@SpringBootTest
class DemoApplicationTests {
@Test
void contextLoads() {
Notify.sendMsg(new ErrorMsg("this is a test mail", "2"));
}
}
芜湖~起飞
wangb, bin|this is a test mail
遗漏的邮件模块
是不是忘了点什么,我要做什么来着??
好家伙,我的邮件推送模块呢?
开搞开搞,博客什么的翻一翻,CTRL C/V 完事
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
spring:
mail:
host: smtp.exmail.qq.com
username: ******
password: ******
标准的 service
安排上,其实也就是在 springboot
下调用 start-mail
里封装好的邮件发送的方法
public interface EmailService {
/**
* 发送简单邮件
* @param sendTo 收件人地址
* @param titel 邮件标题
* @param content 邮件内容
*/
public void sendSimpleMail(String sendTo, String titel, String content);
}
@Service
public class EmailServiceImpl implements EmailService {
@Autowired
private JavaMailSender mailSender;
public void sendSimpleMail(String sendTo, String titel, String content) {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(innorevEmailServer);
message.setTo(sendTo);
message.setSubject(titel);
message.setText(content);
mailSender.send(message);
}
@Value("${spring.mail.username}")
private String innorevEmailServer;
}
对应的 MailNotify.class
修改下里面的函数调用
@Component
public class MailNotify extends Notify {
@Override
protected void sendToUsers(List<User> users, ErrorMsg msg) {
// System.out.println(users.stream().map(n -> n.getName()).collect(Collectors.joining(", ")) + " " + msg.getMsg());
String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"));
users.forEach(u -> emailService.sendSimpleMail(u.getEmail(), "现场运行异常提醒 " + time, msg.getMsg()));
}
@Autowired
EmailService emailService;
}
邮件发送效果预览
效果图:
后续需要改造成批量发送,单个发送估计会比较吃资源
完整的流程图
代码修修改改的完成了,那就再画一张完整的流程图
原创文章,未经允许,禁止转载
-- by 安逸的咸鱼