java使用观察者模式异步短信/邮箱提醒用户群
需求
用户中有人设置了账户余额达到阈值时,短信/邮箱进行提醒的服务。我们将需要在他账户余额阈值达到指定数值的时候进行短信/邮箱消息通知,允许账户余额阈值出现偏差的时候通知,如果某个用户48小时内已经短信/邮箱进行过通知了,那么将不再进行通知。
剖析
- 存在两个主题:短信通知和邮箱通知
- 存在两种观察者:设置了短信通知且账户余额到达阈值的用户,设置了邮箱通知且账户余额到达阈值的用户。
- 用spring的定时器,每10分钟去数据库获取某个主题已经达到阈值且开始了该主题的提醒功能的用户
- 用spring的@Asycn注解异步短信通知,邮箱通知的相关方法
- 用redis设置用户短信/邮箱为键名,设置过期时间为48小时。如果获取不到该键值对,说明其在观察者行列
代码
观察者父类
/**
*订阅观察者
*@authorAdministrator
*
*/
@Component
//标志为多例
@Scope(value=ConfigurableBeanFactory.SCOPE_PROTOTYPE)
publicclassSubscriberObserverimplementsObserver{
privateStringemail;
privateStringphone;
privateStringusername;
@Autowired
UserFunctionServiceUserFunctionService;
@Override
publicvoidupdate(Observableo,Objectarg){
if(oinstanceofEmailAlertSubject){
UserFunctionService.alertUserEmail(email,username);
}
if(oinstanceofPhoneAlertSubject){
UserFunctionService.alertUserPhone(phone,username);
}
}
publicStringgetEmail(){
returnemail;
}
publicvoidsetEmail(Stringemail){
this.email=email;
}
publicStringgetPhone(){
returnphone;
}
publicvoidsetPhone(Stringphone){
this.phone=phone;
}
publicStringgetUsername(){
returnusername;
}
publicvoidsetUsername(Stringusername){
this.username=username;
}
publicSubscriberObserver(){
super();
//TODOAuto-generatedconstructorstub
}
}
主题
/**
*email提醒主题
*@authorAdministrator
*
*/
@Component
publicclassEmailAlertSubjectextendsObservable{
publicvoidalert(){
this.setChanged();
//如果用拉的方式,这么调用
this.notifyObservers();
}
}
/**
*短信提醒主题
*@authorAdministrator
*
*/
@Component
publicclassPhoneAlertSubjectextendsObservable{
publicvoidalert(){
this.setChanged();
//如果用拉的方式,这么调用
this.notifyObservers();
}
}
定时器
/**
*定时给订阅了短信提醒和email提醒的用户服务
*@authorAdministrator
*
*/
@Component
publicclassTimeAlertTaskUtil{
@Autowired
CommonUserServicecommonUserService;
@Autowired
JedisConnectionFactoryfactory;
@Autowired
EmailAlertSubjectemailSubject;
@Autowired
PhoneAlertSubjectphoneSubject;
privatestaticfinalStringemailKeyName="emailAlert:";
privatestaticfinalStringphoneKeyName="phoneAlert:";
/**
*定时获取需要email提醒的用户,每10分钟调用一次
*/
@Scheduled(fixedDelay=1000*60*10)
publicvoidalertEmailTask(){
//1.获取数据库中达到了阈值的用户
Listemails=commonUserService.getUserAlertEmailAndName();
//2.查看redis中是否有达到阈值,且48小时已经通知的用户,将其排除在观察者行列,最终得出观察者队伍
ListinformEmail=getInformObserver(emails);
//3.创建主题,添加观察者
addObservers(emailSubject,informEmail);
//4.通知
emailSubject.alert();
//5.将已经通知的观察者信息存储到reids内,设置过期时间为一天
setRedisCache(emails);
//6.将观察者从主题中移除
deleteObservers(emailSubject,informEmail);
}
/**
*定时获取需要短信提醒的用户,每10分钟调用一次
*
*/
@Scheduled(fixedDelay=1000*60*10)
publicvoidalertPhoneTask(){
//1.获取数据库中达到了阈值的用户
Listphones=commonUserService.getUserAlertPhoneAndName();
//2.查看redis中是否有达到阈值,且今天已经通知的用户,将其排除在观察者行列,最终得出观察者队伍
ListinformPhones=getInformObserver(phones);
//3.创建主题,添加观察者
addObservers(phoneSubject,informPhones);
//4.通知
phoneSubject.alert();
//5.将已经通知的观察者信息存储到reids内,设置过期时间为一天
setRedisCache(phones);
//6.将观察者从主题中移除
deleteObservers(phoneSubject,informPhones);
}
/**
*------------------------------------------------------------------------
*-----------------------------------------------------
**/
/**
*过滤掉今天已经email提醒的用户,返回真正需要提醒的观察者列表
*
*@paramemails
*@return
*/
privateListgetInformObserver(
Listusers){
Listobs=newArrayList();
Jedisjedis=factory.getConnection().getNativeConnection();
for(Useruser:users){
Stringvalue;
SubscriberObserverobserver=(SubscriberObserver)SpringConfigTool
.getBean("subscriberObserver");
if(user.getEmail()!=null){
value=jedis.get(emailKeyName+user.getEmail());
if(value==null||!value.equals("success")){
observer.setEmail(user.getEmail());
observer.setUsername(user.getName());
obs.add(observer);
}
}else{
value=jedis.get(phoneKeyName+user.getPhone());
if(value==null||!value.equals("success")){
observer.setPhone(user.getPhone());
observer.setUsername(user.getName());
obs.add(observer);
}
}
}
returnobs;
}
/**
*将指定的观察者列表添加到指定的主题
*
*@paramsubject
*@paramlist
*/
privatevoidaddObservers(Observablesubject,Listlist){
for(SubscriberObserverobs:list){
subject.addObserver(obs);
}
}
privatevoiddeleteObservers(Observablesubject,
Listlist){
for(SubscriberObserverobs:list){
subject.deleteObserver(obs);
}
}
/**
*将列表的值作为键,存入redis,过期时间为48小时
*
*@paramlist
*/
privatevoidsetRedisCache(Listusers){
Jedisjedis=factory.getConnection().getNativeConnection();
for(Useruser:users){
if(user.getEmail()!=null){
jedis.set(emailKeyName+user.getEmail(),"success","NX","EX",
60*60*24*2);
}else{
jedis.set(phoneKeyName+user.getPhone(),"success","NX","EX",
60*60*24*2);
}
}
}
}
总结
代码是不全面的,只是个示例而已。关于短信通知和邮箱通知的服务类和工具类并没有给出,因为里面涉及到一些隐私参数。所以关于异步通知示例代码没有,但使用Spring管理的@Async注解和在spring进行一定的配置即可,可以在我的另外一篇博客找到关于异步通知的示例代码。
事实上根据需求,可以使用redis的发布订阅,或者消息队列mq来实现类似的功能。但为了加深对设计模式的理解,所以写了一个不是很纯正的观察者模式来模仿发布订阅的操作。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。