ip限制接口加强版

ip限制接口加强版

  • 传统ip限制接口实现思路
    • 1、定义注解IpLimiter
    • 2、ip调用频次限制器切面
    • 3、测试
            • postman调用接口第三次返回自己设定的信息【请求次数过于频繁,请联系管理员】
            • 传统ip接口限制就如上面代码一样没有问题,因为我们把这个ip限制放在了获取验证码接口上面,主要作用就是防止别人暴力破解,突然有一天线上有人反馈一个问题,如下图:
            • 分析:
            • 解决方案:

传统ip限制接口实现思路

1、定义注解IpLimiter

/*** 方法级的ip调用频次限制器**/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface IpLimiter {/*** 允许通过次数,要求为数值* 默认不限制为-1** @return 通过次数*/String permits() default "-1";/*** 限制时间区间,可为分钟、小时、天等** @return 时间单元*/TimeUnit timeUnit() default TimeUnit.DAYS;

2、ip调用频次限制器切面

package com.daihuowang.aop;import com.daihuowang.redis.RedisHelper;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;/*** ip调用频次限制器切面* 用于处理限制某ip在固定时间区间内的访问次数* 

* Created by zhangbingxiao on 2020-02-24*/ @Aspect @Component public class IpLimiterAspect implements InitializingBean {private Logger logger = LoggerFactory.getLogger(IpLimiterAspect.class);@Autowiredprivate RedisHelper redisHelper;@Overridepublic void afterPropertiesSet() {IpLimiterRedisTemplate.redis = redisHelper;}@Pointcut("@annotation(com.daihuowang.aop.IpLimiter)")public void myPointCut(){}@Before("myPointCut()")public void doBefore(JoinPoint joinPoint) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();// 获取当前方法上注解IpLimiter ipLimiter = method.getAnnotation(IpLimiter.class);// 判断当前是否需要记录operateBizIdif (StringUtils.isNotBlank(ipLimiter.permits())&& Objects.nonNull(ipLimiter.timeUnit())) {Integer permits = Integer.valueOf(ipLimiter.permits());TimeUnit timeUnit = ipLimiter.timeUnit();// 默认-1为不限量if (permits == -1) {return;}// 获取当前ipRequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;HttpServletRequest request = servletRequestAttributes.getRequest();String ip;String forwardAddress = request.getHeader("X-Forwarded-For");if (StringUtils.isBlank(forwardAddress)) {ip = request.getRemoteAddr();} else {String[] addresses = forwardAddress.split(",");ip = addresses[0];}String key = buildCacheKey(timeUnit) + ip;// 获取该ip是否有请求过Integer callTimes = Optional.ofNullable(IpLimiterRedisTemplate.get(key)).orElse(0) + 1;logger.info("methodName:{},当前调用ip:{},单位时间内调用次数:{}", method.getName(), ip, callTimes);if (callTimes > permits) {throw new RuntimeException("请求次数过于频繁,请联系管理员");}// 在当前时间区间内,新增callTimesIpLimiterRedisTemplate.set(key, 1L, timeUnit);}}/*** 根据日期单位构建缓存key** @param timeUnit* @return*/private String buildCacheKey(TimeUnit timeUnit) {Date now = new Date();if (TimeUnit.MINUTES.equals(timeUnit)) {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");return sdf.format(now);} else if (TimeUnit.HOURS.equals(timeUnit)) {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH");return sdf.format(now);}// 默认使用日级别限制else {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");return sdf.format(now);}}/*** ip限制器redis实例*/private static class IpLimiterRedisTemplate {/*** ip限制redis key*/static String IP_LIMITER_PREFIX = "IP_LIMITER:";/*** redis实例*/static RedisHelper redis;public static void set(String ip, Long duration, TimeUnit timeUnit) {redis.increment(IP_LIMITER_PREFIX + ip, 1L, duration, timeUnit);}public static Integer get(String ip) {Object object = Optional.ofNullable(redis.get(IP_LIMITER_PREFIX + ip)).orElse("0");return Integer.valueOf(object.toString());}} }

3、测试

 	@IpLimiter(permits = "2")@ApiOperation(value = "/testIpLimiter", notes = "测试ip调用频次限制器")@RequestMapping(value = "/testIpLimiter", method = RequestMethod.GET)public Result testIpLimiter() {return Result.buildSuccessResult("ok");}
postman调用接口第三次返回自己设定的信息【请求次数过于频繁,请联系管理员】

在这里插入图片描述

传统ip接口限制就如上面代码一样没有问题,因为我们把这个ip限制放在了获取验证码接口上面,主要作用就是防止别人暴力破解,突然有一天线上有人反馈一个问题,如下图:

在这里插入图片描述

分析:
这个环节是用户获取验证码,然后去支付金额购买商品,出现原因是因为大家连的是同一个wifi,然后都去购买,如果超过我们设定的次数就会报错,当然用户如果切成自己的4G,那肯定是没有问题的
针对这个问题,由于过两天我们会在一个酒店举办全球发布会,所以需求把请求次数做成可以配置的,这个怎么办呢?
解决方案:
新增type,用作类型区分
/*** 方法级的ip调用频次限制器** @author zhang*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface IpLimiter {/*** 允许通过次数,要求为数值* 默认不限制为-1** @return 通过次数*/String permits() default "-1";/*** 限制时间区间,可为分钟、小时、天等** @return 时间单元*/TimeUnit timeUnit() default TimeUnit.DAYS;/*** 限制类型 1表示取acm动态配置* @return*/String limitType() default "0";
}
底层修改

在这里插入图片描述
测试接口
在这里插入图片描述
阿里云acm配置
在这里插入图片描述在这里插入图片描述
以上就是我的设计思路,下一篇会讲实战设计模式
有问题可以私信我,请多多指正!


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部