限制接口的访问次数
情景:发送手机验证码或者邮箱验证码时限制规则:一分钟只可以发一次,一天内也有次数限制。以防止恶意访问,降低服务器压力。
解决思路:获取用户ip地址,判断此ip是否首次访问,如果是首次访问,在redis创建minKey,dayKey.并设置minKey过期60s,dayKey为86400s,也就是24H。首次访问则次数加一。超过1次或一天超过限制次数时,禁止访问。
1.获取用户真实ip地址。
/** * 自定义访问对象工具类 * 获取对象的IP地址等信息 */ public class GetIpAddressUtil { /** * 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址, * * 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢? * 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。 * * 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130, * 192.168.1.100 * 用户真实IP为: 192.168.1.110 * @param request * @return */ public static String getIpInfo(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } } else if (ip.length() > 15) { String[] ips = ip.split(","); for (int index = 0; index < ips.length; index++) { String strIp = (String) ips[index]; if (!("unknown".equalsIgnoreCase(strIp))) { ip = strIp; break; } } } return ip; } }
2.Redis配置
public class MyJedisPool { private final static Logger logger = LoggerFactory.getLogger(MyJedisPool.class); private static JedisPool jedisPool = null; //redis服务配置 public static final String Redis_IP = "192.168.xx.xx"; public static final int Redis_PORT =6379; public static final String Redis_jedisUser ="";//密码 public static final int ExpireSeconds = 3600; //静态代码初始化池配置 static { try{ //创建jedis池配置实例 JedisPoolConfig config = new JedisPoolConfig(); //设置池配置项值 config.setMaxTotal(20); config.setMaxIdle(10); config.setMaxWaitMillis(3000); config.setTestOnBorrow(true); config.setTestOnReturn(false); //根据配置实例化jedis池 //jedisPool = new JedisPool(config,Redis_IP,Redis_PORT,1000*2,Redis_jedisUser); jedisPool = new JedisPool(config,SystemFileConfig.get("Redis_IP"), Integer.parseInt(SystemFileConfig.get("Redis_PORT")), 1000*2, SystemFileConfig.get("Redis_jedisUser"), Integer.parseInt(SystemFileConfig.get("Redis_database"))); }catch (Exception e) { logger.info("redis连接池异常",e); } } /**获得jedis对象*/ public static Jedis getJedisObject(){ return jedisPool.getResource(); } /**归还jedis对象*/ public static void returnJedisOjbect(Jedis jedis){ if (jedis != null) { jedis.close(); } } }
3.操作redis完成次数限制
String userIpAddr = GetIpAddressUtil.getIpInfo(request); Jedis jedis = MyJedisPool.getJedisObject();//获取jedis链接对象 boolean flag = true; flag = limitSendCount(userIpAddr,jedis); System.out.println(flag); //根据flag判断是否超出限制 if (jedis != null) { jedis.close(); //用完关闭jedis资源 } //判断是否超出限制 public boolean limitSendCount(String ip, Jedis jedis) { String value = jedis.get(ip+"MinLimte"); String dayValue = jedis.get(ip+"DayLimte"); if(value==null){ jedis.set(ip+"MinLimte", "1"); jedis.expire(ip+"MinLimte", 60);//设置过期时间60秒 if(dayValue==null) { jedis.set(ip+"DayLimte", "1"); jedis.expire(ip+"DayLimte", 86400);//设置过期时间24hours }else { jedis.incr(ip+"DayLimte"); //加一次 int parseIntDay = Integer.parseInt(dayValue); if(parseIntDay>20){ System.out.println("访问受限!!!!"); return false; } } return true; }else{ int parseInt = Integer.parseInt(value); int parseIntDay = Integer.parseInt(dayValue); 60秒内访问超过1次,或者一天超过20次,就禁止访问 if(parseInt>=1 || parseIntDay>20){ System.out.println("访问受限!!!!"); return false; } jedis.incr(ip+"MinLimte"); jedis.incr(ip+"DayLimte"); } return true; }
4.以上后端验证次数已完成。如果前后不分离,前端需要在点击按钮后禁止点击,按钮显示倒计时直至为0s方可再次点击时,前端js如下:
//发送成功后60秒内不可再次发送 var id = 60; //定时执行 var timeing = setInterval(function() { id = id - 1; $('#sendVcode').html(id + 's'); }, 1000); //延迟执行
window.setTimeout(function() { //结束定时,恢复按钮可用 clearInterval(timeing); $('#sendVcode').html('点击发送Send').removeAttr("disabled"); $("#sendVcode").removeClass("sendclass2");//设置自己的按钮样式 $("#sendVcode").addClass("sendclass1"); }, 60000);
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
todo 注解防刷