自定义注解接口防刷

91 阅读1分钟

1、自定义一个注解类

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AcctessLimit {

    /**
     * 多少秒
     * @return
     */
    long second() default 5L;

    /**
     * 可以访问多少次
     * @return
     */
    long maxTime() default 3L;

    /**
     * 禁用时间
     * @return
     */
    long bidDenTime() default 60L;
}

2、编写自定义过滤器实现HandlerInterceptor类

@Slf4j
@Component
public class AccessLimintInterceptor implements HandlerInterceptor {
    private RedisTemplate redisTemplate;

    @Autowired
    public AccessLimintInterceptor(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 锁住时的key前缀
     */
    public static final String LOCK_PREFIX = "INTERFACE_LOCK";

    /**
     * 统计次数时的key前缀
     */
    public static final String COUNT_PREFIX = "INTERFACE_COUNT";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 判断访问的是否是接口方法
        if(handler instanceof HandlerMethod){
            // 访问的是接口方法,转化为待访问的目标方法对象
            HandlerMethod targetMethod = (HandlerMethod) handler;
            // 取出目标方法中的 AccessLimit 注解
            AcctessLimit accessLimit = targetMethod.getMethod().getAnnotation(AcctessLimit.class);
            // 判断此方法接口是否要进行防刷处理(方法上没有对应注解就代表不需要,不需要的话进行放行)
            if(!Objects.isNull(accessLimit)){
                // 需要进行防刷处理
                //取出数据
                String ip = request.getRemoteAddr();
                String uri = request.getRequestURI();
                String lockKey = LOCK_PREFIX + ip + uri;
                Object isLock = redisTemplate.opsForValue().get(lockKey);
                // 判断此ip用户访问此接口是否已经被禁用
                if (Objects.isNull(isLock)) {
                    // 还未被禁用
                    String countKey = COUNT_PREFIX + ip + uri;
                    Object count = redisTemplate.opsForValue().get(countKey);
                    long second = accessLimit.second();
                    long maxTime = accessLimit.maxTime();

                    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);
                            long forbiddenTime = accessLimit.bidDenTime();
                            // 禁用
                            redisTemplate.opsForValue().set(lockKey, 1, forbiddenTime, TimeUnit.SECONDS);
                            // 删除统计--已经禁用了就没必要存在了
                            redisTemplate.delete(countKey);
                            throw new ApiException("接口访问超限,请稍后尝试");
                        }
                    }
                } else {
                    // 此用户访问此接口已被禁用
                    throw new ApiException("接口访问超限,请稍后尝试");
                }
            }
        }
        return true;
   }
}

3、添加过滤器到应用

@Configuration
public class WebFilterConfig implements WebMvcConfigurer {
    private AccessLimintInterceptor accessLimintInterceptor;

    @Autowired
    public WebFilterConfig(AccessLimintInterceptor accessLimintInterceptor) {
        this.accessLimintInterceptor = accessLimintInterceptor;
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(accessLimintInterceptor).addPathPatterns("/**");
    }


}