程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> java發送短信系列之限制發送頻率

java發送短信系列之限制發送頻率

編輯:關於JAVA

java發送短信系列之限制發送頻率。本站提示廣大學習愛好者:(java發送短信系列之限制發送頻率)文章只能為提供參考,不一定能成為您想要的結果。以下是java發送短信系列之限制發送頻率正文


本篇是發送短信的第二部門, 這裡我們引見一下若何限制向統一個用戶(依據手機號和ip)發送短信的頻率。

1、應用session

假如是web法式, 那末在session中記載前次發送的時光也能夠, 然則可以被繞曩昔. 最簡略的, 直接重啟閱讀器 或許 消除cache等可以標志session的數據, 那末便可以繞過session中的記載. 固然許多人都不是盤算機專業的, 也沒學過這些. 然則我們須要留意的是, 之所以限制發送頻率, 是為了避免"短信炸彈", 也就是有人歹意的頻仍的要求向某個手機號碼發送短信. 所以這小我是有能夠理解這些常識的.

上面我們應用"全局"的數據限制向統一個用戶發送頻率. 我們先做一些"預備"任務

2、界說接口、實體類

我們須要的實體類以下:

SmsEntity.java

public class SmsEntity{
  private Integer id;
  private String mobile;
  private String ip;
  private Integer type;
  private Date time;
  private String captcha;

  // 省略結構辦法和getter、setter辦法
}

過濾接口以下:

SmsFilter.java

public interface SmsFilter {

  /**
   * 初始化該過濾器
   */
  void init() throws Exception;

  /**
   * 斷定短信能否可以發送.
   * @param smsEntity 將要發送的短信內容
   * @return 可以發送則前往true, 不然前往false
   */
  boolean filter(SmsEntity smsEntity);

  /**
   * 燒毀該過濾器
   */
  void destroy();

}

3、重要代碼

限制發送頻率, 須要記載某個手機號(IP)及前次發送短信的時光. 很合適Map去完成, 這裡我們先應用ConcurrentMap完成:

FrequencyFilter.java

public class FrequencyFilter implements SmsFilter {
  /**
   * 發送距離, 單元: 毫秒
   */
  private long sendInterval;
  private ConcurrentMap<String, Long> sendAddressMap = new ConcurrentHashMap<>();

  // 省略了部門無用代碼

  @Override
  public boolean filter(SmsEntity smsEntity) {
    if(setSendTime(smsEntity.getMobile()) && setSendTime(smsEntity.getIp())){
      return true;
    }
    return false;
  }

  /**
   * 將發送時光修正為以後時光.
   * 假如間隔前次發送的時光距離年夜於{@link #sendInterval}則設置發送時光為以後時光. 不然不修正任何內容.
   *
   * @param id 發送手機號 或 ip
   * @return 假如勝利將發送時光修正為以後時光, 則前往true. 不然前往false
   */
  private boolean setSendTime(String id) {
    long currentTime = System.currentTimeMillis();

    Long sendTime = sendAddressMap.putIfAbsent(id, currentTime);
    if(sendTime == null) {
      return true;
    }

    long nextCanSendTime = sendTime + sendInterval;
    if(currentTime < nextCanSendTime) {
      return false;
    }

    return sendAddressMap.replace(id, sendTime, currentTime);
  }
}

這裡, 重要的邏輯在setSendTime辦法中完成:

第25-28行: 起首假定用戶是第一次發送短信, 那末應當把如今的時光放到sendAddressMap中. 假如putIfAbsent前往null, 那末解釋用戶確切是第一次發送短信, 並且如今的時光也曾經放到了map中, 可以發送.

第30-33行: 假如用戶不是第一次發送短信, 那末就須要斷定前次發送短信的時光和如今的距離能否小於發送時光距離. 假如小於發送距離, 那末不克不及發送.

第35行: 假如時光距離足夠年夜, 那末須要測驗考試著將發送時光設置為以後時光.

  • 假如調換勝利, 那末可以發送短信.
  • 假如調換掉敗, 解釋有別的一個線程在本線程履行26-35行之間曾經停止了調換, 也就是說在適才曾經發送了一次短信.

1)、那末可以再反復履行25-35行, 確保相對准確.
2)、也能夠直接以為不克不及發送, 由於固然實際上"履行26-35行"的時光能夠年夜於"發送距離", 然則幾率有多年夜呢? 根本上可以疏忽了吧.
這段代碼算是完成了頻率的限制, 然則假如只要"入"而沒有"出"那末sendAddressMap占用的內容會愈來愈年夜, 直到發生OutOfMemoryError異常. 上面我們再添加代碼准時清算過時的數據.

