最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

java發(fā)送短信系列之限制發(fā)送頻率

 更新時(shí)間:2016年02月21日 10:25:18   作者:BK  
這篇文章主要為大家詳細(xì)介紹了java發(fā)送短信系列之限制發(fā)送頻率,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

本篇是發(fā)送短信的第二部分, 這里我們介紹一下如何限制向同一個(gè)用戶(根據(jù)手機(jī)號(hào)和ip)發(fā)送短信的頻率。

1、使用session

如果是web程序, 那么在session中記錄上次發(fā)送的時(shí)間也可以, 但是可以被繞過(guò)去. 最簡(jiǎn)單的, 直接重啟瀏覽器 或者 清除cache等可以標(biāo)記session的數(shù)據(jù), 那么就可以繞過(guò)session中的記錄. 雖然很多人都不是計(jì)算機(jī)專業(yè)的, 也沒(méi)學(xué)過(guò)這些. 但是我們需要注意的是, 之所以限制發(fā)送頻率, 是為了防止"短信炸彈", 也就是有人惡意的頻繁的請(qǐng)求向某個(gè)手機(jī)號(hào)碼發(fā)送短信. 所以這個(gè)人是有可能懂得這些知識(shí)的.

下面我們使用"全局"的數(shù)據(jù)限制向同一個(gè)用戶發(fā)送頻率. 我們先做一些"準(zhǔn)備"工作

2、定義接口、實(shí)體類

我們需要的實(shí)體類如下:

SmsEntity.java

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

  // 省略構(gòu)造方法和getter、setter方法
}

過(guò)濾接口如下:

SmsFilter.java

public interface SmsFilter {

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

  /**
   * 判斷短信是否可以發(fā)送.
   * @param smsEntity 將要發(fā)送的短信內(nèi)容
   * @return 可以發(fā)送則返回true, 否則返回false
   */
  boolean filter(SmsEntity smsEntity);

  /**
   * 銷毀該過(guò)濾器
   */
  void destroy();

}

3、主要代碼

限制發(fā)送頻率, 需要記錄某個(gè)手機(jī)號(hào)(IP)及上次發(fā)送短信的時(shí)間. 很適合Map去完成, 這里我們先使用ConcurrentMap實(shí)現(xiàn):

FrequencyFilter.java

public class FrequencyFilter implements SmsFilter {
  /**
   * 發(fā)送間隔, 單位: 毫秒
   */
  private long sendInterval;
  private ConcurrentMap<String, Long> sendAddressMap = new ConcurrentHashMap<>();

  // 省略了部分無(wú)用代碼

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

  /**
   * 將發(fā)送時(shí)間修改為當(dāng)前時(shí)間.
   * 如果距離上次發(fā)送的時(shí)間間隔大于{@link #sendInterval}則設(shè)置發(fā)送時(shí)間為當(dāng)前時(shí)間. 否則不修改任何內(nèi)容.
   *
   * @param id 發(fā)送手機(jī)號(hào) 或 ip
   * @return 如果成功將發(fā)送時(shí)間修改為當(dāng)前時(shí)間, 則返回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方法中實(shí)現(xiàn):

第25-28行: 首先假設(shè)用戶是第一次發(fā)送短信, 那么應(yīng)該把現(xiàn)在的時(shí)間放到sendAddressMap中. 如果putIfAbsent返回null, 那么說(shuō)明用戶確實(shí)是第一次發(fā)送短信, 而且現(xiàn)在的時(shí)間也已經(jīng)放到了map中, 可以發(fā)送.

第30-33行: 如果用戶不是第一次發(fā)送短信, 那么就需要判斷上次發(fā)送短信的時(shí)間和現(xiàn)在的間隔是否小于發(fā)送時(shí)間間隔. 如果小于發(fā)送間隔, 那么不能發(fā)送.

第35行: 如果時(shí)間間隔足夠大, 那么需要嘗試著將發(fā)送時(shí)間設(shè)置為當(dāng)前時(shí)間.

  • 如果替換成功, 那么可以發(fā)送短信.
  • 如果替換失敗, 說(shuō)明有另外一個(gè)線程在本線程執(zhí)行26-35行之間已經(jīng)進(jìn)行了替換, 也就是說(shuō)在剛才已經(jīng)發(fā)送了一次短信.

1)、那么可以再重復(fù)執(zhí)行25-35行, 確保絕對(duì)正確.
2)、也可以直接認(rèn)為不能發(fā)送, 因?yàn)殡m然理論上"執(zhí)行26-35行"的時(shí)間可能大于"發(fā)送間隔", 但是概率有多大呢? 基本上可以忽略了吧.
這段代碼算是實(shí)現(xiàn)了頻率的限制, 但是如果只有"入"而沒(méi)有"出"那么sendAddressMap占用的內(nèi)容會(huì)越來(lái)越大, 直到產(chǎn)生OutOfMemoryError異常. 下面我們?cè)偬砑哟a定時(shí)清理過(guò)期的數(shù)據(jù).

4、清理過(guò)期數(shù)據(jù)

FrequencyFilter.java

/**
 * 在上面代碼的基礎(chǔ)上, 再添加如下代碼
 */
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中的所有過(guò)期數(shù)據(jù)刪除
   */
  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();
  }
}

