java发送短信系列之限制日发送次数

 更新时间:2016年02月21日 10:34:13   作者:BK  
这篇文章主要为大家详细介绍了java发送短信系列之限制日发送次数,详细介绍了限制每日向同一个用户(根据手机号和ip判断)发送短信次数的方法,感兴趣的小伙伴们可以参考一下

在前两篇文章中, 我们实现了同步/异步发送短信以及限制发送短信频率.这一篇, 我们介绍一下限制每日向同一个用户(根据手机号和ip判断)发送短信的次数

1、数据表结构

由于需要记录整天的发送记录, 因此这里我们将数据保存到数据库中. 数据表结构如下:

type为验证码的类型, 比如注册, 重置密码等.
sendTime的默认值为当前时间.

2、限制日发送次数

我们这里需要用到上一篇中提到的接口和实体类.

DailyCountFilter.java

public class DailyCountFilter implements SmsFilter {

  private int ipDailyMaxSendCount;
  private int mobileDailyMaxSendCount;
  private SmsDao smsDao;

  // 省略了部分无用代码

  @Override
  public boolean filter(SmsEntity smsEntity) {
    if (smsDao.getMobileCount(smsEntity.getMobile()) >= mobileDailyMaxSendCount) {
      return false;
    }
    if (smsDao.getIPCount(smsEntity.getIp()) >= ipDailyMaxSendCount) {
      return false;
    }
    smsDao.saveEntity(smsEntity);
    return true;
  }

}

主要代码很简单, 首先判断向指定的手机号发送的次数是否达到了日最大发送次数, 之后再判断指定的ip请求发送的次数是否达到了最大次数. 如果都没有, 则将本次发送的手机号, ip等信息保存到数据库中.

当然, 这个类存在一定的问题: 在判断是否超过最大次数到保存实体数据之间可能已经有其他线程保存了新的数据. 造成上面的两个判断并不是绝对的准确.

我们可以使用序列化等级的事务保证不会发生错误, 但是代价太高. 因此我们这里不做处理. 因为我们前面已经实现了限制发送频率. 如果先使用FrequencyFilter过滤一次, 限制发送频率, 那么基本上不可能出现前面说的问题.

还有一个问题: 随着时间的推移, 这个表会越来越大, 造成查询的性能相当的差. 我们可以向上一篇中那样, 每隔一段时间就删除无用的数据; 也可以动态的创建表, 然后向新表中插入数据.

3、使用动态表

这里我们采用第二种方案: 数据表的名字为"sms_四位年_两位月", 比如"sms_2016_02". 插入数据时根据现在的时间获得表名, 然后再插入. 另外使用Quartz在每月的20号2点生成下个月以及下下个月的数据表:

我们首先修改DailyCountFilter类, 在这个类中添加任务计划, 定时生成数据表:

DailyCountFilter.java

// 在上面代码的基础上, 再添加如下代码
public class DailyCountFilter implements SmsFilter {

  private Scheduler sched;

  @Override
  public void init() throws SchedulerException {
    smsDao.createTable(0); // 创建这个月的数据表
    smsDao.createTable(1); // 创建下个月的数据表

    SchedulerFactory sf = new StdSchedulerFactory();
    sched = sf.getScheduler(); // 创建Quartz容器

    JobDataMap jobDataMap = new JobDataMap();
    jobDataMap.put("smsDao", smsDao);  // 创建运行任务时需要使用的数据map 

    // 创建job对象, 该对象执行实际的任务
    JobDetail job = JobBuilder.newJob(CreateSmsTableJob.class)
        .usingJobData(jobDataMap)
        .withIdentity("create sms table job").build();

    // 创建trigger对象, 该对象用来描述触发执行job的时间规则
    // 比如这里的每月20号2点
    CronTrigger trigger = TriggerBuilder.newTrigger()
        .withIdentity("create sms table trigger")
        .withSchedule(CronScheduleBuilder.cronSchedule("0 0 2 20 * ?"))// 每月的20号2点
        .build();

    sched.scheduleJob(job, trigger);  // 注册任务和触发规则
    sched.start(); // 启动调度
  }

  @Override
  public void destroy() {
    try {
      sched.shutdown();
    }
    catch (SchedulerException e) {}
  }

  public static class CreateSmsTableJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
      JobDataMap dataMap = context.getJobDetail().getJobDataMap();
      SmsDao smsDao = (SmsDao) dataMap.get("smsDao"); // 获得传过来的smsDao对象
      smsDao.createTable(1); // 创建下个月的数据表
      smsDao.createTable(2); // 创建下下个月的数据表
     }
  }
}

接下来, 我们看看SmsDao的部分代码:

SmsDao.java

public class SmsDao {

