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程序设计有所帮助。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索java发送短信
短信发送频率限制java、java发送邮件频率限制、qq邮箱发送频率限制、移动发送短信频率、sim卡发送短信频率,以便于您获取更多的相关知识。

时间: 2024-10-27 05:12:45

java发送短信系列之限制发送频率_java的相关文章

java发送短信系列之同步、异步发送短信_java

本篇本章是发送短信的第一部分, 说一下同步/异步发送短信的代码, 以后几篇我们稍微完善一下功能, 添加发送频率的限制和日发送次数的限制. 发送短信的方法可能不少, 我们的方法是使用服务商提供的服务. 一般来说, 这些服务都是和语言无关的, 这里我们使用java写示例程序.1.发送短信的接口 根据自己的情况选择服务商.2.开发文档 从开发文档中我们可以看到. 可以直接使用http请求也可以使用WebService请求发送短信. 由于DEMO文件夹下的java和jsp文件夹中的代码都是使用http请

Android开发中实现发送短信的小程序示例_Android

上图为代码结构图. 现在我们看下具体的代码. Send.java package cn.com.sms.send; import java.util.ArrayList; import java.util.Iterator; import android.app.Activity; import android.app.PendingIntent; import android.content.Intent; import android.os.Bundle; import android.te

php发送短信验证码完成注册功能_php实例

短信验证码注册,很简单,用的是  云通讯的短信系统(收费的,不过有测试的api给我们做测试).好了,不多说,进入正题.  1.收到到云通讯短信系统注册账号,然后下载他们的封装好的短信api接口代码,解压,然后找到CCPRestSDK.php文件和SendTemplateSMS.php文件,将其拉到根目录文件夹里. 2.打开SendTemplateSMS.php文件,首先注意include_once('./CCPRestSDK.php'),千万别包含错路径了,将云通讯给的测试主账号,主账号Toke

Android发送短信方法实例详解_Android

本文实例讲述了Android发送短信方法.分享给大家供大家参考,具体如下: 短信和打电话一样,都是android手机的基本功能,下面以实例说明android如何实现发送短信的功能. 程序如下所示: import java.util.regex.Matcher; import java.util.regex.Pattern; import android.app.Activity; import android.app.PendingIntent; import android.content.I

PHP发送短信代码分享_php技巧

方法一(比较好,推荐) //PHP发送短信 Monxin专用(PHP代码函数) //本代码基于Monxin 运行 //代码来源:Monxin ./config/functions.php function sms($config,$language,$pdo,$sender,$phone_number,$content){ //demo var_dump(sms(self::$config,self::$language,$pdo,"system","18074507509,

Android发送短信方法总结_Android

android API 中提供了SmsManager类处理短信.其中的sendTextMessage(num, null, content, pend, null)函数就是发送,具体介绍如下: SMS涉及的主要类SmsManager 实现SMS主要用到SmsManager类,该类继承自java.lang.Object类,下面我们介绍一下该类的主要成员.公有方法: 1.ArrayList<String> divideMessage(String text) 当短信超过SMS消息的最大长度时,将短

javascript发送短信验证码实现代码_javascript技巧

本文首先分析手机发送验证码的原理,再对javascript发送短信验证码予以实现,具体思路如下: 实现点击"发送验证码"按钮后,按钮依次显示为"59秒后重试"."58秒后重试"-直至倒计时至0秒时再恢复显示为"发送验证码".在倒计时期间按钮为禁用状态 . 第一步.获取按钮.绑定事件.设置定时器变量和计时变量 第二步.添加定时器,每隔1秒钟计时减 1,直至当计时小于等于 0 时清除定时器,按钮恢复为"发送验证码&quo

开发安卓android发送短信监听器

问题描述 开发安卓android发送短信监听器 发送者发送俩条短信,怎么屏蔽第二条,就是隐藏发送.最好详细一些,谢谢 解决方案 自己记录发送的条数http://www.linuxidc.com/Linux/2011-10/46071.htm 解决方案二: 屏蔽第二条?你是想让他不显示在手机上?让用手机的人不知道?

Windows Phone开发(24):启动器与选择器之发送短信

原文:Windows Phone开发(24):启动器与选择器之发送短信 本节我们通过一个简单的发送短信示例来演示一下如果配合使用PhoneNumberChooserTask和SmsComposeTask类. PhoneNumberChooserTask是选择器,它用于从你的电话簿里选择你要发送短信的电话号码: SmsComposeTask就是用来启动发送短信组件并显示发送窗口. 注意,这些操作都在用户的操控之中,发送短信一定会显示可视化页面的,而且不会偷偷地在后台发送,因为Windows pho