這段程序不算復(fù)雜, 啟動(dòng)一個(gè)定時(shí)器, 每隔cleanMapInterval毫秒執(zhí)行一次cleanSendAddressMap方法清理過(guò)期數(shù)據(jù).

cleanSendAddressMap方法中首先獲取當(dāng)前時(shí)間, 根據(jù)當(dāng)前時(shí)間獲得一個(gè)時(shí)間值: 所有在這個(gè)時(shí)間之后發(fā)送短信的, 現(xiàn)在不可以再次發(fā)送短信. 然后從整個(gè)map中刪除所有value小于這個(gè)時(shí)間值的鍵值對(duì).

當(dāng)然, 添加上面的代碼后, 最開(kāi)始的代碼又有bug了: 當(dāng)最后一行sendAddressMap.replace(id, sendTime, currentTime)執(zhí)行失敗時(shí)不一定是其他線程進(jìn)行了替換, 也有可能是清理線程把數(shù)據(jù)刪了. 所以我們需要修改setSendTime方法最后幾行:

FrequencyFilter.java

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

這里如果替換成功, 那么直接返回true.

如果替換不成功. 那么可能是其他線程先替換了(第一種情況); 也可能是被清理線程刪除了(第二種情況); 甚至可以能是先被清理線程刪除了, 又有其他線程插入了新的時(shí)間值(第三種情況).

  • 如果是第一種情況 或者 第三種情況, 那么情況和最開(kāi)始分析的一樣, 可以直接認(rèn)為不能發(fā)送.
  • 如果是第二種情況, 那么應(yīng)該是可以發(fā)送的.
  • 為了確認(rèn)是哪種情況, 我們可以執(zhí)行一次putIfAbsent, 如果成功, 說(shuō)明是第二種情況, 可以發(fā)送; 否則是第一種或者第三種情況, 不能發(fā)送.

至此, 限制發(fā)送時(shí)間的代碼就算是完成了. 當(dāng)然, 這段程序還有一個(gè)小bug或者說(shuō)"特性":

假如, IP為"192.168.0.1"的客戶請(qǐng)求向手機(jī)號(hào)"12345678900"發(fā)送短信, 然后在sendInterval之內(nèi)又在IP為"192.168.0.2"的機(jī)器上請(qǐng)求向手機(jī)號(hào)"12345678900"發(fā)送短信. 那么短信將不會(huì)發(fā)出去, 而且手機(jī)號(hào)"12345678900"的上次發(fā)送時(shí)間被置為當(dāng)前時(shí)間.

5、使用實(shí)例

下面我們提供一個(gè)Server層, 展示如何將上一篇以及這一篇中的代碼整合到一起:

SmsService.java

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

  // 省略了部分代碼

  /**
   * 發(fā)送驗(yàn)證碼
   *
   * @param smsEntity 發(fā)送短信的基本數(shù)據(jù)
   * @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;
  }

  /**
   * 發(fā)送注冊(cè)驗(yàn)證碼
   *
   * @param smsEntity 發(fā)送短信的基本數(shù)據(jù)
   */
  private void sendRegisterSms(SmsEntity smsEntity) {
    sms.sendMessage(smsEntity.getMobile(),
        template.getProperty("register").replace("{captcha}", smsEntity.getCaptcha()));
  }

}

之后將FrequencyFilter以及上一篇中的AsyncSmsImpl通過(guò)set方法"注入"進(jìn)去即可。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家學(xué)習(xí)java程序設(shè)計(jì)有所幫助。

