Java使用觀察者模式異步短信/郵箱提醒用戶羣

需求

用戶中有人設置了賬戶餘額達到閾值時,短信/郵箱進行提醒的服務。我們將需要在他賬戶餘額閾值達到指定數值的時候進行短信/郵箱消息通知,允許賬戶餘額閾值出現偏差的時候通知,如果某個用戶48小時內已經短信/郵箱進行過通知了,那麼將不再進行通知。

剖析

  • 存在兩個主題:短信通知和郵箱通知
  • 存在兩種觀察者:設置了短信通知且賬戶餘額到達閾值的用戶,設置了郵箱通知且賬戶餘額到達閾值的用戶。
  • 用spring的定時器,每10分鐘去數據庫獲取某個主題已經達到閾值且開始了該主題的提醒功能的用戶
  • 用spring的@Asycn註解異步短信通知,郵箱通知的相關方法
  • 用redis設置用戶短信/郵箱爲鍵名,設置過期時間爲48小時。如果獲取不到該鍵值對,說明其在觀察者行列

代碼

觀察者父類

/**
 * 訂閱觀察者
 * @author Administrator
 *
 */
@Component
//標誌爲多例
@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();
         //如果用拉的方式,這麼調用
         this.notifyObservers();
    }
}
/**
 * 短信提醒主題
 * @author Administrator
 *
 */
@Component
public class PhoneAlertSubject extends Observable{

    public void alert(){
         this.setChanged();
         //如果用拉的方式,這麼調用
         this.notifyObservers();
    }
}

定時器

/**
 * 定時給訂閱了短信提醒和email提醒的用戶服務
 * @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分鐘調用一次
     */
    @Scheduled(fixedDelay = 1000 * 60 * 10)
    public void alertEmailTask() {
        // 1.獲取數據庫中達到了閾值的用戶
        List<User> emails = commonUserService.getUserAlertEmailAndName();
        // 2.查看redis中是否有達到閾值,且48小時已經通知的用戶,將其排除在觀察者行列,最終得出觀察者隊伍
        List<SubscriberObserver> informEmail = getInformObserver(emails);
        // 3.創建主題,添加觀察者
        addObservers(emailSubject, informEmail);
        // 4.通知
        emailSubject.alert();

        // 5.將已經通知的觀察者信息存儲到reids內,設置過期時間爲一天
        setRedisCache(emails);
        // 6.將觀察者從主題中移除
        deleteObservers(emailSubject, informEmail);
    }

    /**
     * 定時獲取需要短信提醒的用戶,每10分鐘調用一次
     * 
     */
    @Scheduled(fixedDelay = 1000 * 60 * 10)
    public void alertPhoneTask() {
        // 1.獲取數據庫中達到了閾值的用戶
        List<User> phones = commonUserService.getUserAlertPhoneAndName();
        // 2.查看redis中是否有達到閾值,且今天已經通知的用戶,將其排除在觀察者行列,最終得出觀察者隊伍
        List<SubscriberObserver> informPhones = getInformObserver(phones);

        // 3.創建主題,添加觀察者
        addObservers(phoneSubject, informPhones);
        // 4.通知
        phoneSubject.alert();
        // 5.將已經通知的觀察者信息存儲到reids內,設置過期時間爲一天
        setRedisCache(phones);
        // 6.將觀察者從主題中移除
        deleteObservers(phoneSubject, informPhones);
    }

    /**
     * ------------------------------------------------------------------------
     * -----------------------------------------------------
     **/
    /**
     * 過濾掉今天已經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);
            }
        }
    }
}

總結

代碼是不全面的,只是個示例而已。關於短信通知和郵箱通知的服務類和工具類並沒有給出,因爲裏面涉及到一些隱私參數。所以關於異步通知示例代碼沒有,但使用Spring管理的@Async註解和在spring進行一定的配置即可,可以在我的另外一篇博客找到關於異步通知的示例代碼。

事實上根據需求,可以使用redis的發佈訂閱,或者消息隊列mq來實現類似的功能。但爲了加深對設計模式的理解,所以寫了一個不是很純正的觀察者模式來模仿發佈訂閱的操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章