赞
踩
当我们实际项目经常出现接口恶意被刷的问题,让我们很头疼,我们可以使用ip黑名单等当时拦截恶意刷接口,也可以使用工具包guava的限流RateLimiter进行接口限流,当然我们也可以自己造轮子。
接下来我们使用注解 、拦截器 、redis实现接口进行接口防刷,废话不多说,直接上代码
防刷注解AccessLimit,直接作用在方法和类上
/** * @Author charles.yao * @Description 接口防刷注解 * @Date 2023/4/18 14:08 */ @Retention(RUNTIME) @Target({METHOD, TYPE}) public @interface AccessLimit { /** * 秒 * @return 多少秒内 */ long second() default 5L; /** * 最大访问次数 * @return 最大访问次数 */ long maxTime() default 3L; /** * 禁用时长,单位/秒 * @return 禁用时长 */ long forbiddenTime() default 120L; }
防刷拦截器AccessLimintInterceptor
/** * @Author charles.yao * @Description 接口防刷拦截器 * @Date 2023/4/18 14:08 */ @Slf4j public class AccessLimintInterceptor implements HandlerInterceptor { @Resource private RedisTemplate<String, Object> redisTemplate; /** * 锁住时的key前缀 */ public static final String LOCK_PREFIX = "LOCK"; /** * 统计次数时的key前缀 */ public static final String COUNT_PREFIX = "COUNT"; public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 自定义注解 + 反射 实现, 版本 2.0 if (handler instanceof HandlerMethod) { // 访问的是接口方法,转化为待访问的目标方法对象 HandlerMethod targetMethod = (HandlerMethod) handler; // 获取目标接口方法所在类的注解@AccessLimit AccessLimit targetClassAnnotation = targetMethod.getMethod().getDeclaringClass().getAnnotation(AccessLimit.class); // 特别注意不能采用下面这条语句来获取,因为 Spring 采用的代理方式来代理目标方法 // 也就是说targetMethod.getClass()获得是class org.springframework.web.method.HandlerMethod ,而不知我们真正想要的 Controller // AccessLimit targetClassAnnotation = targetMethod.getClass().getAnnotation(AccessLimit.class); // 定义标记位,标记此类是否加了@AccessLimit注解 boolean isBrushForAllInterface = false; String ip = request.getRemoteAddr(); String uri = request.getRequestURI(); long second = 0L; long maxTime = 0L; long forbiddenTime = 0L; if (!Objects.isNull(targetClassAnnotation)) { log.info("目标接口方法所在类上有@AccessLimit注解"); isBrushForAllInterface = true; second = targetClassAnnotation.second(); maxTime = targetClassAnnotation.maxTime(); forbiddenTime = targetClassAnnotation.forbiddenTime(); } // 取出目标方法中的 AccessLimit 注解 AccessLimit accessLimit = targetMethod.getMethodAnnotation(AccessLimit.class); // 判断此方法接口是否要进行防刷处理 if (!Objects.isNull(accessLimit)) { // 需要进行防刷处理,接下来是处理逻辑 second = accessLimit.second(); maxTime = accessLimit.maxTime(); forbiddenTime = accessLimit.forbiddenTime(); if (isForbindden(second, maxTime, forbiddenTime, ip, uri)) { throw new CommonException(ResultCode.ACCESS_FREQUENT); } } else { // 目标接口方法处无@AccessLimit注解,但还要看看其类上是否加了(类上有加,代表针对此类下所有接口方法都要进行防刷处理) if (isBrushForAllInterface && isForbindden(second, maxTime, forbiddenTime, ip, uri)) { throw new CommonException(ResultCode.ACCESS_FREQUENT); } } } return true; } /** * 判断某用户访问某接口是否已经被禁用/是否需要禁用 * * @param second 多长时间 单位/秒 * @param maxTime 最大访问次数 * @param forbiddenTime 禁用时长 单位/秒 * @param ip 访问者ip地址 * @param uri 访问的uri * @return ture为需要禁用 */ private boolean isForbindden(long second, long maxTime, long forbiddenTime, String ip, String uri) { String lockKey = LOCK_PREFIX + ip + uri; //如果此ip访问此uri被禁用时的存在Redis中的 key Object isLock = redisTemplate.opsForValue().get(lockKey); // 判断此ip用户访问此接口是否已经被禁用 if (Objects.isNull(isLock)) { // 还未被禁用 String countKey = COUNT_PREFIX + ip + uri; Object count = redisTemplate.opsForValue().get(countKey); if (Objects.isNull(count)) { // 首次访问 log.info("首次访问"); redisTemplate.opsForValue().set(countKey, 1, second, TimeUnit.SECONDS); } else { // 此用户前一点时间就访问过该接口,且频率没超过设置 if ((Integer) count < maxTime) { redisTemplate.opsForValue().increment(countKey); } else { log.info("{}禁用访问{}", ip, uri); // 禁用 redisTemplate.opsForValue().set(lockKey, 1, forbiddenTime, TimeUnit.SECONDS); // 删除统计--已经禁用了就没必要存在了 redisTemplate.delete(countKey); return true; } } } else { // 此用户访问此接口已被禁用 return true; } return false; } }
测试类
/** * @Author charles.yao * @Description 测试类 * @Date 2023/4/18 14:35 */ @RestController @RequestMapping("/pass") public class TestRest { @GetMapping("/get") // 表示此接口 3 秒内最大访问次数为 2,禁用时长为 40 秒 @AccessLimit(second = 3, maxTime = 2, forbiddenTime = 40L) public Result get() { log.info("执行【pass】-get()方法"); return Result.SUCCESS(); } }
简单用postman测试一下,功能完全没有问题
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。