4、清算過時數據

FrequencyFilter.java

/**
 * 在下面代碼的基本上, 再添加以下代碼
 */
public class FrequencyFilter implements SmsFilter {
  private long cleanMapInterval;
  private Timer timer = new Timer("sms_frequency_filter_clear_data_thread");

  @Override
  public void init() {
    timer.schedule(new TimerTask() {
      @Override
      public void run() {
        cleanSendAddressMap();
      }
    }, cleanMapInterval, cleanMapInterval);
  }

  /**
   * 將sendAddressMap中的一切過時數據刪除
   */
  private void cleanSendAddressMap() {
    long currentTime = System.currentTimeMillis();
    long expireSendTime = currentTime - sendInterval;

    for(String key : sendAddressMap.keySet()) {
      Long sendTime = sendAddressMap.get(key);
      if(sendTime < expireSendTime) {
        sendAddressMap.remove(key, sendTime);
      }
    }
  }

  @Override
  public void destroy() {
    timer.cancel();
  }
}

這段法式不算龐雜, 啟動一個准時器, 每隔cleanMapInterval毫秒履行一次cleanSendAddressMap辦法清算過時數據.

cleanSendAddressMap辦法中起首獲得以後時光, 依據以後時光取得一個時光值: 一切在這個時光以後發送短信的, 如今弗成以再次發送短信. 然後從全部map中刪除一切value小於這個時光值的鍵值對.

固然, 添加下面的代碼後, 最開端的代碼又有bug了: 當最初一行sendAddressMap.replace(id, sendTime, currentTime)履行掉敗時紛歧定是其他線程停止了調換, 也有能夠是清算線程把數據刪了. 所以我們須要修正setSendTime辦法最初幾行:

FrequencyFilter.java

private boolean setSendTime(String id) {
  // 省略後面的代碼
  if(sendAddressMap.replace(id, sendTime, currentTime)) {
    return true;
  }
  return sendAddressMap.putIfAbsent(id, currentTime) == null;
}

這裡假如調換勝利, 那末直接前往true.

假如調換不勝利. 那末能夠是其他線程先調換了(第一種情形); 也能夠是被清算線程刪除(第二種情形); 乃至可以能是先被清算線程刪除, 又有其他線程拔出了新的時光值(第三種情形).

  • 假如是第一種情形 或許 第三種情形, 那末情形和最開端剖析的一樣, 可以直接以為不克不及發送.
  • 假如是第二種情形, 那末應當是可以發送的.
  • 為了確認是哪一種情形, 我們可以履行一次putIfAbsent, 假如勝利, 解釋是第二種情形, 可以發送; 不然是第一種或許第三種情形, 不克不及發送.

至此, 限制發送時光的代碼就算是完成了. 固然, 這段法式還有一個小bug或許說"特征":

假設, IP為"192.168.0.1"的客戶要求向手機號"12345678900"發送短信, 然後在sendInterval以內又在IP為"192.168.0.2"的機械上要求向手機號"12345678900"發送短信. 那末短信將不會收回去, 並且手機號"12345678900"的前次發送時光被置為以後時光.

5、應用實例

上面我們供給一個Server層, 展現若何將上一篇和這一篇中的代碼整合到一路:

SmsService.java

public class SmsService{
  private Sms sms;
  private List<SmsFilter> filters;
  private Properties template;

  // 省略了部門代碼

  /**
   * 發送驗證碼
   *
   * @param smsEntity 發送短信的根本數據
   * @return 假如提交勝利, 前往0. 不然前往其他值.
   */
  public int sendCaptcha(SmsEntity smsEntity){
    for(SmsFilter filter : filters) {
      if(!filter.filter(smsEntity)){
        return 1;
      }
    }
    if(SmsEntity.REGISTER_TYPE.equals(smsEntity.getType())) {
      sendRegisterSms(smsEntity);
    }
    else{
      return 2;
    }
    return 0;
  }

  /**
   * 發送注冊驗證碼
   *
   * @param smsEntity 發送短信的根本數據
   */
  private void sendRegisterSms(SmsEntity smsEntity) {
    sms.sendMessage(smsEntity.getMobile(),
        template.getProperty("register").replace("{captcha}", smsEntity.getCaptcha()));
  }

}

以後將FrequencyFilter和上一篇中的AsyncSmsImpl經由過程set辦法"注入"出來便可。

以上就是本文的全體內容,願望對年夜家進修java法式設計有所贊助。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved