首 页最新软件下载排行文章资讯投稿发布下载专题
维维下载站
您的位置:首页编程开发网络编程编程其它 → java发送短信如何限制发送频率例子代码

java发送短信如何限制发送频率例子代码

来源:本站整理 发布时间:2016-2-21 17:49:08 人气:

本篇文章主要是详细介绍了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"的上一次发送时间被置为当前的时间。

相关下载
栏目导航
本类热门阅览