  /**
   * 创建新的日志表
   *
   * @param monthExcursion 偏移的月数
   */
  public void createTable(int monthExcursion){
    String sql = "CREATE TABLE IF NOT EXISTS "
      + getTableName(monthExcursion) + " LIKE sms";
    // 执行sql语句
  }

  /**
   * 保存SmsEntity实体对象
   */
  public void saveEntity(SmsEntity smsEntity){
    String sql = "INSERT INTO "
      + getNowTableName() + " (mobile, ip, type) VALUES(?, ?, ?)";
    // 执行sql语句
  }

  /**
   * 获得指定手机号今天请求发送短信的次数
   *
   * @param mobile 用户手机号
   * @return 今天请求发送短信的次数
   */
  public long getMobileCount(String mobile){
    String sql = "SELECT count(id) FROM "
      + getNowTableName() + " WHERE mobile=? AND time >= CURDATE()";
    // 执行sql语句, 返回查询结果
  }

  // 省略了getIPCount方法

  /**
   * 获得现在使用的表的名字
   */
  private String getNowTableName() {
    return getTableName(0);
  }

  private DateFormat dateFormat = new SimpleDateFormat("yyyy_MM");

  /**
   * 获得相对现在偏移monthExcursion月的表名
   *
   * @param monthExcursion 偏移的月数
   * @return 对应月的表名
   */
  private String getTableName(int monthExcursion) {
    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.MONTH, monthExcursion);
    Date date = calendar.getTime();
    return "sms_" + dateFormat.format(date);
  }

}

SmsDao中的createTable方法成功运行有个前提, 就是存在sms数据表. createTable方法会复制sms表的结构创建新的数据表.

我们保留发送短信的数据(手机号, ip, 时间等), 而不是直接删除, 是因为以后可能需要分析这些数据, 获取我们想要的信息, 比如判断服务商短信的到达率、是否有人恶意发送短信等. 甚至可能获得意外的"惊喜".

以上就是本文的全部内容,希望大家可以继续关注。

相关文章

  • MyBatis-Plus如何使用枚举自动关联注入详解

    MyBatis-Plus如何使用枚举自动关联注入详解

    这篇文章主要给大家介绍了关于MyBatis-Plus如何使用枚举自动关联注入的相关资料,文中通过实例代码介绍的非常详细,对大家学习或者使用MyBatis-Plus具有一定的参考学习价值,需要的朋友可以参考下
    2022-03-03
  • SpringBoot中时间类型 序列化、反序列化、格式处理示例代码

    SpringBoot中时间类型 序列化、反序列化、格式处理示例代码

    这篇文章主要介绍了SpringBoot中时间类型 序列化、反序列化、格式处理示例代码,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08
  • Java Selenide 简介与用法

    Java Selenide 简介与用法

    Selenium 是目前用的最广泛的Web UI 自动化测试框架,本文给大家介绍下Java Selenide使用,感兴趣的朋友一起看看吧
    2022-01-01
  • java使用软引用实现缓存机制示例

    java使用软引用实现缓存机制示例

    这篇文章主要为大家介绍了java使用软引用实现缓存机制示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • springboot项目拦截前端请求中的特殊字符串(解决方案)

    springboot项目拦截前端请求中的特殊字符串(解决方案)

    springboot项目中,需要对前端请求数据进行过滤,拦截特殊字符,本文通过实例代码给大家分享完美解决方案,感兴趣的朋友一起看看吧
    2023-10-10
  • 解决Spring Mvc中对象绑定参数重名的问题

    解决Spring Mvc中对象绑定参数重名的问题

    最近在工作中遇到了参数绑定的一个问题,发现网上这方面的资料较少,索性自己来总结下,下面这篇文章主要给大家介绍了关于如何解决Spring Mvc中对象绑定参数重名问题的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-08-08
  • springMVC图片上传的处理方式详解

    springMVC图片上传的处理方式详解

    这篇文章主要为大家详细介绍了springMVC图片上传的处理方式,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-08-08
  • Java精确抽取网页发布时间

    Java精确抽取网页发布时间

    这篇文章主要为大家详细介绍了Java精确抽取网页发布时间的相关资料,尽量做到精确无误,感兴趣的小伙伴们可以参考一下
    2016-06-06
  • zookeeper监听器原理的详解

    zookeeper监听器原理的详解

    今天小编就为大家分享一篇关于zookeeper监听器原理的详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • Elasticsearch模糊查询详细介绍

    Elasticsearch模糊查询详细介绍

    这篇文章主要给大家介绍了关于Elasticsearch模糊查询的相关资料,在数据库查询中模糊查询是一种强大的技术,可以用来搜索与指定模式匹配的数据,需要的朋友可以参考下
    2023-09-09

最新评论