在前两篇文章中, 我们实现了同步/异步发送短信以及限制发送短信频率.这一篇, 我们介绍一下限制每日向同一个用户(根据手机号和ip判断)发送短信的次数
数据表结构
由于需要记录整天的发送记录, 因此这里我们将数据保存到数据库中. 数据表结构如下:
- type为验证码的类型, 比如注册, 重置密码等.
- sendTime的默认值为当前时间.
限制日发送次数
我们这里需要用到上一篇中提到的接口和实体类.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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
过滤一次, 限制发送频率, 那么基本上不可能出现前面说的问题.
还有一个问题: 随着时间的推移, 这个表会越来越大, 造成查询的性能相当的差. 我们可以向上一篇中那样, 每隔一段时间就删除无用的数据; 也可以动态的创建表, 然后向新表中插入数据.
使用动态表
这里我们采用第二种方案: 数据表的名字为”sms_四位年_两位月”, 比如”sms_2016_02”. 插入数据时根据现在的时间获得表名, 然后再插入. 另外使用Quartz在每月的20号2点生成下个月以及下下个月的数据表:
我们首先修改DailyCountFilter
类, 在这个类中添加任务计划, 定时生成数据表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| 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(); JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("smsDao", smsDao);
JobDetail job = JobBuilder.newJob(CreateSmsTableJob.class) .usingJobData(jobDataMap) .withIdentity("create sms table job").build();
CronTrigger trigger = TriggerBuilder.newTrigger() .withIdentity("create sms table trigger") .withSchedule(CronScheduleBuilder.cronSchedule("0 0 2 20 * ?")) .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.createTable(1); smsDao.createTable(2); } } }
|
接下来, 我们看看SmsDao
的部分代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| public class SmsDao {
public void createTable(int monthExcursion){ String sql = "CREATE TABLE IF NOT EXISTS " + getTableName(monthExcursion) + " LIKE sms"; }
public void saveEntity(SmsEntity smsEntity){ String sql = "INSERT INTO " + getNowTableName() + " (mobile, ip, type) VALUES(?, ?, ?)"; }
public long getMobileCount(String mobile){ String sql = "SELECT count(id) FROM " + getNowTableName() + " WHERE mobile=? AND send_time >= CURDATE()"; }
private String getNowTableName() { return getTableName(0); }
private DateFormat dateFormat = new SimpleDateFormat("yyyy_MM");
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, 时间等), 而不是直接删除, 是因为以后可能需要分析这些数据, 获取我们想要的信息, 比如判断服务商短信的到达率、是否有人恶意发送短信等. 甚至可能获得意外的”惊喜”.
最后, 示例代码可以在这里下载.
发送短信文章: