情景:發送手機驗證碼或者郵箱驗證碼時限制規則:一分鍾只可以發一次,一天內也有次數限制。以防止惡意訪問,降低服務器壓力。
解決思路:獲取用戶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);
