在前兩篇文章中, 我們實現了同步/異步發送短信以及限制發送短信頻率.這一篇, 我們介紹一下限制每日向同一個用戶(根據手機號和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, 時間等), 而不是直接刪除, 是因為以後可能需要分析這些數據, 獲取我們想要的信息, 比如判斷服務商短信的到達率、是否有人惡意發送短信等. 甚至可能獲得意外的"驚喜".
以上就是本文的全部內容,希望大家可以繼續關注。