相關(guān)文章

  • 通過(guò)JDBC連接oracle數(shù)據(jù)庫(kù)的十大技巧

    通過(guò)JDBC連接oracle數(shù)據(jù)庫(kù)的十大技巧

    通過(guò)JDBC連接oracle數(shù)據(jù)庫(kù)的十大技巧...
    2006-12-12
  • 深入理解SpringMVC中央調(diào)度器DispatcherServlet

    深入理解SpringMVC中央調(diào)度器DispatcherServlet

    這篇文章主要介紹了SpringMVC核心之中央調(diào)度器DispatcherServlet的相關(guān)知識(shí),包括SpringMVC請(qǐng)求處理過(guò)程及SrpingMVC容器和spring?IOC容器關(guān)系,需要的朋友可以參考下
    2022-05-05
  • Java高并發(fā)下鎖的優(yōu)化詳解

    Java高并發(fā)下鎖的優(yōu)化詳解

    這篇文章主要介紹了Java高并發(fā)下鎖的優(yōu)化詳解,鎖是最常用的同步方法之一,在高并發(fā)的環(huán)境下,激烈的鎖競(jìng)爭(zhēng)會(huì)導(dǎo)致程序的性能下降,下面是一些關(guān)于鎖的使用建議,可以把這種副作用降到最低,需要的朋友可以參考下
    2024-01-01
  • Hadoop使用hdfs指令查看hdfs目錄的根目錄顯示被拒的原因及解決方案

    Hadoop使用hdfs指令查看hdfs目錄的根目錄顯示被拒的原因及解決方案

    這篇文章主要介紹了Hadoop使用hdfs指令查看hdfs目錄的根目錄顯示被拒的原因及解決方案,分布式部署hadoop,服務(wù)機(jī)只有namenode節(jié)點(diǎn),主機(jī)包含其他所有節(jié)點(diǎn),本文給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2023-10-10
  • Redis集群與SSM整合使用方法

    Redis集群與SSM整合使用方法

    這篇文章主要介紹了Redis集群與SSM整合使用方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2017-12-12
  • jackson 如何將實(shí)體轉(zhuǎn)json json字符串轉(zhuǎn)實(shí)體

    jackson 如何將實(shí)體轉(zhuǎn)json json字符串轉(zhuǎn)實(shí)體

    這篇文章主要介紹了jackson 實(shí)現(xiàn)將實(shí)體轉(zhuǎn)json json字符串轉(zhuǎn)實(shí)體,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • 基于JDBC封裝的BaseDao(實(shí)例代碼)

    基于JDBC封裝的BaseDao(實(shí)例代碼)

    下面小編就為大家?guī)?lái)一篇基于JDBC封裝的BaseDao(實(shí)例代碼)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-07-07
  • Java精品項(xiàng)目瑞吉外賣之員工信息管理篇

    Java精品項(xiàng)目瑞吉外賣之員工信息管理篇

    這篇文章主要為大家詳細(xì)介紹了java精品項(xiàng)目-瑞吉外賣訂餐系統(tǒng),此項(xiàng)目過(guò)大,分為多章獨(dú)立講解,本篇內(nèi)容為員工信息分頁(yè)查詢與啟用或禁用員工狀態(tài),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • 深入探究Java多線程并發(fā)編程的要點(diǎn)

    深入探究Java多線程并發(fā)編程的要點(diǎn)

    這篇文章主要介紹了深入探究Java多線程并發(fā)編程的要點(diǎn),包括關(guān)鍵字synchronized的使用和wait()與notify()獲取對(duì)象鎖的三種方式,需要的朋友可以參考下
    2015-11-11
  • 淺談Springboot2.0防止XSS攻擊的幾種方式

    淺談Springboot2.0防止XSS攻擊的幾種方式

    本文主要介紹了Springboot2.0防止XSS攻擊的幾種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08

最新評(píng)論

晋江市| 上饶市| 班戈县| 阿拉善右旗| 百色市| 稷山县| 昭通市| 封丘县| 信宜市| 漳平市| 吉木萨尔县| 綦江县| 金山区| 阿图什市| 东至县| 厦门市| 奇台县| 龙州县| 潼南县| 台东市| 安吉县| 苍梧县| 涪陵区| 滦南县| 成武县| 渝中区| 香格里拉县| 弥渡县| 盐城市| 社旗县| 仙居县| 长白| 阜平县| 轮台县| 永登县| 留坝县| 庄浪县| 始兴县| 马鞍山市| 稷山县| 新丰县|