java發(fā)送短信系列之限制日發(fā)送次數(shù)
在前兩篇文章中, 我們實現(xiàn)了同步/異步發(fā)送短信以及限制發(fā)送短信頻率.這一篇, 我們介紹一下限制每日向同一個用戶(根據(jù)手機號和ip判斷)發(fā)送短信的次數(shù)
1、數(shù)據(jù)表結(jié)構(gòu)
由于需要記錄整天的發(fā)送記錄, 因此這里我們將數(shù)據(jù)保存到數(shù)據(jù)庫中. 數(shù)據(jù)表結(jié)構(gòu)如下:
type為驗證碼的類型, 比如注冊, 重置密碼等.
sendTime的默認值為當前時間.
2、限制日發(fā)送次數(shù)
我們這里需要用到上一篇中提到的接口和實體類.
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; } }
主要代碼很簡單, 首先判斷向指定的手機號發(fā)送的次數(shù)是否達到了日最大發(fā)送次數(shù), 之后再判斷指定的ip請求發(fā)送的次數(shù)是否達到了最大次數(shù). 如果都沒有, 則將本次發(fā)送的手機號, ip等信息保存到數(shù)據(jù)庫中.
當然, 這個類存在一定的問題: 在判斷是否超過最大次數(shù)到保存實體數(shù)據(jù)之間可能已經(jīng)有其他線程保存了新的數(shù)據(jù). 造成上面的兩個判斷并不是絕對的準確.
我們可以使用序列化等級的事務(wù)保證不會發(fā)生錯誤, 但是代價太高. 因此我們這里不做處理. 因為我們前面已經(jīng)實現(xiàn)了限制發(fā)送頻率. 如果先使用FrequencyFilter過濾一次, 限制發(fā)送頻率, 那么基本上不可能出現(xiàn)前面說的問題.
還有一個問題: 隨著時間的推移, 這個表會越來越大, 造成查詢的性能相當?shù)牟? 我們可以向上一篇中那樣, 每隔一段時間就刪除無用的數(shù)據(jù); 也可以動態(tài)的創(chuàng)建表, 然后向新表中插入數(shù)據(jù).
3、使用動態(tài)表
這里我們采用第二種方案: 數(shù)據(jù)表的名字為"sms_四位年_兩位月", 比如"sms_2016_02". 插入數(shù)據(jù)時根據(jù)現(xiàn)在的時間獲得表名, 然后再插入. 另外使用Quartz在每月的20號2點生成下個月以及下下個月的數(shù)據(jù)表:
我們首先修改DailyCountFilter類, 在這個類中添加任務(wù)計劃, 定時生成數(shù)據(jù)表:
DailyCountFilter.java
// 在上面代碼的基礎(chǔ)上, 再添加如下代碼 public class DailyCountFilter implements SmsFilter { private Scheduler sched; @Override public void init() throws SchedulerException { smsDao.createTable(0); // 創(chuàng)建這個月的數(shù)據(jù)表 smsDao.createTable(1); // 創(chuàng)建下個月的數(shù)據(jù)表 SchedulerFactory sf = new StdSchedulerFactory(); sched = sf.getScheduler(); // 創(chuàng)建Quartz容器 JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("smsDao", smsDao); // 創(chuàng)建運行任務(wù)時需要使用的數(shù)據(jù)map // 創(chuàng)建job對象, 該對象執(zhí)行實際的任務(wù) JobDetail job = JobBuilder.newJob(CreateSmsTableJob.class) .usingJobData(jobDataMap) .withIdentity("create sms table job").build(); // 創(chuàng)建trigger對象, 該對象用來描述觸發(fā)執(zhí)行job的時間規(guī)則 // 比如這里的每月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); // 注冊任務(wù)和觸發(fā)規(guī)則 sched.start(); // 啟動調(diào)度 } @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); // 創(chuàng)建下個月的數(shù)據(jù)表 smsDao.createTable(2); // 創(chuàng)建下下個月的數(shù)據(jù)表 } } }
接下來, 我們看看SmsDao的部分代碼:
SmsDao.java
public class SmsDao { /** * 創(chuàng)建新的日志表 * * @param monthExcursion 偏移的月數(shù) */ public void createTable(int monthExcursion){ String sql = "CREATE TABLE IF NOT EXISTS " + getTableName(monthExcursion) + " LIKE sms"; // 執(zhí)行sql語句 } /** * 保存SmsEntity實體對象 */ public void saveEntity(SmsEntity smsEntity){ String sql = "INSERT INTO " + getNowTableName() + " (mobile, ip, type) VALUES(?, ?, ?)"; // 執(zhí)行sql語句 } /** * 獲得指定手機號今天請求發(fā)送短信的次數(shù) * * @param mobile 用戶手機號 * @return 今天請求發(fā)送短信的次數(shù) */ public long getMobileCount(String mobile){ String sql = "SELECT count(id) FROM " + getNowTableName() + " WHERE mobile=? AND time >= CURDATE()"; // 執(zhí)行sql語句, 返回查詢結(jié)果 } // 省略了getIPCount方法 /** * 獲得現(xiàn)在使用的表的名字 */ private String getNowTableName() { return getTableName(0); } private DateFormat dateFormat = new SimpleDateFormat("yyyy_MM"); /** * 獲得相對現(xiàn)在偏移monthExcursion月的表名 * * @param monthExcursion 偏移的月數(shù) * @return 對應(yīng)月的表名 */ 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數(shù)據(jù)表. createTable方法會復制sms表的結(jié)構(gòu)創(chuàng)建新的數(shù)據(jù)表.
我們保留發(fā)送短信的數(shù)據(jù)(手機號, ip, 時間等), 而不是直接刪除, 是因為以后可能需要分析這些數(shù)據(jù), 獲取我們想要的信息, 比如判斷服務(wù)商短信的到達率、是否有人惡意發(fā)送短信等. 甚至可能獲得意外的"驚喜".
以上就是本文的全部內(nèi)容,希望大家可以繼續(xù)關(guān)注。
相關(guān)文章
MyBatis-Plus如何使用枚舉自動關(guān)聯(lián)注入詳解
這篇文章主要給大家介紹了關(guān)于MyBatis-Plus如何使用枚舉自動關(guān)聯(lián)注入的相關(guān)資料,文中通過實例代碼介紹的非常詳細,對大家學習或者使用MyBatis-Plus具有一定的參考學習價值,需要的朋友可以參考下2022-03-03SpringBoot中時間類型 序列化、反序列化、格式處理示例代碼
這篇文章主要介紹了SpringBoot中時間類型 序列化、反序列化、格式處理示例代碼,代碼簡單易懂,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08springboot項目攔截前端請求中的特殊字符串(解決方案)
springboot項目中,需要對前端請求數(shù)據(jù)進行過濾,攔截特殊字符,本文通過實例代碼給大家分享完美解決方案,感興趣的朋友一起看看吧2023-10-10