【开源微服务项目】基于AOP+Redis+自定义注解实现细粒度接口IP访问限制,有多种方式限制访问。从控制粒度来看,可以分为:全局访问限制和接口访问限制。此条目介绍的是接口访问限制。本章讲解的主要内容在项目中的位置:scblogs/common/common-web/src/main/java/cn/sticki/common/web/anno/我的写法是基于AOP+自定义注解+Redis,并封装在一个单独的模块common-web下,需要用到的模块只需要导入包,在需要限制的方法上加上注解即可,非常方便松耦合?。唯一的缺点是该方法只支持给方法添加注解,不支持给类添加注解。如果要对一个类的所有方法进行限制,则必须为该类的所有方法添加注解。如果有同学想改善这个缺点,欢迎访问文章顶部的git链接,加入我们的项目?。实现步骤1.引入依赖实现这个功能,我们主要需要Redis和AOP的依赖。我们对redis使用spring,然后对aop使用org.aspectj下的aspectjweaver,主要是下面两个org.springframework。启动spring-boot-starter-data-redisorg.aspectjaspectjweaverPS:我的项目文件中引入了自己的common-redis模块,里面包含了springredis的依赖。2.写注解新建一个包,命名为anno,然后在包下新建一个注解,命名为RequestLimit,并新建一个类,命名为RequestLimitAspect,如下图:然后我们先写下内容注释:包cn.sticki.common。web.anno;importorg.springframework.core.Ordered;importorg.springframework.core.annotation.Order;importjava.lang.annotation.*;/***请求请求限制拦截**@author阿极*@version1.0*@date2022/7/3120:19*/@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@Documented@Order(Ordered.HIGHEST_PRECEDENCE)public@interfaceRequestLimit{/***访问次数允许,默认值120*/intcount()default120;/***间隔时间段,单位秒,默认值60*/inttime()default60;/***访问达到限制后需要等待的世界,单位为秒,默认值为120*/intwaits()default120;}说明:这里设置@Target(ElementType.METHOD),这意味着这个注解只能用在方法上。设置@Order(Ordered.HIGHEST_PRECEDENCE)是为了提高这个注解的优先级,即先判断访问限制,再做其他事情。然后通过注解中的参数在不同的接口下设置不同的限制,用户可以根据接口的要求进行设置。3、写逻辑(注解环绕)现在我们写围绕基于RequestLimit注解的操作的逻辑,即开始写RequestLimitAspect的内容,下面的操作都在这个类中进行。1、在新建的RequestLimitAspect类上添加使用@Aspect的注解,因为后面我们会自动将这个类注入到Spring中,所以要给它添加@Component注解。2、注入RedisTemplate既然我们要在redis(分布式)中记录访问次数,就必须要有redis的工具类。那么问题来了,我们是一个工具模块,不会启动,没有启动类,也没有配置文件,那么这种情况下我们怎么获取redis呢?答案是:需要RedisTemplate来找到导入我们的模块。因为这些bean都是spring控制的,包括我们现在写的RedisTemplate和RequestLimitAspect,以后都会在spring容器里面,所以我们直接在代码中找spring进行注入。如果以后介绍给我们的模块中有RedisTemplate,那我们自然就可以获取到。所以这一步很简单,直接注入即可,但是不要忘记定义一个key前缀,后面用来拼接redis的key。@ResourceprivateRedisTemplateredisTemplate;privatestaticfinalStringIPLIMIT_KEY="ipLimit:";3.定义一个方法在类中定义一个before方法,并在方法上使用@Around()注解,在Around中填写新建RequestLimit的全路径名。在这一步之后,代码看起来像我的:;导入org.aspectj.lang.annotation.Aspect;导入org.springframework.data.redis.core.RedisTemplate;导入org.springframework.stereotype.Component;导入javax.annotation.Resource;/***@author阿格*@version1.0*@date2022/7/3120:24*/@Aspect@Component@Slf4jpublicclassRequestLimitAspect{@ResourceprivateRedisTemplateredisTemplate;privatestaticfinalStringIPLIMIT_KEY="ipLimit:";/***带有{@linkRequestLimit}注解的拦截方法*/@Around("@annotation(cn.sticki.common.web.anno.RequestLimit)")publicObjectbefore(ProceedingJoinPointpjp)throwsThrowable{returnpjp.proceed();}}4。实现方法步骤:获取注解参数,获取当前请求的ip,生成key,获取key在redis中的访问次数,判断次数是否超出范围。范围,拒绝访问,返回提示,并将TTL重置为注释上的等待时间。如果注解上的等待时间没有超过范围,则允许访问并增加访问次数+1。如果无法查询到key,则将其添加到redis。将值设置为1,并将TTL设置为注释上的值。完整的实现代码如下:packagecn.sticki.common.web.anno;importcn.sticki.common.result.RestResult;importcn.sticki.common.web.utils.RequestUtils;importcn.sticki.common。web.utils.ResponseUtils;导入lombok.extern.slf4j.Slf4j;导入org.aspectj.lang.ProceedingJoinPoint;导入org.aspectj.lang.annotation.Around;导入org.aspectj。lang.annotation.Aspect;导入org.aspectj.lang.reflect.MethodSignature;导入org.springframework.data.redis.core.RedisTemplate;导入org.springframework.stereotype.Component;导入org.springframework.web.context.request。RequestContextHolder;导入org.springframework.web.context.request.ServletRequestAttributes;导入javax.annotation.Resource;导入javax.servlet.http.HttpServletRequest;importjava.lang.reflect.Method;importjava.util.concurrent.TimeUnit;/***@authorApole*@version1.0*@date2022/7/3120:24*/@Aspect@Component@Slf4jpublic类RequestLimitAspect{@ResourceprivateRedisTemplateredisTemplate;privatestaticfinalStringIPLIMIT_KEY="ipLimit:";/***带有{@linkRequestLimit}注解的拦截方法*/@Around("@annotation(cn.sticki.common.web.anno.RequestLimit)")publicObjectbefore(ProceedingJoinPointpjp)throwsThrowable{MethodSignaturesignature=(方法签名)pjp.getSignature();//1\。获取拦截的方法和方法名Methodmethod=signature.getMethod();StringmethodName=signature.getDeclaringTypeName()+"."+signature.getName();log.debug("拦截方法{}",methodName);//1.2获取注解参数RequestLimitlimit=method.getAnnotation(RequestLimit.class);//2\。获取当前线程的请求ServletRequestAattributes属性=(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();if(attribute==null){log.warn(this.getClass().getName()+"canonlybeusedinwebcontrollermethods");返回pjp.proceed();}HttpServletRequest请求=attribute.getRequest();//2.2获取当前请求的ipStringip=RequestUtils.getIpAddress(request);//3\。生成密钥Stringkey=IPLIMIT_KEY+methodName+":"+ip;//4\。获取Redis中的数据Integercount=redisTemplate.opsForValue().get(key);intnowCount=count==null?0:计数;if(nowCount>=limit.count()){//5\.超出限制,拒绝访问assertattribute.getResponse()!=null;log.info("访问频繁被拒绝访问,ip:{},method:{}",ip,signature.getName());ResponseUtils.objectToJson(attribute.getResponse(),RestResult.fail("频繁访问"));如果(nowCount==limit.count()){//5.2重新设置Redis时间为设置等待值log.debug("重新设置redis值为{},等待{}",nowCount+1,limit.waits());redisTemplate.opsForValue().set(key,nowCount+1,limit.waits(),TimeUnit.SECONDS);}返回空值;}if(count==null){//重置计数器log.debug("resetcounter");redisTemplate.opsForValue().set(key,1,limit.time(),TimeUnit.SECONDS);}else{//计数器+1,不重置TTLredisTemplate.opsForValue().increment(key);}log.debug("方法发布");返回pjp.proceed();}}5.开启spring自动装配Spring会自动注入spring.factories文件中的类,所以我们只需要写spring.factories在resources-INF文件夹下新建一个META,然后在这个下面新建一个文件文件夹,命名为spring.factories。文件内容如下:org.springframework.boot.autoconfigure.EnableAutoConfiguration=\cn.sticki.common.web.anno.RequestLimitAspect这里的fullyqualifiedname需要改成自己的类路径名。4.测试使用maven将刚刚编写的模块打包到本地,然后在其他服务中将该模块作为依赖引入,用于需要访问限制的方法。运行项目访问该接口进行测试。正常启动*多次访问后被拒绝*查看redis数据,发现符合我设置的条件。对文章内容感兴趣的可以微信搜索公众号:敲代码的老贾,获取相应资料