在前兩篇文章中, 我們實(shí)現(xiàn)了同步/異步發(fā)送短信以及限制發(fā)送短信頻率.這一篇, 我們介紹一下限制每日向同一個(gè)用戶(根據(jù)手機(jī)號(hào)和ip判斷)發(fā)送短信的次數(shù)
1、數(shù)據(jù)表結(jié)構(gòu)
由于需要記錄整天的發(fā)送記錄, 因此這里我們將數(shù)據(jù)保存到數(shù)據(jù)庫(kù)中. 數(shù)據(jù)表結(jié)構(gòu)如下:
type為驗(yàn)證碼的類型, 比如注冊(cè), 重置密碼等.
sendTime的默認(rèn)值為當(dāng)前時(shí)間.
2、限制日發(fā)送次數(shù)
我們這里需要用到上一篇中提到的接口和實(shí)體類.
DailyCountFilter.java
public class DailyCountFilter implements SmsFilter { private int ipDailyMaxSendCount; private int mobileDailyMaxSendCount; private SmsDao smsDao; // 省略了部分無(wú)用代碼 @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; }}
主要代碼很簡(jiǎn)單, 首先判斷向指定的手機(jī)號(hào)發(fā)送的次數(shù)是否達(dá)到了日最大發(fā)送次數(shù), 之后再判斷指定的ip請(qǐng)求發(fā)送的次數(shù)是否達(dá)到了最大次數(shù). 如果都沒(méi)有, 則將本次發(fā)送的手機(jī)號(hào), ip等信息保存到數(shù)據(jù)庫(kù)中.
當(dāng)然, 這個(gè)類存在一定的問(wèn)題: 在判斷是否超過(guò)最大次數(shù)到保存實(shí)體數(shù)據(jù)之間可能已經(jīng)有其他線程保存了新的數(shù)據(jù). 造成上面的兩個(gè)判斷并不是絕對(duì)的準(zhǔn)確.
我們可以使用序列化等級(jí)的事務(wù)保證不會(huì)發(fā)生錯(cuò)誤, 但是代價(jià)太高. 因此我們這里不做處理. 因?yàn)槲覀兦懊嬉呀?jīng)實(shí)現(xiàn)了限制發(fā)送頻率. 如果先使用FrequencyFilter過(guò)濾一次, 限制發(fā)送頻率, 那么基本上不可能出現(xiàn)前面說(shuō)的問(wèn)題.
還有一個(gè)問(wèn)題: 隨著時(shí)間的推移, 這個(gè)表會(huì)越來(lái)越大, 造成查詢的性能相當(dāng)?shù)牟? 我們可以向上一篇中那樣, 每隔一段時(shí)間就刪除無(wú)用的數(shù)據(jù); 也可以動(dòng)態(tài)的創(chuàng)建表, 然后向新表中插入數(shù)據(jù).
3、使用動(dòng)態(tài)表
這里我們采用第二種方案: 數(shù)據(jù)表的名字為"sms_四位年_兩位月", 比如"sms_2016_02". 插入數(shù)據(jù)時(shí)根據(jù)現(xiàn)在的時(shí)間獲得表名, 然后再插入. 另外使用Quartz在每月的20號(hào)2點(diǎn)生成下個(gè)月以及下下個(gè)月的數(shù)據(jù)表:
我們首先修改DailyCountFilter類, 在這個(gè)類中添加任務(wù)計(jì)劃, 定時(shí)生成數(shù)據(jù)表:
DailyCountFilter.java
// 在上面代碼的基礎(chǔ)上, 再添加如下代碼public class DailyCountFilter implements SmsFilter { private Scheduler sched; @Override public void init() throws SchedulerException { smsDao.createTable(0); // 創(chuàng)建這個(gè)月的數(shù)據(jù)表 smsDao.createTable(1); // 創(chuàng)建下個(gè)月的數(shù)據(jù)表 SchedulerFactory sf = new StdSchedulerFactory(); sched = sf.getScheduler(); // 創(chuàng)建Quartz容器 JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("smsDao", smsDao); // 創(chuàng)建運(yùn)行任務(wù)時(shí)需要使用的數(shù)據(jù)map // 創(chuàng)建job對(duì)象, 該對(duì)象執(zhí)行實(shí)際的任務(wù) JobDetail job = JobBuilder.newJob(CreateSmsTableJob.class) .usingJobData(jobDataMap) .withIdentity("create sms table job").build(); // 創(chuàng)建trigger對(duì)象, 該對(duì)象用來(lái)描述觸發(fā)執(zhí)行job的時(shí)間規(guī)則 // 比如這里的每月20號(hào)2點(diǎn) CronTrigger trigger = TriggerBuilder.newTrigger() .withIdentity("create sms table trigger") .withSchedule(CronScheduleBuilder.cronSchedule("0 0 2 20 * ?"))// 每月的20號(hào)2點(diǎn) .build(); sched.scheduleJob(job, trigger); // 注冊(cè)任務(wù)和觸發(fā)規(guī)則 sched.start(); // 啟動(dòng)調(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"); // 獲得傳過(guò)來(lái)的smsDao對(duì)象 smsDao.createTable(1); // 創(chuàng)建下個(gè)月的數(shù)據(jù)表 smsDao.createTable(2); // 創(chuàng)建下下個(gè)月的數(shù)據(jù)表 } }}
接下來(lái), 我們看看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語(yǔ)句 } /** * 保存SmsEntity實(shí)體對(duì)象 */ public void saveEntity(SmsEntity smsEntity){ String sql = "INSERT INTO " + getNowTableName() + " (mobile, ip, type) VALUES(?, ?, ?)"; // 執(zhí)行sql語(yǔ)句 } /** * 獲得指定手機(jī)號(hào)今天請(qǐng)求發(fā)送短信的次數(shù) * * @param mobile 用戶手機(jī)號(hào) * @return 今天請(qǐng)求發(fā)送短信的次數(shù) */ public long getMobileCount(String mobile){ String sql = "SELECT count(id) FROM " + getNowTableName() + " WHERE mobile=? AND time >= CURDATE()"; // 執(zhí)行sql語(yǔ)句, 返回查詢結(jié)果 } // 省略了getIPCount方法 /** * 獲得現(xiàn)在使用的表的名字 */ private String getNowTableName() { return getTableName(0); } private DateFormat dateFormat = new SimpleDateFormat("yyyy_MM"); /** * 獲得相對(duì)現(xiàn)在偏移monthExcursion月的表名 * * @param monthExcursion 偏移的月數(shù) * @return 對(duì)應(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方法成功運(yùn)行有個(gè)前提, 就是存在sms數(shù)據(jù)表. createTable方法會(huì)復(fù)制sms表的結(jié)構(gòu)創(chuàng)建新的數(shù)據(jù)表.
我們保留發(fā)送短信的數(shù)據(jù)(手機(jī)號(hào), ip, 時(shí)間等), 而不是直接刪除, 是因?yàn)橐院罂赡苄枰治鲞@些數(shù)據(jù), 獲取我們想要的信息, 比如判斷服務(wù)商短信的到達(dá)率、是否有人惡意發(fā)送短信等. 甚至可能獲得意外的"驚喜".
以上就是本文的全部?jī)?nèi)容,希望大家可以繼續(xù)關(guān)注。
新聞熱點(diǎn)
疑難解答
圖片精選