场景:
发送短信属于付费业务,有时为了防止短信攻击,须要限制发送短信的频率,例如在1个小时以内最多发送11条短信.
如何实现呢?java
Date now=new Date(); Date oneHourAgo= //1个小时以前的时刻 //查询条件有两个:时间范围,手机号 List<SMS> smsList=this.smsService.query(fromTime,toTime,mobile); if(smsList.size()>11){ System.out.println("超出限制,禁止发送"); }else{ System.out.println("能够发送"); }
dao中:redis
/*** * 获取指定时间长度(范围)内,发送的短信次数 <br /> * 在指定时间长度(范围)内,发送的短信次数是否超出限制 * @param startDate * @param endDate * @param mobile * @return */ public Long count(String startDate, String endDate, String mobile) { CriteriaHelper criteriaHelper = CriteriaHelper.getInstance(this); return criteriaHelper.between("createTime",startDate,endDate) .eq("mobile",mobile) .count(); }
Service中:优化
/*** * 在指定时间长度(范围)内,发送的短信次数是否超出限制 * @param mobile * @return */ public boolean validateSMSSendCountByTimeRange(String mobile) { Date now=new Date(); //1小时前的时刻 Date oneHouseAgo = TimeHWUtil.getDateBeforeHour(now, 1); Long count = this.sMSDao.count(TimeHWUtil.formatDateTime(oneHouseAgo), TimeHWUtil.formatDateTime(now), mobile); if (count >= SMSUtil.LIMITCOUNT_SEND_SMS) { String msg="超出限制"; logger.warn(msg); smsLogger.warn(msg); return false; } return true; }
每次发送短信,要写入当前时间戳到redis:
String mobile="13718486139"; String time=String.valueOf(DateTimeUtil.getCurrentMillisecond()); RedisHelper.getInstance().saveKeyCache("limit_one_hour", mobile+"_"+time, time);
this
检查时先获取全部时间戳:.net
Map map=RedisHelper.getInstance().getAllKeyCache("limit_one_hour");
具体判断逻辑:code
@Test public void test_limitOneHour2(){ String mobile="13718486139"; int limitCount=11; int limitTime=60*60;//1小时,单位:秒 Map<String,String> map=new HashMap<String,String>(); map.put("13718486139_1445429819328", "1445431479437"); map.put("13718486139_1445429874699", "1445431485996"); map.put("13718486139_1445429874799", "1445431491527"); map.put("13718486139_1445430757886", "1445431496853"); System.out.println(map); List<Long>list=new ArrayList<Long>(); for(String key:map.keySet()){ if(key.startsWith(mobile)){ list.add(Long.parseLong(map.get(key))/1000); } } SortList<Long>sortUtil=new SortList<Long>(); sortUtil.Sort(list, "longValue", "desc"); int length=list.size(); int toIndex=0;//要截取的最大序号 if(limitCount>length){ toIndex=length; }else{ toIndex=limitCount; } List<Long>result=list.subList(0, toIndex); long delter=list.get(0).longValue()-list.get(toIndex-1).longValue(); long delterSecond=delter; System.out.println(delterSecond); if(delterSecond<limitTime){ System.out.println("超限"); }else{ System.out.println("能够继续发短信"); } System.out.println(result); }
步骤:
(1)把当前手机号的全部时间戳放入list中;orm
(2)对list排序,按时间顺序,从大到小;(时间越大,表示离如今越近)blog
(3)根据次数(limitCount)限制 来截取list;排序
(4)计算list中第一个元素和最后一个元素的差量,即limitCount条短信的时间跨度delterget
(5)若delter 小于时间限制limitTime,则表示超过限制,那么禁止发送短信
优化以后的代码:
public static boolean isLimit() { long n = System.currentTimeMillis(); Map records = RedisCacheUtil2.getPushRecordList(); if (ValueWidget.isNullOrEmpty(records)) { return false; } List<String> timestamps = new ArrayList<String>(records.values()); SortList<String> sortUtil = new SortList<String>(); sortUtil.sort(timestamps, null, "desc"); // 1 分钟以内不能超过 4(limitCount) int limitCount = 4; int limitTime = 60 * 1000;//1 分钟,单位:豪秒 int length = timestamps.size(); if (length < limitCount) { //没有超过限制 return false; } int toIndex = 0;//要截取的最大序号 /*if (limitCount + 1 > length) { toIndex = length; } else {*/ toIndex = limitCount; // } List<String> result = timestamps.subList(0, toIndex); //和当前时间比较 System.out.println("n :" + n); long delter = /*result.get(0))*/n - Long.parseLong(result.get(toIndex - 1)); long delterSecond = delter; System.out.println("delter :" + delter); System.out.println(delterSecond); if (delterSecond < limitTime) { System.out.println("record :" + HWJacksonUtils.getJsonP(result)); System.out.println("timestamps :" + HWJacksonUtils.getJsonP(timestamps)); System.out.println("超限"); return true; } else { System.out.println("能够继续发短信"); return false; } }
参考: https://my.oschina.net/hanchao/blog/1833612
参考:http://hw1287789687.iteye.com/blog/2250898