java使用觀察者模式異步短信/郵箱提醒用戶群
需求
用戶中有人設(shè)置了賬戶余額達到閾值時,短信/郵箱進行提醒的服務(wù)。我們將需要在他賬戶余額閾值達到指定數(shù)值的時候進行短信/郵箱消息通知,允許賬戶余額閾值出現(xiàn)偏差的時候通知,如果某個用戶48小時內(nèi)已經(jīng)短信/郵箱進行過通知了,那么將不再進行通知。
剖析
- 存在兩個主題:短信通知和郵箱通知
- 存在兩種觀察者:設(shè)置了短信通知且賬戶余額到達閾值的用戶,設(shè)置了郵箱通知且賬戶余額到達閾值的用戶。
- 用spring的定時器,每10分鐘去數(shù)據(jù)庫獲取某個主題已經(jīng)達到閾值且開始了該主題的提醒功能的用戶
- 用spring的@Asycn注解異步短信通知,郵箱通知的相關(guān)方法
- 用redis設(shè)置用戶短信/郵箱為鍵名,設(shè)置過期時間為48小時。如果獲取不到該鍵值對,說明其在觀察者行列
代碼
觀察者父類
/** * 訂閱觀察者 * @author Administrator * */ @Component //標(biāo)志為多例 @Scope(value=ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class SubscriberObserver implements Observer{ private String email; private String phone; private String username; @Autowired UserFunctionService UserFunctionService; @Override public void update(Observable o, Object arg) { if(o instanceof EmailAlertSubject){ UserFunctionService.alertUserEmail(email,username); } if(o instanceof PhoneAlertSubject){ UserFunctionService.alertUserPhone(phone,username); } } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public SubscriberObserver() { super(); // TODO Auto-generated constructor stub } }
主題
/** * email提醒主題 * @author Administrator * */ @Component public class EmailAlertSubject extends Observable{ public void alert(){ this.setChanged(); //如果用拉的方式,這么調(diào)用 this.notifyObservers(); } }
/** * 短信提醒主題 * @author Administrator * */ @Component public class PhoneAlertSubject extends Observable{ public void alert(){ this.setChanged(); //如果用拉的方式,這么調(diào)用 this.notifyObservers(); } }
定時器
/** * 定時給訂閱了短信提醒和email提醒的用戶服務(wù) * @author Administrator * */ @Component public class TimeAlertTaskUtil { @Autowired CommonUserService commonUserService; @Autowired JedisConnectionFactory factory; @Autowired EmailAlertSubject emailSubject; @Autowired PhoneAlertSubject phoneSubject; private static final String emailKeyName = "emailAlert:"; private static final String phoneKeyName = "phoneAlert:"; /** * 定時獲取需要email提醒的用戶,每10分鐘調(diào)用一次 */ @Scheduled(fixedDelay = 1000 * 60 * 10) public void alertEmailTask() { // 1.獲取數(shù)據(jù)庫中達到了閾值的用戶 List<User> emails = commonUserService.getUserAlertEmailAndName(); // 2.查看redis中是否有達到閾值,且48小時已經(jīng)通知的用戶,將其排除在觀察者行列,最終得出觀察者隊伍 List<SubscriberObserver> informEmail = getInformObserver(emails); // 3.創(chuàng)建主題,添加觀察者 addObservers(emailSubject, informEmail); // 4.通知 emailSubject.alert(); // 5.將已經(jīng)通知的觀察者信息存儲到reids內(nèi),設(shè)置過期時間為一天 setRedisCache(emails); // 6.將觀察者從主題中移除 deleteObservers(emailSubject, informEmail); } /** * 定時獲取需要短信提醒的用戶,每10分鐘調(diào)用一次 * */ @Scheduled(fixedDelay = 1000 * 60 * 10) public void alertPhoneTask() { // 1.獲取數(shù)據(jù)庫中達到了閾值的用戶 List<User> phones = commonUserService.getUserAlertPhoneAndName(); // 2.查看redis中是否有達到閾值,且今天已經(jīng)通知的用戶,將其排除在觀察者行列,最終得出觀察者隊伍 List<SubscriberObserver> informPhones = getInformObserver(phones); // 3.創(chuàng)建主題,添加觀察者 addObservers(phoneSubject, informPhones); // 4.通知 phoneSubject.alert(); // 5.將已經(jīng)通知的觀察者信息存儲到reids內(nèi),設(shè)置過期時間為一天 setRedisCache(phones); // 6.將觀察者從主題中移除 deleteObservers(phoneSubject, informPhones); } /** * ------------------------------------------------------------------------ * ----------------------------------------------------- **/ /** * 過濾掉今天已經(jīng)email提醒的用戶,返回真正需要提醒的觀察者列表 * * @param emails * @return */ private List<SubscriberObserver> getInformObserver( List<User> users) { List<SubscriberObserver> obs = new ArrayList<SubscriberObserver>(); Jedis jedis = factory.getConnection().getNativeConnection(); for (User user : users) { String value; SubscriberObserver observer = (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); } } } return obs; } /** * 將指定的觀察者列表添加到指定的主題 * * @param subject * @param list */ private void addObservers(Observable subject, List<SubscriberObserver> list) { for (SubscriberObserver obs : list) { subject.addObserver(obs); } } private void deleteObservers(Observable subject, List<SubscriberObserver> list) { for (SubscriberObserver obs : list) { subject.deleteObserver(obs); } } /** * 將列表的值作為鍵,存入redis,過期時間為48小時 * * @param list */ private void setRedisCache(List<User> users) { Jedis jedis = factory.getConnection().getNativeConnection(); for (User user : 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); } } } }
總結(jié)
代碼是不全面的,只是個示例而已。關(guān)于短信通知和郵箱通知的服務(wù)類和工具類并沒有給出,因為里面涉及到一些隱私參數(shù)。所以關(guān)于異步通知示例代碼沒有,但使用Spring管理的@Async注解和在spring進行一定的配置即可,可以在我的另外一篇博客找到關(guān)于異步通知的示例代碼。
事實上根據(jù)需求,可以使用redis的發(fā)布訂閱,或者消息隊列mq來實現(xiàn)類似的功能。但為了加深對設(shè)計模式的理解,所以寫了一個不是很純正的觀察者模式來模仿發(fā)布訂閱的操作。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java案例之HashMap集合存儲學(xué)生對象并遍歷
這篇文章主要介紹了Java案例之HashMap集合存儲學(xué)生對象并遍歷,創(chuàng)建一個HashMap集合,鍵是學(xué)號(String),值是學(xué)生對象(Student),存儲三個鍵值對元素并遍歷,下文具體操作需要的朋友可以參考一下2022-04-04使用Spring的JAVA Mail支持簡化郵件發(fā)送功能
這篇文章主要為大家詳細介紹了使用Spring的JAVA Mail支持簡化郵件發(fā)送功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-04-04SpringBoot 整合Tess4J庫實現(xiàn)圖片文字識別案例詳解
Tess4J是一個基于Tesseract OCR引擎的Java接口,可以用來識別圖像中的文本,說白了,就是封裝了它的API,讓Java可以直接調(diào)用,今天給大家分享一個SpringBoot整合Tess4j庫實現(xiàn)圖片文字識別的小案例2023-10-10SpringBoot整合SSO(single sign on)單點登錄
這篇文章主要介紹了SpringBoot整合SSO(single sign on)單點登錄,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06SpringMVC接收java.util.Date類型數(shù)據(jù)的2種方式小結(jié)
這篇文章主要介紹了使用SpringMVC接收java.util.Date類型數(shù)據(jù)的2種方法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08